hedgequantx 2.6.161 → 2.6.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/src/menus/ai-agent-connect.js +181 -0
  3. package/src/menus/ai-agent-models.js +219 -0
  4. package/src/menus/ai-agent-oauth.js +292 -0
  5. package/src/menus/ai-agent-ui.js +141 -0
  6. package/src/menus/ai-agent.js +88 -1489
  7. package/src/pages/algo/copy-engine.js +449 -0
  8. package/src/pages/algo/copy-trading.js +11 -543
  9. package/src/pages/algo/smart-logs-data.js +218 -0
  10. package/src/pages/algo/smart-logs.js +9 -214
  11. package/src/pages/algo/ui-constants.js +144 -0
  12. package/src/pages/algo/ui-summary.js +184 -0
  13. package/src/pages/algo/ui.js +42 -526
  14. package/src/pages/stats-calculations.js +191 -0
  15. package/src/pages/stats-ui.js +381 -0
  16. package/src/pages/stats.js +14 -507
  17. package/src/services/ai/client-analysis.js +194 -0
  18. package/src/services/ai/client-models.js +333 -0
  19. package/src/services/ai/client.js +6 -489
  20. package/src/services/ai/index.js +2 -257
  21. package/src/services/ai/providers/direct-providers.js +323 -0
  22. package/src/services/ai/providers/index.js +8 -472
  23. package/src/services/ai/providers/other-providers.js +104 -0
  24. package/src/services/ai/proxy-install.js +249 -0
  25. package/src/services/ai/proxy-manager.js +29 -411
  26. package/src/services/ai/proxy-remote.js +161 -0
  27. package/src/services/ai/supervisor-optimize.js +215 -0
  28. package/src/services/ai/supervisor-sync.js +178 -0
  29. package/src/services/ai/supervisor.js +50 -515
  30. package/src/services/ai/validation.js +250 -0
  31. package/src/services/hqx-server-events.js +110 -0
  32. package/src/services/hqx-server-handlers.js +217 -0
  33. package/src/services/hqx-server-latency.js +136 -0
  34. package/src/services/hqx-server.js +51 -403
  35. package/src/services/position-constants.js +28 -0
  36. package/src/services/position-exit-logic.js +174 -0
  37. package/src/services/position-manager.js +90 -629
  38. package/src/services/position-momentum.js +206 -0
  39. package/src/services/projectx/accounts.js +142 -0
  40. package/src/services/projectx/index.js +40 -289
  41. package/src/services/projectx/trading.js +180 -0
  42. package/src/services/rithmic/contracts.js +218 -0
  43. package/src/services/rithmic/handlers.js +2 -208
  44. package/src/services/rithmic/index.js +28 -712
  45. package/src/services/rithmic/latency-tracker.js +182 -0
  46. package/src/services/rithmic/market-data-decoders.js +229 -0
  47. package/src/services/rithmic/market-data.js +1 -278
  48. package/src/services/rithmic/orders-fast.js +246 -0
  49. package/src/services/rithmic/orders.js +1 -251
  50. package/src/services/rithmic/proto-decoders.js +403 -0
  51. package/src/services/rithmic/protobuf.js +7 -443
  52. package/src/services/rithmic/specs.js +146 -0
  53. package/src/services/rithmic/trade-history.js +254 -0
  54. package/src/services/strategy/hft-signal-calc.js +147 -0
  55. package/src/services/strategy/hft-tick.js +33 -133
  56. package/src/services/tradovate/index.js +6 -119
  57. package/src/services/tradovate/orders.js +145 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @fileoverview Rithmic Trade History
