hedgequantx 2.6.27 → 2.6.29

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.6.27",
3
+ "version": "2.6.29",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -16,12 +16,16 @@ const { checkMarketHours } = require('../../services/projectx/market');
16
16
  const { FAST_SCALPING } = require('../../config/settings');
17
17
  const { PositionManager } = require('../../services/position-manager');
18
18
 
19
- // Strategy & Market Data (obfuscated)
19
+ // Strategy & Market Data
20
20
  const { M1 } = require('../../../dist/lib/m/s1');
21
+ const { hftStrategy } = require('../../services/strategy/hft-tick');
21
22
  const { MarketDataFeed } = require('../../../dist/lib/data');
22
23
  const { RithmicMarketDataFeed } = require('../../services/rithmic/market-data');
23
24
  const { algoLogger } = require('./logger');
24
25
 
26
+ // Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
27
+ const USE_HFT_STRATEGY = true;
28
+
25
29
  // AI Strategy Supervisor - observes, learns, and optimizes the strategy
26
30
  const aiService = require('../../services/ai');
27
31
  const StrategySupervisor = require('../../services/ai/strategy-supervisor');
@@ -263,13 +267,15 @@ const launchAlgo = async (service, account, contract, config) => {
263
267
  let lastTradeCount = 0; // Track number of trades from API
264
268
  let lastPositionQty = 0; // Track position changes
265
269
 
266
- // Initialize Strategy FIRST (M1 is singleton instance)
267
- // Strategy needs to be initialized before PositionManager so it can access math models
268
- const strategy = M1;
270
+ // Initialize Strategy
271
+ // Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
272
+ const strategy = (USE_HFT_STRATEGY && useFastPath) ? hftStrategy : M1;
273
+ const strategyName = (USE_HFT_STRATEGY && useFastPath) ? 'HFT-TICK' : 'M1';
269
274
 
270
- // Only initialize strategy if we have tick data from API
275
+ // Initialize strategy with tick specs
271
276
  if (tickSize !== null && tickValue !== null) {
272
277
  strategy.initialize(contractId, tickSize, tickValue);
278
+ algoLogger.info(ui, 'STRATEGY', `${strategyName} initialized | tick=${tickSize} value=$${tickValue}`);
273
279
  } else {
274
280
  algoLogger.warning(ui, 'WARNING', 'Tick size/value not available from API');
275
281
  }
@@ -587,15 +593,21 @@ const launchAlgo = async (service, account, contract, config) => {
587
593
 
588
594
  // Heartbeat every 30 seconds - show strategy model values
589
595
  if (Date.now() - lastHeartbeat > 30000) {
590
- const modelValues = strategy.getModelValues?.(contractId);
591
- if (modelValues) {
592
- const ofi = (modelValues.ofi || 0).toFixed(1);
593
- const delta = (modelValues.delta || 0).toFixed(1);
596
+ // Try to get model values from strategy
597
+ const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(contractId) || null;
598
+
599
+ if (modelValues && modelValues.ofi !== undefined) {
600
+ // HFT Strategy - pure tick values
601
+ const ofi = (modelValues.ofi || 0).toFixed(2);
602
+ const delta = modelValues.delta || 0;
594
603
  const zscore = (modelValues.zscore || 0).toFixed(2);
595
- const momentum = modelValues.momentum ? (modelValues.momentum > 0 ? 'BULL' : 'BEAR') : 'FLAT';
596
- algoLogger.info(ui, 'M1 STATUS', `OFI=${ofi} Delta=${delta} Z=${zscore} ${momentum} | ${tps} ticks/30s`);
604
+ const mom = (modelValues.momentum || 0).toFixed(1);
605
+ const signals = modelValues.signalCount || 0;
606
+ const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
607
+ algoLogger.info(ui, strategyName, `OFI=${ofi} Z=${zscore} Mom=${mom} Δ=${deltaStr} | ${signals} signals | ${tps} t/30s`);
597
608
  } else {
598
- algoLogger.heartbeat(ui, tps, stats.latency);
609
+ // Fallback
610
+ algoLogger.info(ui, 'SCANNING', `${tps} ticks/30s | ${tickCount} total | price=${tickData.price}`);
599
611
  }
600
612
  lastHeartbeat = Date.now();
601
613
  tps = 0;
@@ -0,0 +1,494 @@
1
+ /**
2
+ * =============================================================================
3
+ * HFT TICK-BY-TICK STRATEGY
4
+ * =============================================================================
5
+ * Pure tick-based strategy for high-frequency trading
6
+ * No bars, no candles - every tick counts
7
+ *
8
+ * MODELS:
9
+ * 1. Order Flow Imbalance (OFI) - Real-time buy/sell pressure
10
+ * 2. Cumulative Delta - Net volume direction
11
+ * 3. Bid/Ask Pressure - Spread and size analysis
12
+ * 4. Momentum - Price velocity and acceleration
13
+ * 5. Mean Reversion - Z-score on tick prices
14
+ *
15
+ * SIGNALS:
16
+ * - Generated on each tick when conditions align
17
+ * - Sub-second decision making
18
+ * - Designed for <50ms execution
19
+ */
20
+
21
+ const EventEmitter = require('events');
22
+ const { logger } = require('../../utils/logger');
23
+
24
+ const log = logger.scope('HFT');
25
+
26
+ /**
27
+ * Circular buffer for efficient tick storage
28
+ */
29
+ class CircularBuffer {
30
+ constructor(size) {
31
+ this.size = size;
32
+ this.buffer = new Array(size);
33
+ this.head = 0;
34
+ this.count = 0;
35
+ }
36
+
37
+ push(item) {
38
+ this.buffer[this.head] = item;
39
+ this.head = (this.head + 1) % this.size;
40
+ if (this.count < this.size) this.count++;
41
+ }
42
+
43
+ get(index) {
44
+ if (index >= this.count) return null;
45
+ const actualIndex = (this.head - this.count + index + this.size) % this.size;
46
+ return this.buffer[actualIndex];
47
+ }
48
+
49
+ getLast(n = 1) {
50
+ const result = [];
51
+ const count = Math.min(n, this.count);
52
+ for (let i = this.count - count; i < this.count; i++) {
53
+ result.push(this.get(i));
54
+ }
55
+ return result;
56
+ }
57
+
58
+ getAll() {
59
+ return this.getLast(this.count);
60
+ }
61
+
62
+ get length() {
63
+ return this.count;
64
+ }
65
+
66
+ clear() {
67
+ this.head = 0;
68
+ this.count = 0;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * HFT Tick Strategy
74
+ */
75
+ class HFTTickStrategy extends EventEmitter {
76
+ constructor() {
77
+ super();
78
+
79
+ // Configuration
80
+ this.tickSize = 0.25;
81
+ this.tickValue = 5.0;
82
+ this.contractId = null;
83
+ this.initialized = false;
84
+
85
+ // Tick buffers (circular for memory efficiency)
86
+ this.tickBuffer = new CircularBuffer(1000); // Last 1000 ticks
87
+ this.priceBuffer = new CircularBuffer(500); // Last 500 prices
88
+
89
+ // Real-time metrics
90
+ this.cumulativeDelta = 0;
91
+ this.buyVolume = 0;
92
+ this.sellVolume = 0;
93
+ this.lastPrice = 0;
94
+ this.lastBid = 0;
95
+ this.lastAsk = 0;
96
+
97
+ // OFI calculation
98
+ this.ofiWindow = 50; // Ticks for OFI calculation
99
+ this.ofiValue = 0;
100
+
101
+ // Momentum
102
+ this.momentumWindow = 20;
103
+ this.momentum = 0;
104
+ this.acceleration = 0;
105
+
106
+ // Mean reversion
107
+ this.zscoreWindow = 100;
108
+ this.zscore = 0;
109
+ this.mean = 0;
110
+ this.std = 0;
111
+
112
+ // Signal thresholds
113
+ this.ofiThreshold = 0.3; // |OFI| > 0.3 for signal
114
+ this.zscoreThreshold = 1.5; // |Z| > 1.5 for mean reversion
115
+ this.momentumThreshold = 0.5; // Momentum confirmation
116
+ this.minConfidence = 0.6; // Minimum confidence for signal
117
+
118
+ // Trade parameters
119
+ this.baseStopTicks = 8;
120
+ this.baseTargetTicks = 12;
121
+
122
+ // State
123
+ this.tickCount = 0;
124
+ this.signalCount = 0;
125
+ this.lastSignalTime = 0;
126
+ this.cooldownMs = 5000; // 5 second cooldown between signals
127
+ }
128
+
129
+ /**
130
+ * Initialize strategy for a contract
131
+ */
132
+ initialize(contractId, tickSize = 0.25, tickValue = 5.0) {
133
+ this.contractId = contractId;
134
+ this.tickSize = tickSize;
135
+ this.tickValue = tickValue;
136
+ this.initialized = true;
137
+
138
+ // Reset state
139
+ this.tickBuffer.clear();
140
+ this.priceBuffer.clear();
141
+ this.cumulativeDelta = 0;
142
+ this.buyVolume = 0;
143
+ this.sellVolume = 0;
144
+ this.tickCount = 0;
145
+
146
+ log.info(`Initialized for ${contractId}: tick=${tickSize}, value=${tickValue}`);
147
+ }
148
+
149
+ /**
150
+ * Process a single tick - CORE HFT FUNCTION
151
+ * Called on every market tick
152
+ */
153
+ processTick(tick) {
154
+ if (!this.initialized) return;
155
+
156
+ const { price, bid, ask, volume, side, timestamp } = tick;
157
+
158
+ // Store tick
159
+ this.tickBuffer.push({
160
+ price,
161
+ bid: bid || this.lastBid,
162
+ ask: ask || this.lastAsk,
163
+ volume: volume || 1,
164
+ side: side || this.inferSide(price, bid, ask),
165
+ timestamp: timestamp || Date.now(),
166
+ });
167
+
168
+ this.priceBuffer.push(price);
169
+ this.tickCount++;
170
+
171
+ // Update last values
172
+ this.lastPrice = price;
173
+ if (bid) this.lastBid = bid;
174
+ if (ask) this.lastAsk = ask;
175
+
176
+ // Update real-time metrics
177
+ this._updateDelta(tick);
178
+ this._updateOFI();
179
+ this._updateMomentum();
180
+ this._updateZScore();
181
+
182
+ // Check for signal (every tick!)
183
+ this._checkSignal(tick);
184
+ }
185
+
186
+ /**
187
+ * Infer trade side from price position
188
+ */
189
+ inferSide(price, bid, ask) {
190
+ if (!bid || !ask) return 'unknown';
191
+ const mid = (bid + ask) / 2;
192
+ return price >= mid ? 'BUY' : 'SELL';
193
+ }
194
+
195
+ /**
196
+ * Update cumulative delta
197
+ */
198
+ _updateDelta(tick) {
199
+ const vol = tick.volume || 1;
200
+ const side = tick.side?.toUpperCase() || this.inferSide(tick.price, tick.bid, tick.ask);
201
+
202
+ if (side === 'BUY') {
203
+ this.buyVolume += vol;
204
+ this.cumulativeDelta += vol;
205
+ } else if (side === 'SELL') {
206
+ this.sellVolume += vol;
207
+ this.cumulativeDelta -= vol;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Calculate Order Flow Imbalance on recent ticks
213
+ */
214
+ _updateOFI() {
215
+ const ticks = this.tickBuffer.getLast(this.ofiWindow);
216
+ if (ticks.length < 10) {
217
+ this.ofiValue = 0;
218
+ return;
219
+ }
220
+
221
+ let buyPressure = 0;
222
+ let sellPressure = 0;
223
+
224
+ for (const tick of ticks) {
225
+ const vol = tick.volume || 1;
226
+ const side = (tick.side || '').toUpperCase();
227
+
228
+ if (side === 'BUY') {
229
+ buyPressure += vol;
230
+ } else if (side === 'SELL') {
231
+ sellPressure += vol;
232
+ }
233
+ }
234
+
235
+ const total = buyPressure + sellPressure;
236
+ if (total > 0) {
237
+ // OFI range: -1 (all sells) to +1 (all buys)
238
+ this.ofiValue = (buyPressure - sellPressure) / total;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Calculate price momentum
244
+ */
245
+ _updateMomentum() {
246
+ const prices = this.priceBuffer.getLast(this.momentumWindow);
247
+ if (prices.length < 5) {
248
+ this.momentum = 0;
249
+ this.acceleration = 0;
250
+ return;
251
+ }
252
+
253
+ // Momentum = price change over window
254
+ const first = prices[0];
255
+ const last = prices[prices.length - 1];
256
+ this.momentum = (last - first) / this.tickSize;
257
+
258
+ // Acceleration = change in momentum
259
+ if (prices.length >= 10) {
260
+ const mid = prices[Math.floor(prices.length / 2)];
261
+ const firstHalf = (mid - first) / this.tickSize;
262
+ const secondHalf = (last - mid) / this.tickSize;
263
+ this.acceleration = secondHalf - firstHalf;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Calculate Z-Score for mean reversion
269
+ */
270
+ _updateZScore() {
271
+ const prices = this.priceBuffer.getLast(this.zscoreWindow);
272
+ if (prices.length < 20) {
273
+ this.zscore = 0;
274
+ return;
275
+ }
276
+
277
+ // Calculate mean
278
+ this.mean = prices.reduce((a, b) => a + b, 0) / prices.length;
279
+
280
+ // Calculate standard deviation
281
+ const variance = prices.reduce((sum, p) => sum + Math.pow(p - this.mean, 2), 0) / prices.length;
282
+ this.std = Math.sqrt(variance);
283
+
284
+ // Z-Score
285
+ if (this.std > 0.0001) {
286
+ this.zscore = (this.lastPrice - this.mean) / this.std;
287
+ } else {
288
+ this.zscore = 0;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Check for trading signal
294
+ */
295
+ _checkSignal(tick) {
296
+ // Need minimum data
297
+ if (this.tickCount < 50) return;
298
+
299
+ // Cooldown check
300
+ const now = Date.now();
301
+ if (now - this.lastSignalTime < this.cooldownMs) return;
302
+
303
+ // Calculate composite score
304
+ const { direction, confidence, scores } = this._calculateSignal();
305
+
306
+ if (direction === 'none' || confidence < this.minConfidence) return;
307
+
308
+ // Generate signal
309
+ this.signalCount++;
310
+ this.lastSignalTime = now;
311
+
312
+ const signal = this._buildSignal(direction, confidence, scores, tick);
313
+
314
+ log.info(`SIGNAL #${this.signalCount}: ${direction.toUpperCase()} @ ${tick.price} | ` +
315
+ `OFI=${this.ofiValue.toFixed(2)} Z=${this.zscore.toFixed(2)} Mom=${this.momentum.toFixed(1)} | ` +
316
+ `Conf=${(confidence * 100).toFixed(0)}%`);
317
+
318
+ this.emit('signal', signal);
319
+ }
320
+
321
+ /**
322
+ * Calculate trading signal from all models
323
+ */
324
+ _calculateSignal() {
325
+ let direction = 'none';
326
+ let confidence = 0;
327
+
328
+ const scores = {
329
+ ofi: 0,
330
+ zscore: 0,
331
+ momentum: 0,
332
+ delta: 0,
333
+ composite: 0,
334
+ };
335
+
336
+ // === MODEL 1: OFI ===
337
+ const absOfi = Math.abs(this.ofiValue);
338
+ if (absOfi > this.ofiThreshold) {
339
+ scores.ofi = Math.min(1.0, absOfi / 0.6);
340
+ }
341
+
342
+ // === MODEL 2: Z-Score Mean Reversion ===
343
+ const absZ = Math.abs(this.zscore);
344
+ if (absZ > this.zscoreThreshold) {
345
+ scores.zscore = Math.min(1.0, absZ / 3.0);
346
+ }
347
+
348
+ // === MODEL 3: Momentum ===
349
+ const absMom = Math.abs(this.momentum);
350
+ if (absMom > this.momentumThreshold) {
351
+ scores.momentum = Math.min(1.0, absMom / 3.0);
352
+ }
353
+
354
+ // === MODEL 4: Delta ===
355
+ const totalVol = this.buyVolume + this.sellVolume;
356
+ if (totalVol > 0) {
357
+ const deltaRatio = this.cumulativeDelta / totalVol;
358
+ scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
359
+ }
360
+
361
+ // === COMPOSITE SCORE ===
362
+ scores.composite =
363
+ scores.ofi * 0.35 + // OFI: 35%
364
+ scores.zscore * 0.25 + // Z-Score: 25%
365
+ scores.momentum * 0.20 + // Momentum: 20%
366
+ scores.delta * 0.20; // Delta: 20%
367
+
368
+ confidence = scores.composite;
369
+
370
+ // === DETERMINE DIRECTION ===
371
+ // Mean reversion: go opposite to z-score
372
+ // Momentum: confirm with OFI and delta
373
+
374
+ if (scores.composite >= this.minConfidence) {
375
+ // Primary: Mean reversion
376
+ if (absZ > this.zscoreThreshold) {
377
+ direction = this.zscore > 0 ? 'short' : 'long';
378
+
379
+ // Confirm with OFI
380
+ const ofiConfirms =
381
+ (direction === 'long' && this.ofiValue > 0) ||
382
+ (direction === 'short' && this.ofiValue < 0);
383
+
384
+ if (ofiConfirms) {
385
+ confidence += 0.1;
386
+ } else if (Math.abs(this.ofiValue) > 0.2) {
387
+ // OFI contradicts - reduce confidence
388
+ confidence -= 0.15;
389
+ }
390
+ }
391
+ // Fallback: Momentum breakout
392
+ else if (absMom > this.momentumThreshold * 2 && absOfi > this.ofiThreshold) {
393
+ direction = this.momentum > 0 ? 'long' : 'short';
394
+ // Must be confirmed by OFI
395
+ if ((direction === 'long' && this.ofiValue < 0) ||
396
+ (direction === 'short' && this.ofiValue > 0)) {
397
+ direction = 'none';
398
+ }
399
+ }
400
+ }
401
+
402
+ confidence = Math.min(1.0, Math.max(0, confidence));
403
+
404
+ return { direction, confidence, scores };
405
+ }
406
+
407
+ /**
408
+ * Build signal object
409
+ */
410
+ _buildSignal(direction, confidence, scores, tick) {
411
+ const entry = tick.price;
412
+ const isLong = direction === 'long';
413
+
414
+ // Adaptive stops based on volatility (std dev)
415
+ const volMult = Math.max(0.5, Math.min(2.0, this.std / this.tickSize / 4));
416
+ const stopTicks = Math.round(this.baseStopTicks * volMult);
417
+ const targetTicks = Math.round(this.baseTargetTicks * volMult);
418
+
419
+ const stopLoss = isLong
420
+ ? entry - stopTicks * this.tickSize
421
+ : entry + stopTicks * this.tickSize;
422
+
423
+ const takeProfit = isLong
424
+ ? entry + targetTicks * this.tickSize
425
+ : entry - targetTicks * this.tickSize;
426
+
427
+ return {
428
+ id: `hft-${Date.now()}-${this.signalCount}`,
429
+ timestamp: Date.now(),
430
+ contractId: this.contractId,
431
+ direction,
432
+ side: isLong ? 0 : 1,
433
+ entry,
434
+ stopLoss,
435
+ takeProfit,
436
+ confidence,
437
+ scores,
438
+ // Model values for logging
439
+ ofi: this.ofiValue,
440
+ zscore: this.zscore,
441
+ momentum: this.momentum,
442
+ delta: this.cumulativeDelta,
443
+ // Tick count
444
+ tickCount: this.tickCount,
445
+ };
446
+ }
447
+
448
+ /**
449
+ * Get current model values for monitoring
450
+ */
451
+ getModelValues() {
452
+ return {
453
+ ofi: this.ofiValue,
454
+ delta: this.cumulativeDelta,
455
+ zscore: this.zscore,
456
+ momentum: this.momentum,
457
+ mean: this.mean,
458
+ std: this.std,
459
+ buyVolume: this.buyVolume,
460
+ sellVolume: this.sellVolume,
461
+ tickCount: this.tickCount,
462
+ signalCount: this.signalCount,
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Get state for display
468
+ */
469
+ getState() {
470
+ return this.getModelValues();
471
+ }
472
+
473
+ /**
474
+ * Reset strategy state
475
+ */
476
+ reset() {
477
+ this.tickBuffer.clear();
478
+ this.priceBuffer.clear();
479
+ this.cumulativeDelta = 0;
480
+ this.buyVolume = 0;
481
+ this.sellVolume = 0;
482
+ this.tickCount = 0;
483
+ this.signalCount = 0;
484
+ this.ofiValue = 0;
485
+ this.zscore = 0;
486
+ this.momentum = 0;
487
+ log.info('Strategy reset');
488
+ }
489
+ }
490
+
491
+ // Singleton instance
492
+ const hftStrategy = new HFTTickStrategy();
493
+
494
+ module.exports = { HFTTickStrategy, hftStrategy };