hedgequantx 2.6.163 → 2.7.1

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 (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +8 -5
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -1,438 +0,0 @@
1
- /**
2
- * @fileoverview Position Manager for Fast Scalping
3
- * @module services/position-manager
4
- *
5
- * Manages position lifecycle with:
6
- * - 10 second minimum hold (prop firm rule - NON-NEGOTIABLE)
7
- * - Intelligent exit based on momentum (OFI 50%, Kalman 25%, Z-Score 25%)
8
- * - Trailing stop after profit threshold
9
- * - 60 second failsafe exit (NON-NEGOTIABLE)
10
- * - VPIN protection filter
11
- *
12
- * Data source: Rithmic ORDER_PLANT (fills), PNL_PLANT (positions), TICKER_PLANT (prices)
13
- */
14
-
15
- const EventEmitter = require('events');
16
- const { FAST_SCALPING } = require('../config/settings');
17
- const { logger } = require('../utils/logger');
18
- const { MOMENTUM, WEIGHTS } = require('./position-constants');
19
- const { checkExitConditions, checkBreakevenActivation, calculatePnlTicks } = require('./position-exit-logic');
20
-
21
- const log = logger.scope('PositionMgr');
22
-
23
- /**
24
- * Position Manager Service
25
- * Handles position lifecycle for fast scalping strategy
26
- */
27
- class PositionManager extends EventEmitter {
28
- constructor(rithmicService, strategy = null) {
29
- super();
30
- this.rithmic = rithmicService;
31
- this.strategy = strategy;
32
-
33
- this.positions = new Map();
34
- this.latestPrices = new Map();
35
- this.contractInfo = new Map();
36
-
37
- this._monitorInterval = null;
38
- this._isRunning = false;
39
-
40
- this._onOrderFilled = this._onOrderFilled.bind(this);
41
- this._onPriceUpdate = this._onPriceUpdate.bind(this);
42
- this._onPositionUpdate = this._onPositionUpdate.bind(this);
43
- }
44
-
45
- setStrategy(strategy) {
46
- this.strategy = strategy;
47
- log.debug('Strategy reference set');
48
- }
49
-
50
- setContractInfo(symbol, info) {
51
- this.contractInfo.set(symbol, {
52
- tickSize: info.tickSize,
53
- tickValue: info.tickValue,
54
- contractId: info.contractId,
55
- });
56
- log.debug('Contract info set', { symbol, tickSize: info.tickSize, tickValue: info.tickValue });
57
- }
58
-
59
- start() {
60
- if (this._isRunning) return;
61
-
62
- log.info('Starting position manager', {
63
- minHoldMs: FAST_SCALPING.MIN_HOLD_MS,
64
- maxHoldMs: FAST_SCALPING.MAX_HOLD_MS,
65
- targetTicks: FAST_SCALPING.TARGET_TICKS,
66
- stopTicks: FAST_SCALPING.STOP_TICKS,
67
- momentumWeights: WEIGHTS,
68
- });
69
-
70
- this.rithmic.on('orderFilled', this._onOrderFilled);
71
- this.rithmic.on('priceUpdate', this._onPriceUpdate);
72
- this.rithmic.on('positionUpdate', this._onPositionUpdate);
73
-
74
- this._monitorInterval = setInterval(() => {
75
- this._monitorPositions();
76
- }, FAST_SCALPING.MONITOR_INTERVAL_MS);
77
-
78
- this._isRunning = true;
79
- log.debug('Position manager started');
80
- }
81
-
82
- stop() {
83
- if (!this._isRunning) return;
84
-
85
- log.info('Stopping position manager');
86
-
87
- this.rithmic.off('orderFilled', this._onOrderFilled);
88
- this.rithmic.off('priceUpdate', this._onPriceUpdate);
89
- this.rithmic.off('positionUpdate', this._onPositionUpdate);
90
-
91
- if (this._monitorInterval) {
92
- clearInterval(this._monitorInterval);
93
- this._monitorInterval = null;
94
- }
95
-
96
- this._isRunning = false;
97
- log.debug('Position manager stopped');
98
- }
99
-
100
- registerEntry(entryResult, orderData, contractInfo = null) {
101
- if (!entryResult.success) {
102
- log.warn('Cannot register failed entry', { error: entryResult.error });
103
- return null;
104
- }
105
-
106
- const { orderTag, entryTime, latencyMs } = entryResult;
107
-
108
- const info = contractInfo || this.contractInfo.get(orderData.symbol) || {
109
- tickSize: null,
110
- tickValue: null,
111
- contractId: orderData.contractId || orderData.symbol,
112
- };
113
-
114
- const position = {
115
- orderTag,
116
- accountId: orderData.accountId,
117
- symbol: orderData.symbol,
118
- exchange: orderData.exchange || 'CME',
119
- side: orderData.side,
120
- size: orderData.size,
121
- entryPrice: null,
122
- entryTime,
123
- fillTime: null,
124
- highWaterMark: null,
125
- lowWaterMark: null,
126
- status: 'pending',
127
- holdComplete: false,
128
- breakevenActive: false,
129
- breakevenPrice: null,
130
- exitReason: null,
131
- latencyMs,
132
- tickSize: info.tickSize,
133
- tickValue: info.tickValue,
134
- contractId: info.contractId,
135
- };
136
-
137
- this.positions.set(orderTag, position);
138
-
139
- log.debug('Registered entry', {
140
- orderTag,
141
- symbol: orderData.symbol,
142
- side: orderData.side === 0 ? 'LONG' : 'SHORT',
143
- size: orderData.size,
144
- latencyMs,
145
- tickSize: info.tickSize,
146
- });
147
-
148
- return orderTag;
149
- }
150
-
151
- _onOrderFilled(fillInfo) {
152
- const { orderTag, avgFillPrice, symbol } = fillInfo;
153
-
154
- if (!orderTag) return;
155
-
156
- const position = this.positions.get(orderTag);
157
- if (!position) {
158
- // Check if this is an exit fill
159
- for (const [tag, pos] of this.positions) {
160
- if (pos.status === 'exiting' && pos.symbol === symbol) {
161
- this._handleExitFill(tag, pos, avgFillPrice);
162
- return;
163
- }
164
- }
165
- log.debug('Fill for untracked order', { orderTag });
166
- return;
167
- }
168
-
169
- if (position.status === 'pending') {
170
- this._handleEntryFill(orderTag, position, avgFillPrice);
171
- } else if (position.status === 'exiting') {
172
- this._handleExitFill(orderTag, position, avgFillPrice);
173
- }
174
- }
175
-
176
- _handleEntryFill(orderTag, position, avgFillPrice) {
177
- position.entryPrice = avgFillPrice;
178
- position.fillTime = Date.now();
179
- position.highWaterMark = avgFillPrice;
180
- position.lowWaterMark = avgFillPrice;
181
- position.status = 'holding';
182
-
183
- const fillLatency = position.fillTime - position.entryTime;
184
-
185
- log.info('ENTRY FILLED', {
186
- orderTag,
187
- symbol: position.symbol,
188
- side: position.side === 0 ? 'LONG' : 'SHORT',
189
- size: position.size,
190
- price: avgFillPrice,
191
- entryLatencyMs: position.latencyMs,
192
- fillLatencyMs: fillLatency,
193
- });
194
-
195
- this.emit('entryFilled', { orderTag, position, fillLatencyMs: fillLatency });
196
-
197
- setTimeout(() => this._onHoldComplete(orderTag), FAST_SCALPING.MIN_HOLD_MS);
198
- setTimeout(() => this._failsafeExit(orderTag), FAST_SCALPING.MAX_HOLD_MS);
199
- }
200
-
201
- _handleExitFill(orderTag, position, avgFillPrice) {
202
- position.status = 'closed';
203
- const holdDuration = Date.now() - position.fillTime;
204
- const pnlTicks = this._calculatePnlTicks(position, avgFillPrice);
205
-
206
- log.info('EXIT FILLED', {
207
- orderTag,
208
- symbol: position.symbol,
209
- exitPrice: avgFillPrice,
210
- entryPrice: position.entryPrice,
211
- pnlTicks,
212
- holdDurationMs: holdDuration,
213
- reason: position.exitReason,
214
- });
215
-
216
- this.emit('exitFilled', {
217
- orderTag,
218
- position,
219
- exitPrice: avgFillPrice,
220
- pnlTicks,
221
- holdDurationMs: holdDuration,
222
- });
223
-
224
- this.positions.delete(orderTag);
225
- }
226
-
227
- _failsafeExit(orderTag) {
228
- const position = this.positions.get(orderTag);
229
- if (!position) return;
230
-
231
- if (position.status === 'holding' || position.status === 'active') {
232
- const currentPrice = this.latestPrices.get(position.symbol);
233
- const pnlTicks = currentPrice ? this._calculatePnlTicks(position, currentPrice) : 0;
234
-
235
- log.warn('FAILSAFE EXIT - 60s max hold exceeded', { orderTag, symbol: position.symbol, pnlTicks });
236
-
237
- this._executeExit(orderTag, {
238
- type: 'failsafe',
239
- reason: '60s max hold exceeded (NON-NEGOTIABLE)',
240
- pnlTicks,
241
- });
242
- }
243
- }
244
-
245
- _onPriceUpdate(priceData) {
246
- const { symbol, price } = priceData;
247
-
248
- this.latestPrices.set(symbol, price);
249
-
250
- for (const [, position] of this.positions) {
251
- if (position.symbol === symbol && (position.status === 'holding' || position.status === 'active') && position.entryPrice) {
252
- if (position.side === 0) {
253
- position.highWaterMark = Math.max(position.highWaterMark, price);
254
- } else {
255
- position.lowWaterMark = Math.min(position.lowWaterMark, price);
256
- }
257
- }
258
- }
259
- }
260
-
261
- _onPositionUpdate(posData) {
262
- log.debug('Position update from PNL_PLANT', { symbol: posData?.symbol, qty: posData?.quantity });
263
- }
264
-
265
- _onHoldComplete(orderTag) {
266
- const position = this.positions.get(orderTag);
267
- if (!position) return;
268
-
269
- if (position.status === 'holding') {
270
- position.holdComplete = true;
271
- position.status = 'active';
272
-
273
- log.info('Hold complete - now monitoring for exit', {
274
- orderTag,
275
- symbol: position.symbol,
276
- entryPrice: position.entryPrice,
277
- });
278
-
279
- this.emit('holdComplete', { orderTag, position });
280
- }
281
- }
282
-
283
- _monitorPositions() {
284
- const now = Date.now();
285
-
286
- for (const [orderTag, position] of this.positions) {
287
- if (position.status !== 'active') continue;
288
-
289
- const currentPrice = this.latestPrices.get(position.symbol);
290
- if (!currentPrice) continue;
291
-
292
- const holdDuration = now - position.fillTime;
293
- const pnlTicks = this._calculatePnlTicks(position, currentPrice);
294
-
295
- // Check for breakeven activation
296
- const beActivation = checkBreakevenActivation(position, pnlTicks, (p) => this._getTickSize(p));
297
- if (beActivation) {
298
- position.breakevenPrice = beActivation.breakevenPrice;
299
- position.breakevenActive = true;
300
-
301
- log.info('BREAKEVEN ACTIVATED', {
302
- symbol: position.symbol,
303
- entryPrice: position.entryPrice,
304
- bePrice: position.breakevenPrice,
305
- pnlTicks,
306
- });
307
-
308
- this.emit('breakevenActivated', {
309
- orderTag: position.orderTag,
310
- position,
311
- breakevenPrice: position.breakevenPrice,
312
- pnlTicks,
313
- });
314
- }
315
-
316
- // Check exit conditions
317
- const exitReason = checkExitConditions({
318
- position,
319
- currentPrice,
320
- pnlTicks,
321
- holdDuration,
322
- strategy: this.strategy,
323
- getTickSize: (p) => this._getTickSize(p),
324
- latestPrices: this.latestPrices,
325
- });
326
-
327
- if (exitReason) {
328
- this._executeExit(orderTag, exitReason);
329
- }
330
- }
331
- }
332
-
333
- _executeExit(orderTag, exitReason) {
334
- const position = this.positions.get(orderTag);
335
- if (!position || position.status === 'exiting' || position.status === 'closed') return;
336
-
337
- position.status = 'exiting';
338
- position.exitReason = exitReason;
339
-
340
- log.info('Executing EXIT', {
341
- orderTag,
342
- symbol: position.symbol,
343
- reason: exitReason.reason,
344
- pnlTicks: exitReason.pnlTicks,
345
- momentum: exitReason.momentum,
346
- });
347
-
348
- const exitSide = position.side === 0 ? 1 : 0;
349
-
350
- const exitResult = this.rithmic.fastExit({
351
- accountId: position.accountId,
352
- symbol: position.symbol,
353
- exchange: position.exchange,
354
- size: position.size,
355
- side: exitSide,
356
- });
357
-
358
- if (exitResult.success) {
359
- log.debug('Exit order fired', {
360
- orderTag: position.orderTag,
361
- exitOrderTag: exitResult.orderTag,
362
- latencyMs: exitResult.latencyMs,
363
- });
364
-
365
- this.emit('exitOrderFired', {
366
- orderTag,
367
- exitOrderTag: exitResult.orderTag,
368
- exitReason,
369
- latencyMs: exitResult.latencyMs,
370
- });
371
- } else {
372
- log.error('Exit order FAILED', { orderTag, error: exitResult.error });
373
- position.status = 'active';
374
- position.exitReason = null;
375
- }
376
- }
377
-
378
- _getTickSize(position) {
379
- if (position.tickSize !== null && position.tickSize !== undefined) {
380
- return position.tickSize;
381
- }
382
-
383
- const info = this.contractInfo.get(position.symbol);
384
- if (info && info.tickSize) {
385
- return info.tickSize;
386
- }
387
-
388
- log.warn('No tick size available for symbol', { symbol: position.symbol });
389
- return null;
390
- }
391
-
392
- _getTickValue(position) {
393
- if (position.tickValue !== null && position.tickValue !== undefined) {
394
- return position.tickValue;
395
- }
396
-
397
- const info = this.contractInfo.get(position.symbol);
398
- if (info && info.tickValue) {
399
- return info.tickValue;
400
- }
401
-
402
- log.warn('No tick value available for symbol', { symbol: position.symbol });
403
- return null;
404
- }
405
-
406
- _calculatePnlTicks(position, currentPrice) {
407
- return calculatePnlTicks(position, currentPrice, (p) => this._getTickSize(p));
408
- }
409
-
410
- getActivePositions() {
411
- return Array.from(this.positions.values()).filter(
412
- p => p.status === 'holding' || p.status === 'active'
413
- );
414
- }
415
-
416
- getPosition(orderTag) {
417
- return this.positions.get(orderTag) || null;
418
- }
419
-
420
- canEnter(symbol) {
421
- for (const position of this.positions.values()) {
422
- if (position.symbol === symbol && position.status !== 'closed') {
423
- return false;
424
- }
425
- }
426
- return true;
427
- }
428
-
429
- getMomentumThresholds() {
430
- return { ...MOMENTUM };
431
- }
432
-
433
- getMomentumWeights() {
434
- return { ...WEIGHTS };
435
- }
436
- }
437
-
438
- module.exports = { PositionManager };
@@ -1,206 +0,0 @@
1
- /**
2
- * @fileoverview Position Momentum Calculator
3
- * Calculates momentum using strategy's math models (OFI, Kalman, Z-Score)
4
- *
5
- * Data sources:
6
- * - OFI: Strategy's computeOrderFlowImbalance()
7
- * - Kalman: Strategy's kalmanStates
8
- * - Z-Score: Strategy's computeZScore()
9
- * - VPIN: Strategy's computeVPIN()
10
- */
11
-
12
- const { logger } = require('../utils/logger');
13
- const { WEIGHTS } = require('./position-constants');
14
-
15
- const log = logger.scope('PositionMomentum');
16
-
17
- /**
18
- * Get OFI (Order Flow Imbalance) from strategy
19
- * @param {Object} strategy - Strategy instance
20
- * @param {string} contractId - Contract identifier
21
- * @returns {number|null} OFI value [-1, 1] or null if unavailable
22
- */
23
- const getOFI = (strategy, contractId) => {
24
- if (!strategy) return null;
25
-
26
- // Try strategy's computeOrderFlowImbalance (direct calculation from bars)
27
- if (typeof strategy.computeOrderFlowImbalance === 'function') {
28
- const bars = strategy.getBarHistory?.(contractId);
29
- if (bars && bars.length >= 20) {
30
- try {
31
- return strategy.computeOrderFlowImbalance(bars);
32
- } catch (error) {
33
- log.debug('OFI calculation failed', { error: error.message });
34
- }
35
- }
36
- }
37
-
38
- // Try getModelValues (pre-calculated values)
39
- const modelValues = strategy.getModelValues?.(contractId);
40
- if (modelValues && modelValues.rawOfi !== undefined) {
41
- return modelValues.rawOfi;
42
- }
43
-
44
- return null;
45
- };
46
-
47
- /**
48
- * Get Kalman velocity from strategy's Kalman filter
49
- * @param {Object} strategy - Strategy instance
50
- * @param {string} contractId - Contract identifier
51
- * @param {number} currentPrice - Latest price
52
- * @returns {number|null} Velocity value or null if unavailable
53
- */
54
- const getKalmanVelocity = (strategy, contractId, currentPrice) => {
55
- if (!strategy) return null;
56
-
57
- // Try to access kalmanStates from strategy
58
- if (strategy.kalmanStates) {
59
- const state = strategy.kalmanStates.get(contractId);
60
- if (state && typeof state.estimate === 'number') {
61
- if (currentPrice !== undefined && currentPrice !== null) {
62
- return currentPrice - state.estimate;
63
- }
64
- }
65
- }
66
-
67
- return null;
68
- };
69
-
70
- /**
71
- * Get Z-Score from strategy
72
- * @param {Object} strategy - Strategy instance
73
- * @param {string} contractId - Contract identifier
74
- * @returns {number|null} Z-Score value or null if unavailable
75
- */
76
- const getZScore = (strategy, contractId) => {
77
- if (!strategy) return null;
78
-
79
- // Try strategy's computeZScore (direct calculation from price buffer)
80
- if (typeof strategy.computeZScore === 'function') {
81
- const prices = strategy.priceBuffer?.get(contractId);
82
- if (prices && prices.length >= 50) {
83
- try {
84
- return strategy.computeZScore(prices);
85
- } catch (error) {
86
- log.debug('Z-Score calculation failed', { error: error.message });
87
- }
88
- }
89
- }
90
-
91
- return null;
92
- };
93
-
94
- /**
95
- * Get VPIN from strategy
96
- * @param {Object} strategy - Strategy instance
97
- * @param {string} contractId - Contract identifier
98
- * @returns {number|null} VPIN value [0, 1] or null if unavailable
99
- */
100
- const getVPIN = (strategy, contractId) => {
101
- if (!strategy) return null;
102
-
103
- // Try strategy's computeVPIN (direct calculation from volume buffer)
104
- if (typeof strategy.computeVPIN === 'function') {
105
- const volumes = strategy.volumeBuffer?.get(contractId);
106
- if (volumes && volumes.length >= 50) {
107
- try {
108
- return strategy.computeVPIN(volumes);
109
- } catch (error) {
110
- log.debug('VPIN calculation failed', { error: error.message });
111
- }
112
- }
113
- }
114
-
115
- // Try getModelValues (pre-calculated, stored as 1 - vpin for scoring)
116
- const modelValues = strategy.getModelValues?.(contractId);
117
- if (modelValues && typeof modelValues.vpin === 'number') {
118
- return 1 - modelValues.vpin;
119
- }
120
-
121
- return null;
122
- };
123
-
124
- /**
125
- * Calculate momentum score using strategy's existing math models
126
- * Weighted: OFI (50%) + Kalman Velocity (25%) + Z-Score (25%)
127
- *
128
- * @param {Object} params - Parameters
129
- * @param {Object} params.strategy - Strategy instance
130
- * @param {string} params.contractId - Contract identifier
131
- * @param {number} params.side - Position side (0=Long, 1=Short)
132
- * @param {number} params.currentPrice - Latest price
133
- * @param {number|null} params.tickSize - Tick size from API
134
- * @returns {number|null} Momentum score [-1 to 1], positive = favorable, null if insufficient data
135
- */
136
- const calculateMomentum = ({ strategy, contractId, side, currentPrice, tickSize }) => {
137
- if (!strategy) {
138
- return null;
139
- }
140
-
141
- // Get individual model values (all from API/strategy, not invented)
142
- const ofi = getOFI(strategy, contractId);
143
- const velocity = getKalmanVelocity(strategy, contractId, currentPrice);
144
- const zscore = getZScore(strategy, contractId);
145
-
146
- // Count how many models have data
147
- let availableModels = 0;
148
- let totalWeight = 0;
149
- let weightedSum = 0;
150
-
151
- // 1. OFI (50%) - Order Flow Imbalance
152
- if (ofi !== null) {
153
- // For long: positive OFI = favorable, For short: negative OFI = favorable
154
- const favorableOfi = side === 0 ? ofi : -ofi;
155
- const ofiScore = Math.min(1, Math.max(-1, favorableOfi));
156
- weightedSum += ofiScore * WEIGHTS.OFI;
157
- totalWeight += WEIGHTS.OFI;
158
- availableModels++;
159
- }
160
-
161
- // 2. Kalman Velocity (25%)
162
- if (velocity !== null && tickSize !== null) {
163
- // Normalize velocity: favorable direction = positive
164
- const favorableVelocity = side === 0 ? velocity : -velocity;
165
- // Normalize to [-1, 1]: 4 ticks of velocity = 1.0 score
166
- const normalizedVelocity = favorableVelocity / (tickSize * 4);
167
- const velocityScore = Math.min(1, Math.max(-1, normalizedVelocity));
168
- weightedSum += velocityScore * WEIGHTS.KALMAN;
169
- totalWeight += WEIGHTS.KALMAN;
170
- availableModels++;
171
- }
172
-
173
- // 3. Z-Score (25%) - Progression toward mean
174
- if (zscore !== null) {
175
- let zscoreScore;
176
- if (side === 0) {
177
- // Long: entered when Z < -threshold, improving = Z moving toward 0
178
- zscoreScore = zscore > -0.5 ? 0.5 : -0.5;
179
- } else {
180
- // Short: entered when Z > threshold, improving = Z moving toward 0
181
- zscoreScore = zscore < 0.5 ? 0.5 : -0.5;
182
- }
183
- weightedSum += zscoreScore * WEIGHTS.ZSCORE;
184
- totalWeight += WEIGHTS.ZSCORE;
185
- availableModels++;
186
- }
187
-
188
- // Need at least 1 model with data to calculate momentum
189
- if (availableModels === 0 || totalWeight === 0) {
190
- return null;
191
- }
192
-
193
- // Normalize by actual total weight (in case some models unavailable)
194
- const momentum = weightedSum / totalWeight;
195
-
196
- // Clamp to [-1, 1]
197
- return Math.min(1, Math.max(-1, momentum));
198
- };
199
-
200
- module.exports = {
201
- getOFI,
202
- getKalmanVelocity,
203
- getZScore,
204
- getVPIN,
205
- calculateMomentum,
206
- };