3
+ * Handles trade history fetching and P&L calculation
4
+ *
5
+ * NO FAKE DATA - Only real values from Rithmic API
6
+ */
7
+
8
+ const { logger } = require('../../utils/logger');
9
+ const { getTickMultiplier } = require('./specs');
10
+
11
+ const log = logger.scope('RithmicTradeHistory');
12
+
13
+ /**
14
+ * Get trade history from Rithmic API
15
+ * Uses RequestShowOrderHistory to fetch historical fills
16
+ * @param {Object} service - RithmicService instance
17
+ * @param {string} accountId - Optional account filter
18
+ * @param {number} days - Number of days to fetch (default: 30)
19
+ * @returns {Promise<{success: boolean, trades: Array}>}
20
+ */
21
+ const getTradeHistory = async (service, accountId, days = 30) => {
22
+ if (!service.orderConn || !service.loginInfo) {
23
+ return { success: true, trades: service.completedTrades || [] };
24
+ }
25
+
26
+ return new Promise((resolve) => {
27
+ const historyOrders = [];
28
+ let resolved = false;
29
+
30
+ // Timeout after 5 seconds
31
+ const timeout = setTimeout(() => {
32
+ if (!resolved) {
33
+ resolved = true;
34
+ cleanup();
35
+ // Combine API history with session trades
36
+ const allTrades = [...processHistoryToTrades(historyOrders, service), ...service.completedTrades];
37
+ resolve({ success: true, trades: allTrades });
38
+ }
39
+ }, 5000);
40
+
41
+ // Listen for order history snapshots
42
+ const onOrderNotification = (data) => {
43
+ try {
44
+ if (data.isSnapshot || data.status === 'complete' || data.status === 'Complete') {
45
+ const order = {
46
+ orderId: data.orderId || data.orderTag,
47
+ accountId: data.accountId,
48
+ symbol: data.symbol,
49
+ exchange: data.exchange,
50
+ side: data.transactionType === 1 ? 0 : 1,
51
+ quantity: data.quantity || data.totalFillQuantity || 0,
52
+ fillPrice: data.avgFillPrice || data.lastFillPrice || 0,
53
+ timestamp: data.ssboe ? data.ssboe * 1000 : Date.now(),
54
+ status: data.status,
55
+ };
56
+
57
+ if (order.quantity > 0 && order.fillPrice > 0) {
58
+ historyOrders.push(order);
59
+ }
60
+ }
61
+ } catch (e) {
62
+ // Ignore parse errors
63
+ }
64
+ };
65
+
66
+ const onHistoryComplete = () => {
67
+ if (!resolved) {
68
+ resolved = true;
69
+ cleanup();
70
+ const allTrades = [...processHistoryToTrades(historyOrders, service), ...service.completedTrades];
71
+ resolve({ success: true, trades: allTrades });
72
+ }
73
+ };
74
+
75
+ const cleanup = () => {
76
+ clearTimeout(timeout);
77
+ service.removeListener('orderNotification', onOrderNotification);
78
+ service.removeListener('orderHistoryComplete', onHistoryComplete);
79
+ };
80
+
81
+ service.on('orderNotification', onOrderNotification);
82
+ service.on('orderHistoryComplete', onHistoryComplete);
83
+
84
+ // Request order history for each account
85
+ try {
86
+ const accounts = accountId
87
+ ? service.accounts.filter(a => a.accountId === accountId)
88
+ : service.accounts;
89
+
90
+ for (const acc of accounts) {
91
+ service.orderConn.send('RequestShowOrderHistory', {
92
+ templateId: 324,
93
+ userMsg: ['HQX-HISTORY'],
94
+ fcmId: acc.fcmId || service.loginInfo.fcmId,
95
+ ibId: acc.ibId || service.loginInfo.ibId,
96
+ accountId: acc.accountId,
97
+ });
98
+ }
99
+ } catch (e) {
100
+ if (!resolved) {
101
+ resolved = true;
102
+ cleanup();
103
+ resolve({ success: false, error: e.message, trades: service.completedTrades || [] });
104
+ }
105
+ }
106
+ });
107
+ };
108
+
109
+ /**
110
+ * Process historical orders into trades with P&L
111
+ * Matches entries and exits to calculate P&L
112
+ * @param {Array} orders - Historical orders
113
+ * @param {Object} service - RithmicService instance (for _getTickMultiplier)
114
+ * @returns {Array} Processed trades
115
+ */
116
+ const processHistoryToTrades = (orders, service) => {
117
+ const trades = [];
118
+ const openPositions = new Map();
119
+
120
+ // Sort by timestamp (oldest first)
121
+ const sorted = [...orders].sort((a, b) => a.timestamp - b.timestamp);
122
+
123
+ for (const order of sorted) {
124
+ const key = `${order.accountId}:${order.symbol}`;
125
+ const open = openPositions.get(key);
126
+
127
+ if (open && open.side !== order.side) {
128
+ // Closing trade - calculate P&L
129
+ const closeQty = Math.min(order.quantity, open.quantity);
130
+ let pnl;
131
+
132
+ if (open.side === 0) {
133
+ pnl = (order.fillPrice - open.price) * closeQty;
134
+ } else {
135
+ pnl = (open.price - order.fillPrice) * closeQty;
136
+ }
137
+
138
+ const tickMultiplier = getTickMultiplier(order.symbol);
139
+ pnl = pnl * tickMultiplier;
140
+
141
+ trades.push({
142
+ id: order.orderId,
143
+ accountId: order.accountId,
144
+ symbol: order.symbol,
145
+ exchange: order.exchange,
146
+ side: open.side,
147
+ size: closeQty,
148
+ entryPrice: open.price,
149
+ exitPrice: order.fillPrice,
150
+ price: order.fillPrice,
151
+ timestamp: order.timestamp,
152
+ creationTimestamp: new Date(order.timestamp).toISOString(),
153
+ status: 'CLOSED',
154
+ profitAndLoss: pnl,
155
+ pnl: pnl,
156
+ fees: 0,
157
+ });
158
+
159
+ const remaining = open.quantity - closeQty;
160
+ if (remaining <= 0) {
161
+ openPositions.delete(key);
162
+ } else {
163
+ open.quantity = remaining;
164
+ }
165
+ } else if (open && open.side === order.side) {
166
+ // Adding to position
167
+ const totalQty = open.quantity + order.quantity;
168
+ open.price = ((open.price * open.quantity) + (order.fillPrice * order.quantity)) / totalQty;
169
+ open.quantity = totalQty;
170
+ } else {
171
+ // Opening new position
172
+ openPositions.set(key, {
173
+ side: order.side,
174
+ quantity: order.quantity,
175
+ price: order.fillPrice,
176
+ timestamp: order.timestamp,
177
+ });
178
+ }
179
+ }
180
+
181
+ return trades;
182
+ };
183
+
184
+ /**
185
+ * Setup order fill listener for real-time P&L tracking
186
+ * @param {Object} service - RithmicService instance
187
+ */
188
+ const setupOrderFillListener = (service) => {
189
+ service._openEntries = new Map();
190
+
191
+ service.on('orderFilled', (fillInfo) => {
192
+ const key = `${fillInfo.accountId}:${fillInfo.symbol}`;
193
+ const side = fillInfo.transactionType === 1 ? 0 : 1;
194
+ const qty = fillInfo.fillQuantity || fillInfo.totalFillQuantity || 0;
195
+ const price = fillInfo.avgFillPrice || fillInfo.lastFillPrice || 0;
196
+
197
+ const openEntry = service._openEntries.get(key);
198
+ let pnl = null;
199
+
200
+ if (openEntry && openEntry.side !== side) {
201
+ // Closing trade
202
+ const closeQty = Math.min(qty, openEntry.qty);
203
+
204
+ if (openEntry.side === 0) {
205
+ pnl = (price - openEntry.price) * closeQty;
206
+ } else {
207
+ pnl = (openEntry.price - price) * closeQty;
208
+ }
209
+
210
+ const remainingQty = openEntry.qty - closeQty;
211
+ if (remainingQty <= 0) {
212
+ service._openEntries.delete(key);
213
+ } else {
214
+ openEntry.qty = remainingQty;
215
+ }
216
+
217
+ service.completedTrades.push({
218
+ id: fillInfo.orderId || fillInfo.orderTag,
219
+ orderTag: fillInfo.orderTag,
220
+ accountId: fillInfo.accountId,
221
+ symbol: fillInfo.symbol,
222
+ exchange: fillInfo.exchange,
223
+ side: openEntry.side,
224
+ size: closeQty,
225
+ entryPrice: openEntry.price,
226
+ exitPrice: price,
227
+ price: price,
228
+ timestamp: fillInfo.localTimestamp || Date.now(),
229
+ creationTimestamp: new Date().toISOString(),
230
+ status: 'CLOSED',
231
+ profitAndLoss: pnl,
232
+ pnl: pnl,
233
+ fees: 0,
234
+ });
235
+ log.debug('Trade closed', { symbol: fillInfo.symbol, pnl, trades: service.completedTrades.length });
236
+ } else {
237
+ // Opening or adding to position
238
+ if (openEntry && openEntry.side === side) {
239
+ const totalQty = openEntry.qty + qty;
240
+ openEntry.price = ((openEntry.price * openEntry.qty) + (price * qty)) / totalQty;
241
+ openEntry.qty = totalQty;
242
+ } else {
243
+ service._openEntries.set(key, { side, qty, price, timestamp: Date.now() });
244
+ }
245
+ log.debug('Position opened/added', { symbol: fillInfo.symbol, side, qty, price });
246
+ }
247
+ });
248
+ };
249
+
250
+ module.exports = {
251
+ getTradeHistory,
252
+ processHistoryToTrades,
253
+ setupOrderFillListener,
254
+ };
@@ -0,0 +1,147 @@
1
+ /**
2
+ * HFT Signal Calculation
3
+ * @module services/strategy/hft-signal-calc
4
+ *
5
+ * Signal generation and building logic for HFT strategy
6
+ */
7
+
8
+ /**
9
+ * Calculate trading signal from all models
10
+ * @param {Object} state - Strategy state
11
+ * @param {Object} config - Strategy config
12
+ * @returns {Object} {direction, confidence, scores}
13
+ */
14
+ function calculateSignal(state, config) {
15
+ let direction = 'none';
16
+ let confidence = 0;
17
+
18
+ const { ofiValue, zscore, momentum, buyVolume, sellVolume, cumulativeDelta } = state;
19
+ const { ofiThreshold, zscoreThreshold, momentumThreshold, minConfidence } = config;
20
+
21
+ const scores = {
22
+ ofi: 0,
23
+ zscore: 0,
24
+ momentum: 0,
25
+ delta: 0,
26
+ composite: 0,
27
+ };
28
+
29
+ // === MODEL 1: OFI ===
30
+ const absOfi = Math.abs(ofiValue);
31
+ if (absOfi > ofiThreshold) {
32
+ scores.ofi = Math.min(1.0, absOfi / 0.6);
33
+ }
34
+
35
+ // === MODEL 2: Z-Score Mean Reversion ===
36
+ const absZ = Math.abs(zscore);
37
+ if (absZ > zscoreThreshold) {
38
+ scores.zscore = Math.min(1.0, absZ / 3.0);
39
+ }
40
+
41
+ // === MODEL 3: Momentum ===
42
+ const absMom = Math.abs(momentum);
43
+ if (absMom > momentumThreshold) {
44
+ scores.momentum = Math.min(1.0, absMom / 3.0);
45
+ }
46
+
47
+ // === MODEL 4: Delta ===
48
+ const totalVol = buyVolume + sellVolume;
49
+ if (totalVol > 0) {
50
+ const deltaRatio = cumulativeDelta / totalVol;
51
+ scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
52
+ }
53
+
54
+ // === COMPOSITE SCORE ===
55
+ scores.composite =
56
+ scores.ofi * 0.35 + // OFI: 35%
57
+ scores.zscore * 0.25 + // Z-Score: 25%
58
+ scores.momentum * 0.20 + // Momentum: 20%
59
+ scores.delta * 0.20; // Delta: 20%
60
+
61
+ confidence = scores.composite;
62
+
63
+ // === DETERMINE DIRECTION ===
64
+ if (scores.composite >= minConfidence) {
65
+ // Primary: Mean reversion
66
+ if (absZ > zscoreThreshold) {
67
+ direction = zscore > 0 ? 'short' : 'long';
68
+
69
+ // Confirm with OFI
70
+ const ofiConfirms =
71
+ (direction === 'long' && ofiValue > 0) ||
72
+ (direction === 'short' && ofiValue < 0);
73
+
74
+ if (ofiConfirms) {
75
+ confidence += 0.1;
76
+ } else if (Math.abs(ofiValue) > 0.2) {
77
+ confidence -= 0.15;
78
+ }
79
+ }
80
+ // Fallback: Momentum breakout
81
+ else if (absMom > momentumThreshold * 2 && absOfi > ofiThreshold) {
82
+ direction = momentum > 0 ? 'long' : 'short';
83
+ if ((direction === 'long' && ofiValue < 0) ||
84
+ (direction === 'short' && ofiValue > 0)) {
85
+ direction = 'none';
86
+ }
87
+ }
88
+ }
89
+
90
+ confidence = Math.min(1.0, Math.max(0, confidence));
91
+
92
+ return { direction, confidence, scores };
93
+ }
94
+
95
+ /**
96
+ * Build signal object
97
+ * @param {string} direction
98
+ * @param {number} confidence
99
+ * @param {Object} scores
100
+ * @param {Object} tick
101
+ * @param {Object} state
102
+ * @param {Object} config
103
+ * @returns {Object} Signal object
104
+ */
105
+ function buildSignal(direction, confidence, scores, tick, state, config) {
106
+ const entry = tick.price;
107
+ const isLong = direction === 'long';
108
+
109
+ const { std, ofiValue, zscore, momentum, cumulativeDelta, tickCount, signalCount, contractId, tickSize } = state;
110
+ const { baseStopTicks, baseTargetTicks } = config;
111
+
112
+ // Adaptive stops based on volatility
113
+ const volMult = Math.max(0.5, Math.min(2.0, std / tickSize / 4));
114
+ const stopTicks = Math.round(baseStopTicks * volMult);
115
+ const targetTicks = Math.round(baseTargetTicks * volMult);
116
+
117
+ const stopLoss = isLong
118
+ ? entry - stopTicks * tickSize
119
+ : entry + stopTicks * tickSize;
120
+
121
+ const takeProfit = isLong
122
+ ? entry + targetTicks * tickSize
123
+ : entry - targetTicks * tickSize;
124
+
125
+ return {
126
+ id: `hft-${Date.now()}-${signalCount}`,
127
+ timestamp: Date.now(),
128
+ contractId,
129
+ direction,
130
+ side: isLong ? 0 : 1,
131
+ entry,
132
+ stopLoss,
133
+ takeProfit,
134
+ confidence,
135
+ scores,
136
+ ofi: ofiValue,
137
+ zscore,
138
+ momentum,
139
+ delta: cumulativeDelta,
140
+ tickCount,
141
+ };
142
+ }
143
+
144
+ module.exports = {
145
+ calculateSignal,
146
+ buildSignal,
147
+ };
@@ -20,6 +20,7 @@
20
20
 
