hedgequantx 2.9.124 → 2.9.126

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.9.124",
3
+ "version": "2.9.126",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/data.js CHANGED
@@ -36,8 +36,9 @@ class MarketDataFeed extends EventEmitter {
36
36
  /**
37
37
  * Connect to Rithmic TICKER_PLANT
38
38
  * @param {Object} rithmicCredentials - Credentials from RithmicService.getRithmicCredentials()
39
+ * @param {number} retries - Number of connection retries (default: 2)
39
40
  */
40
- async connect(rithmicCredentials) {
41
+ async connect(rithmicCredentials, retries = 2) {
41
42
  if (this.connected) return;
42
43
 
43
44
  if (!rithmicCredentials || !rithmicCredentials.userId || !rithmicCredentials.password) {
@@ -45,7 +46,6 @@ class MarketDataFeed extends EventEmitter {
45
46
  }
46
47
 
47
48
  this.credentials = rithmicCredentials;
48
- this.connection = new RithmicConnection();
49
49
 
50
50
  this.config = {
51
51
  uri: rithmicCredentials.gateway || RITHMIC_ENDPOINTS.CHICAGO,
@@ -56,39 +56,74 @@ class MarketDataFeed extends EventEmitter {
56
56
  appVersion: '2.0.0',
57
57
  };
58
58
 
59
- try {
60
- // Ensure protobuf definitions are loaded
61
- await proto.load();
62
-
63
- await this.connection.connect(this.config);
59
+ // Ensure protobuf definitions are loaded
60
+ await proto.load();
64
61
 
65
- // Setup message handler for market data
66
- this.connection.on('message', (msg) => this._handleMessage(msg));
67
-
68
- // Login to TICKER_PLANT
69
- return new Promise((resolve, reject) => {
70
- const timeout = setTimeout(() => {
71
- reject(new Error('TICKER_PLANT login timeout'));
72
- }, 15000);
73
-
74
- this.connection.once('loggedIn', () => {
75
- clearTimeout(timeout);
76
- this.connected = true;
77
- this.emit('connected');
78
- resolve(true);
62
+ let lastError = null;
63
+ for (let attempt = 0; attempt <= retries; attempt++) {
64
+ try {
65
+ if (attempt > 0) {
66
+ this.emit('debug', `TICKER_PLANT retry ${attempt}/${retries}...`);
67
+ await new Promise(r => setTimeout(r, 2000)); // Wait 2s before retry
68
+ }
69
+
70
+ this.connection = new RithmicConnection();
71
+
72
+ // Connection timeout handler
73
+ this.connection.on('error', (err) => {
74
+ this.emit('debug', `TICKER_PLANT connection error: ${err.message}`);
79
75
  });
76
+
77
+ await this.connection.connect(this.config);
78
+ this.emit('debug', `TICKER_PLANT WebSocket connected to ${this.config.uri}`);
80
79
 
81
- this.connection.once('loginFailed', (data) => {
82
- clearTimeout(timeout);
83
- reject(new Error(data.message || 'TICKER_PLANT login failed'));
80
+ // Setup message handler for market data
81
+ this.connection.on('message', (msg) => this._handleMessage(msg));
82
+
83
+ // Handle disconnection for auto-reconnect
84
+ this.connection.on('disconnected', ({ code, reason }) => {
85
+ this.connected = false;
86
+ this.emit('disconnected', { code, reason });
87
+ // Auto-reconnect if not manual close
88
+ if (code !== 1000 && this.credentials) {
89
+ this.emit('debug', 'TICKER_PLANT disconnected, will reconnect in 3s...');
90
+ setTimeout(() => this.connect(this.credentials, 1), 3000);
91
+ }
84
92
  });
85
93
 
86
- this.connection.login('TICKER_PLANT');
87
- });
88
- } catch (e) {
89
- this.emit('error', e);
90
- throw e;
94
+ // Login to TICKER_PLANT
95
+ return await new Promise((resolve, reject) => {
96
+ const timeout = setTimeout(() => {
97
+ reject(new Error('TICKER_PLANT login timeout'));
98
+ }, 15000);
99
+
100
+ this.connection.once('loggedIn', () => {
101
+ clearTimeout(timeout);
102
+ this.connected = true;
103
+ this.emit('connected');
104
+ resolve(true);
105
+ });
106
+
107
+ this.connection.once('loginFailed', (data) => {
108
+ clearTimeout(timeout);
109
+ reject(new Error(data.message || 'TICKER_PLANT login failed'));
110
+ });
111
+
112
+ this.connection.login('TICKER_PLANT');
113
+ });
114
+ } catch (e) {
115
+ lastError = e;
116
+ this.emit('debug', `TICKER_PLANT attempt ${attempt + 1} failed: ${e.message}`);
117
+ if (this.connection) {
118
+ try { await this.connection.disconnect(); } catch (_) {}
119
+ this.connection = null;
120
+ }
121
+ }
91
122
  }
123
+
124
+ // All retries failed
125
+ this.emit('error', lastError);
126
+ throw lastError;
92
127
  }
93
128
 
94
129
  /**
@@ -234,7 +234,9 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
234
234
  let lastAsk = null;
235
235
  let ticksPerSecond = 0;
236
236
  let lastTickSecond = Math.floor(Date.now() / 1000);
237
- let lastLogSecond = 0;
237
+ let lastBiasLogSecond = 0;
238
+ let lastDebugLogSecond = 0;
239
+ let lastStateLogSecond = 0;
238
240
  let buyVolume = 0;
239
241
  let sellVolume = 0;
240
242
 
@@ -283,64 +285,66 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
283
285
  }
284
286
 
285
287
  // Log tick count every 10 seconds to confirm data flow
286
- if (tickCount % 1000 === 0 || (currentSecond % 10 === 0 && currentSecond !== lastLogSecond)) {
288
+ if (currentSecond - lastDebugLogSecond >= 10) {
289
+ lastDebugLogSecond = currentSecond;
287
290
  const state = strategy.getAnalysisState?.(contractId, price);
288
291
  const bars = state?.barsProcessed || 0;
289
292
  ui.addLog('debug', `Ticks: ${tickCount} | Bars: ${bars} | Price: ${price?.toFixed(2)}`);
290
293
  }
291
294
 
292
295
  // === SMART LOGS - REDUCED FREQUENCY ===
293
- if (currentSecond !== lastLogSecond && tickCount > 1) {
294
- lastLogSecond = currentSecond;
296
+ // Log bias every 5 seconds
297
+ if (currentSecond - lastBiasLogSecond >= 5 && tickCount > 1) {
298
+ lastBiasLogSecond = currentSecond;
295
299
 
296
300
  const totalVol = buyVolume + sellVolume;
297
301
  const buyPressure = totalVol > 0 ? (buyVolume / totalVol) * 100 : 50;
298
302
  const delta = buyVolume - sellVolume;
299
303
 
300
304
  let bias = buyPressure > 55 ? 'LONG' : buyPressure < 45 ? 'SHORT' : 'FLAT';
301
- // Log bias every 5 seconds or when it changes
302
- if (bias !== lastBias || currentSecond % 5 === 0) {
303
- const biasLog = smartLogs.getMarketBiasLog(bias, delta, buyPressure);
304
- const biasType = bias === 'LONG' ? 'bullish' : bias === 'SHORT' ? 'bearish' : 'analysis';
305
- ui.addLog(biasType, `${biasLog.message} ${biasLog.details || ''}`);
306
- lastBias = bias;
307
- // Reset volume after logging
308
- buyVolume = 0;
309
- sellVolume = 0;
310
- }
311
-
312
- // Strategy state log every 30 seconds (reduced frequency)
313
- if (currentSecond % 30 === 0) {
314
- const state = strategy.getAnalysisState?.(contractId, price);
315
- if (state) {
316
- const bars = state.barsProcessed || 0;
317
- sessionLogger.state(state.activeZones || 0, state.swingsDetected || 0, bars, lastBias);
318
- if (!state.ready) {
319
- ui.addLog('system', `${state.message} (${bars} bars)`);
320
- } else {
321
- const resStr = state.nearestResistance ? state.nearestResistance.toFixed(2) : '--';
322
- const supStr = state.nearestSupport ? state.nearestSupport.toFixed(2) : '--';
323
-
324
- ui.addLog('analysis', `Zones: ${state.activeZones} | R: ${resStr} | S: ${supStr} | Swings: ${state.swingsDetected}`);
325
- if (price && state.nearestResistance) {
326
- const gapR = state.nearestResistance - price, ticksR = Math.abs(Math.round(gapR / tickSize));
327
- if (ticksR <= 50) ui.addLog('analysis', `PROX R: ${Math.abs(gapR).toFixed(2)} pts (${ticksR} ticks) | Sweep ABOVE then reject`);
328
- }
329
- if (price && state.nearestSupport) {
330
- const gapS = price - state.nearestSupport, ticksS = Math.abs(Math.round(gapS / tickSize));
331
- if (ticksS <= 50) ui.addLog('analysis', `PROX S: ${Math.abs(gapS).toFixed(2)} pts (${ticksS} ticks) | Sweep BELOW then reject`);
332
- }
333
- if (state.activeZones === 0) ui.addLog('risk', 'Building liquidity map...');
334
- else if (!state.nearestSupport && !state.nearestResistance) ui.addLog('risk', 'Zones outside range');
335
- else if (!state.nearestSupport) ui.addLog('analysis', 'Monitoring R for SHORT sweep');
336
- else if (!state.nearestResistance) ui.addLog('analysis', 'Monitoring S for LONG sweep');
337
- else ui.addLog('ready', 'Both zones active - awaiting sweep');
305
+ const biasLog = smartLogs.getMarketBiasLog(bias, delta, buyPressure);
306
+ const biasType = bias === 'LONG' ? 'bullish' : bias === 'SHORT' ? 'bearish' : 'analysis';
307
+ ui.addLog(biasType, `${biasLog.message} ${biasLog.details || ''}`);
308
+ lastBias = bias;
309
+ // Reset volume after logging to avoid accumulation
310
+ buyVolume = 0;
311
+ sellVolume = 0;
312
+ }
313
+
314
+ // Strategy state log every 30 seconds
315
+ if (currentSecond - lastStateLogSecond >= 30 && tickCount > 1) {
316
+ lastStateLogSecond = currentSecond;
317
+ const state = strategy.getAnalysisState?.(contractId, price);
318
+ if (state) {
319
+ const bars = state.barsProcessed || 0;
320
+ sessionLogger.state(state.activeZones || 0, state.swingsDetected || 0, bars, lastBias);
321
+ if (!state.ready) {
322
+ ui.addLog('system', `${state.message} (${bars} bars)`);
323
+ } else {
324
+ const resStr = state.nearestResistance ? state.nearestResistance.toFixed(2) : '--';
325
+ const supStr = state.nearestSupport ? state.nearestSupport.toFixed(2) : '--';
326
+
327
+ ui.addLog('analysis', `Zones: ${state.activeZones} | R: ${resStr} | S: ${supStr} | Swings: ${state.swingsDetected}`);
328
+ if (price && state.nearestResistance) {
329
+ const gapR = state.nearestResistance - price, ticksR = Math.abs(Math.round(gapR / tickSize));
330
+ if (ticksR <= 50) ui.addLog('analysis', `PROX R: ${Math.abs(gapR).toFixed(2)} pts (${ticksR} ticks) | Sweep ABOVE then reject`);
331
+ }
332
+ if (price && state.nearestSupport) {
333
+ const gapS = price - state.nearestSupport, ticksS = Math.abs(Math.round(gapS / tickSize));
334
+ if (ticksS <= 50) ui.addLog('analysis', `PROX S: ${Math.abs(gapS).toFixed(2)} pts (${ticksS} ticks) | Sweep BELOW then reject`);
338
335
  }
336
+ if (state.activeZones === 0) ui.addLog('risk', 'Building liquidity map...');
337
+ else if (!state.nearestSupport && !state.nearestResistance) ui.addLog('risk', 'Zones outside range');
338
+ else if (!state.nearestSupport) ui.addLog('analysis', 'Monitoring R for SHORT sweep');
339
+ else if (!state.nearestResistance) ui.addLog('analysis', 'Monitoring S for LONG sweep');
340
+ else ui.addLog('ready', 'Both zones active - awaiting sweep');
339
341
  }
340
342
  }
341
-
342
- // AI status every 60s
343
- if (currentSecond % 60 === 0 && supervisionEnabled && supervisionEngine) ui.addLog('analysis', `AI: ${supervisionEngine.getStatus().agents.map(a => a.name.split(' ')[0]).join(', ')}`);
343
+ }
344
+
345
+ // AI status every 60s
346
+ if (currentSecond % 60 === 0 && supervisionEnabled && supervisionEngine) {
347
+ ui.addLog('analysis', `AI: ${supervisionEngine.getStatus().agents.map(a => a.name.split(' ')[0]).join(', ')}`);
344
348
  }
345
349
 
346
350
  lastPrice = price;