21
21
  const EventEmitter = require('events');
22
22
  const { logger } = require('../../utils/logger');
23
+ const { calculateSignal, buildSignal } = require('./hft-signal-calc');
23
24
 
24
25
  const log = logger.scope('HFT');
25
26
 
@@ -306,23 +307,49 @@ class HFTTickStrategy extends EventEmitter {
306
307
  * Check for trading signal
307
308
  */
308
309
  _checkSignal(tick) {
309
- // Need minimum data
310
310
  if (this.tickCount < 50) return;
311
311
 
312
- // Cooldown check
313
312
  const now = Date.now();
314
313
  if (now - this.lastSignalTime < this.cooldownMs) return;
315
314
 
316
- // Calculate composite score
317
- const { direction, confidence, scores } = this._calculateSignal();
315
+ const state = {
316
+ ofiValue: this.ofiValue,
317
+ zscore: this.zscore,
318
+ momentum: this.momentum,
319
+ buyVolume: this.buyVolume,
320
+ sellVolume: this.sellVolume,
321
+ cumulativeDelta: this.cumulativeDelta,
322
+ };
323
+
324
+ const config = {
325
+ ofiThreshold: this.ofiThreshold,
326
+ zscoreThreshold: this.zscoreThreshold,
327
+ momentumThreshold: this.momentumThreshold,
328
+ minConfidence: this.minConfidence,
329
+ };
330
+
331
+ const { direction, confidence, scores } = calculateSignal(state, config);
318
332
 
319
333
  if (direction === 'none' || confidence < this.minConfidence) return;
320
334
 
321
- // Generate signal
322
335
  this.signalCount++;
323
336
  this.lastSignalTime = now;
324
337
 
325
- const signal = this._buildSignal(direction, confidence, scores, tick);
338
+ const signalState = {
339
+ ...state,
340
+ std: this.std,
341
+ tickCount: this.tickCount,
342
+ signalCount: this.signalCount,
343
+ contractId: this.contractId,
344
+ tickSize: this.tickSize,
345
+ };
346
+
347
+ const signalConfig = {
348
+ baseStopTicks: this.baseStopTicks,
349
+ baseTargetTicks: this.baseTargetTicks,
350
+ };
351
+
352
+ const signal = buildSignal(direction, confidence, scores, tick, signalState, signalConfig);
326
353
 
327
354
  log.info(`SIGNAL #${this.signalCount}: ${direction.toUpperCase()} @ ${tick.price} | ` +
328
355
  `OFI=${this.ofiValue.toFixed(2)} Z=${this.zscore.toFixed(2)} Mom=${this.momentum.toFixed(1)} | ` +
@@ -331,133 +358,6 @@ class HFTTickStrategy extends EventEmitter {
331
358
  this.emit('signal', signal);
332
359
  }
333
360
 
334
- /**
335
- * Calculate trading signal from all models
336
- */
337
- _calculateSignal() {
338
- let direction = 'none';
339
- let confidence = 0;
340
-
341
- const scores = {
342
- ofi: 0,
343
- zscore: 0,
344
- momentum: 0,
345
- delta: 0,
346
- composite: 0,
347
- };
348
-
349
- // === MODEL 1: OFI ===
350
- const absOfi = Math.abs(this.ofiValue);
351
- if (absOfi > this.ofiThreshold) {
352
- scores.ofi = Math.min(1.0, absOfi / 0.6);
353
- }
354
-
355
- // === MODEL 2: Z-Score Mean Reversion ===
356
- const absZ = Math.abs(this.zscore);
357
- if (absZ > this.zscoreThreshold) {
358
- scores.zscore = Math.min(1.0, absZ / 3.0);
359
- }
360
-
361
- // === MODEL 3: Momentum ===
362
- const absMom = Math.abs(this.momentum);
363
- if (absMom > this.momentumThreshold) {
364
- scores.momentum = Math.min(1.0, absMom / 3.0);
365
- }
366
-
367
- // === MODEL 4: Delta ===
368
- const totalVol = this.buyVolume + this.sellVolume;
369
- if (totalVol > 0) {
370
- const deltaRatio = this.cumulativeDelta / totalVol;
371
- scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
372
- }
373
-
374
- // === COMPOSITE SCORE ===
375
- scores.composite =
376
- scores.ofi * 0.35 + // OFI: 35%
377
- scores.zscore * 0.25 + // Z-Score: 25%
378
- scores.momentum * 0.20 + // Momentum: 20%
379
- scores.delta * 0.20; // Delta: 20%
380
-
381
- confidence = scores.composite;
382
-
383
- // === DETERMINE DIRECTION ===
384
- // Mean reversion: go opposite to z-score
385
- // Momentum: confirm with OFI and delta
386
-
387
- if (scores.composite >= this.minConfidence) {
388
- // Primary: Mean reversion
389
- if (absZ > this.zscoreThreshold) {
390
- direction = this.zscore > 0 ? 'short' : 'long';
391
-
392
- // Confirm with OFI
393
- const ofiConfirms =
394
- (direction === 'long' && this.ofiValue > 0) ||
395
- (direction === 'short' && this.ofiValue < 0);
396
-
397
- if (ofiConfirms) {
398
- confidence += 0.1;
399
- } else if (Math.abs(this.ofiValue) > 0.2) {
400
- // OFI contradicts - reduce confidence
401
- confidence -= 0.15;
402
- }
403
- }
404
- // Fallback: Momentum breakout
405
- else if (absMom > this.momentumThreshold * 2 && absOfi > this.ofiThreshold) {
406
- direction = this.momentum > 0 ? 'long' : 'short';
407
- // Must be confirmed by OFI
408
- if ((direction === 'long' && this.ofiValue < 0) ||
409
- (direction === 'short' && this.ofiValue > 0)) {
410
- direction = 'none';
411
- }
412
- }
413
- }
414
-
415
- confidence = Math.min(1.0, Math.max(0, confidence));
416
-
417
- return { direction, confidence, scores };
418
- }
419
-
420
- /**
421
- * Build signal object
422
- */
423
- _buildSignal(direction, confidence, scores, tick) {
424
- const entry = tick.price;
425
- const isLong = direction === 'long';
426
-
427
- // Adaptive stops based on volatility (std dev)
428
- const volMult = Math.max(0.5, Math.min(2.0, this.std / this.tickSize / 4));
429
- const stopTicks = Math.round(this.baseStopTicks * volMult);
430
- const targetTicks = Math.round(this.baseTargetTicks * volMult);
431
-
432
- const stopLoss = isLong
433
- ? entry - stopTicks * this.tickSize
434
- : entry + stopTicks * this.tickSize;
435
-
436
- const takeProfit = isLong
437
- ? entry + targetTicks * this.tickSize
438
- : entry - targetTicks * this.tickSize;
439
-
440
- return {
441
- id: `hft-${Date.now()}-${this.signalCount}`,
442
- timestamp: Date.now(),
443
- contractId: this.contractId,
444
- direction,
445
- side: isLong ? 0 : 1,
446
- entry,
447
- stopLoss,
448
- takeProfit,
449
- confidence,
450
- scores,
451
- // Model values for logging
452
- ofi: this.ofiValue,
453
- zscore: this.zscore,
454
- momentum: this.momentum,
455
- delta: this.cumulativeDelta,
456
- // Tick count
457
- tickCount: this.tickCount,
458
- };
459
- }
460
-
461
361
  /**
462
362
  * Get current model values for monitoring
463
363
  */