hedgequantx 2.6.48 → 2.6.50

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.48",
3
+ "version": "2.6.50",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -64,6 +64,8 @@ class CopyEngine {
64
64
  this.pollCount = 0;
65
65
  this.orderCount = 0;
66
66
  this.failedOrders = 0;
67
+ this.lastLogTime = 0;
68
+ this.positionEntryTime = null;
67
69
 
68
70
  // Retry configuration
69
71
  this.maxRetries = 3;
@@ -212,6 +214,9 @@ class CopyEngine {
212
214
  const size = Math.abs(position.quantity);
213
215
  const entry = position.averagePrice || 0;
214
216
 
217
+ // Track entry time for smart logs
218
+ this.positionEntryTime = Date.now();
219
+
215
220
  algoLogger.positionOpened(this.ui, displaySymbol, side, size, entry);
216
221
 
217
222
  // Feed to AI supervisor
@@ -282,6 +287,9 @@ class CopyEngine {
282
287
  const displaySymbol = position.symbol || this.symbol.name;
283
288
  const size = Math.abs(position.quantity);
284
289
 
290
+ // Reset entry time
291
+ this.positionEntryTime = null;
292
+
285
293
  algoLogger.positionClosed(this.ui, displaySymbol, side, size, exitPrice, pnl);
286
294
 
287
295
  // Feed to AI supervisor
@@ -432,6 +440,28 @@ class CopyEngine {
432
440
 
433
441
  this.pollCount++;
434
442
 
443
+ // Smart logs every second - non-repetitive status updates
444
+ const now = Date.now();
445
+ if (now - this.lastLogTime > 1000) {
446
+ const smartLogs = require('./smart-logs');
447
+
448
+ if (this.leadPositions.size > 0) {
449
+ // In position - show position status
450
+ for (const [key, pos] of this.leadPositions) {
451
+ const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
452
+ const unrealizedPnl = pos.profitAndLoss || 0;
453
+ const holdTime = Math.floor((now - (this.positionEntryTime || now)) / 1000);
454
+ const posLog = smartLogs.getPositionUpdateLog(side, Math.abs(pos.quantity), unrealizedPnl, pos.averagePrice || 0, pos.averagePrice || 0, holdTime);
455
+ algoLogger.trade(this.ui, posLog.message, posLog.details);
456
+ }
457
+ } else {
458
+ // No position - show scanning status
459
+ const scanLog = smartLogs.getScanningLog(true);
460
+ algoLogger.info(this.ui, scanLog.message, `poll #${this.pollCount} | ${pollTime}ms`);
461
+ }
462
+ this.lastLogTime = now;
463
+ }
464
+
435
465
  } catch (err) {
436
466
  log.warn('Poll error', { error: err.message });
437
467
  }
@@ -430,10 +430,18 @@ const launchAlgo = async (service, account, contract, config) => {
430
430
  if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
431
431
 
432
432
  // Update position info (like R Trader Positions panel)
433
+ const wasFlat = stats.position === 0;
433
434
  stats.position = pos.quantity || 0;
434
435
  stats.entryPrice = pos.averagePrice || 0;
435
436
  currentPosition = pos.quantity || 0;
436
437
 
438
+ // Track entry time when opening a new position
439
+ if (wasFlat && stats.position !== 0) {
440
+ stats.entryTime = Date.now();
441
+ } else if (stats.position === 0) {
442
+ stats.entryTime = null;
443
+ }
444
+
437
445
  // CRITICAL: Update Open P&L from instrument-level data (real-time, same as R Trader)
438
446
  // pos.openPnl comes from INSTRUMENT_PNL_UPDATE (450) - this is the unrealized P&L
439
447
  if (pos.openPnl !== undefined && pos.openPnl !== null) {
@@ -716,25 +724,43 @@ const launchAlgo = async (service, account, contract, config) => {
716
724
 
717
725
  stats.latency = Date.now() - latencyStart;
718
726
 
719
- // Heartbeat every 15 seconds - show strategy model values
720
- if (Date.now() - lastHeartbeat > 15000) {
721
- // Try to get model values from strategy
727
+ // Smart logs every 1 second - non-repetitive, varied messages
728
+ const now = Date.now();
729
+ if (now - lastHeartbeat > 1000) {
722
730
  const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(contractId) || null;
723
731
 
724
732
  if (modelValues && modelValues.ofi !== undefined) {
725
- // HFT Strategy - pure tick values
726
- const ofi = (modelValues.ofi || 0).toFixed(2);
733
+ const ofi = modelValues.ofi || 0;
727
734
  const delta = modelValues.delta || 0;
728
- const zscore = (modelValues.zscore || 0).toFixed(2);
729
- const mom = (modelValues.momentum || 0).toFixed(1);
730
- const signals = modelValues.signalCount || 0;
731
- const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
732
- algoLogger.info(ui, strategyName, `OFI=${ofi} Z=${zscore} Mom=${mom} Δ=${deltaStr} | ${tps} t/15s | ${tickCount} total`);
735
+ const zscore = modelValues.zscore || 0;
736
+ const mom = modelValues.momentum || 0;
737
+
738
+ // Get smart varied log based on market state
739
+ const smartLogs = require('./smart-logs');
740
+ const stateLog = smartLogs.getMarketStateLog(ofi, zscore, mom, delta);
741
+
742
+ if (currentPosition !== 0) {
743
+ // In position - show position status with varied messages
744
+ const side = currentPosition > 0 ? 'LONG' : 'SHORT';
745
+ const unrealizedPnl = stats.openPnl || 0;
746
+ const holdTime = Math.floor((now - (stats.entryTime || now)) / 1000);
747
+ const posLog = smartLogs.getPositionUpdateLog(side, Math.abs(currentPosition), unrealizedPnl, stats.entryPrice, tickData.price, holdTime);
748
+ ui.addLog('trade', `${posLog.message} - ${posLog.details}`);
749
+ } else {
750
+ // Not in position - show market state with varied messages
751
+ if (stateLog.details) {
752
+ ui.addLog('analysis', `${stateLog.message} - ${stateLog.details}`);
753
+ } else {
754
+ ui.addLog('info', stateLog.message);
755
+ }
756
+ }
733
757
  } else {
734
- // Fallback
735
- algoLogger.info(ui, 'SCANNING', `${tps} ticks/15s | ${tickCount} total | price=${tickData.price}`);
758
+ // Waiting for data
759
+ const smartLogs = require('./smart-logs');
760
+ const scanLog = smartLogs.getScanningLog(true);
761
+ ui.addLog('info', `${scanLog.message} ${tps} ticks/s`);
736
762
  }
737
- lastHeartbeat = Date.now();
763
+ lastHeartbeat = now;
738
764
  tps = 0;
739
765
  }
740
766
  });
@@ -0,0 +1,394 @@
1
+ /**
2
+ * =============================================================================
3
+ * Smart Logging System - Non-repetitive, contextual, varied messages
4
+ * =============================================================================
5
+ * Copied from HQX-TG smart-logs.ts
6
+ * - Uses message pools to avoid repetition
7
+ * - Tracks recent messages to ensure variety
8
+ * - Provides market-context-aware messages
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ // Track recently used messages to avoid repetition
14
+ const recentMessages = new Map();
15
+ const MAX_RECENT = 5;
16
+
17
+ /**
18
+ * Get a message from a pool, avoiding recent ones
19
+ */
20
+ function getVariedMessage(category, pool, defaultMsg) {
21
+ const recent = recentMessages.get(category) || [];
22
+
23
+ // Filter out recently used messages
24
+ const available = pool.filter(msg => !recent.includes(msg));
25
+
26
+ // If all messages were recently used, reset
27
+ if (available.length === 0) {
28
+ recentMessages.set(category, []);
29
+ return pool[Math.floor(Math.random() * pool.length)] || defaultMsg;
30
+ }
31
+
32
+ // Pick a random available message
33
+ const chosen = available[Math.floor(Math.random() * available.length)] || defaultMsg;
34
+
35
+ // Track it
36
+ recent.push(chosen);
37
+ if (recent.length > MAX_RECENT) recent.shift();
38
+ recentMessages.set(category, recent);
39
+
40
+ return chosen;
41
+ }
42
+
43
+ // =============================================================================
44
+ // MESSAGE POOLS - Market Flow
45
+ // =============================================================================
46
+
47
+ const LONG_BIAS_MESSAGES = [
48
+ 'Bullish bias detected',
49
+ 'Buyers in control',
50
+ 'Long-side pressure',
51
+ 'Bid accumulation',
52
+ 'Buy programs active',
53
+ ];
54
+
55
+ const SHORT_BIAS_MESSAGES = [
56
+ 'Bearish bias detected',
57
+ 'Sellers in control',
58
+ 'Short-side pressure',
59
+ 'Offer distribution',
60
+ 'Sell programs active',
61
+ ];
62
+
63
+ const FLAT_BIAS_MESSAGES = [
64
+ 'Market balanced',
65
+ 'Two-way flow',
66
+ 'Consolidation mode',
67
+ 'No clear direction',
68
+ 'Mixed signals',
69
+ ];
70
+
71
+ // =============================================================================
72
+ // MESSAGE POOLS - Trading Events
73
+ // =============================================================================
74
+
75
+ const SIGNAL_LONG_MESSAGES = [
76
+ 'Buy signal generated',
77
+ 'Long opportunity detected',
78
+ 'Bullish setup confirmed',
79
+ 'Entry signal: LONG',
80
+ 'Buy zone activated',
81
+ ];
82
+
83
+ const SIGNAL_SHORT_MESSAGES = [
84
+ 'Sell signal generated',
85
+ 'Short opportunity detected',
86
+ 'Bearish setup confirmed',
87
+ 'Entry signal: SHORT',
88
+ 'Sell zone activated',
89
+ ];
90
+
91
+ const ENTRY_LONG_MESSAGES = [
92
+ 'Long position opened',
93
+ 'Buy order filled',
94
+ 'Entered long',
95
+ 'Long initiated',
96
+ 'Position: LONG',
97
+ ];
98
+
99
+ const ENTRY_SHORT_MESSAGES = [
100
+ 'Short position opened',
101
+ 'Sell order filled',
102
+ 'Entered short',
103
+ 'Short initiated',
104
+ 'Position: SHORT',
105
+ ];
106
+
107
+ const EXIT_PROFIT_MESSAGES = [
108
+ 'Target reached',
109
+ 'Profit taken',
110
+ 'Winner closed',
111
+ 'TP hit',
112
+ 'Profit locked',
113
+ ];
114
+
115
+ const EXIT_LOSS_MESSAGES = [
116
+ 'Stop triggered',
117
+ 'Loss taken',
118
+ 'Loser closed',
119
+ 'SL hit',
120
+ 'Risk contained',
121
+ ];
122
+
123
+ // =============================================================================
124
+ // MESSAGE POOLS - Analysis
125
+ // =============================================================================
126
+
127
+ const MOMENTUM_UP_MESSAGES = [
128
+ 'Momentum building up',
129
+ 'Price accelerating',
130
+ 'Bullish momentum',
131
+ 'Upward thrust',
132
+ 'Buying momentum',
133
+ ];
134
+
135
+ const MOMENTUM_DOWN_MESSAGES = [
136
+ 'Momentum building down',
137
+ 'Price declining',
138
+ 'Bearish momentum',
139
+ 'Downward thrust',
140
+ 'Selling momentum',
141
+ ];
142
+
143
+ const OVERBOUGHT_MESSAGES = [
144
+ 'Overbought condition',
145
+ 'Extended to upside',
146
+ 'Mean reversion setup (short)',
147
+ 'Exhaustion detected',
148
+ 'Stretched high',
149
+ ];
150
+
151
+ const OVERSOLD_MESSAGES = [
152
+ 'Oversold condition',
153
+ 'Extended to downside',
154
+ 'Mean reversion setup (long)',
155
+ 'Capitulation detected',
156
+ 'Stretched low',
157
+ ];
158
+
159
+ const HIGH_VOLATILITY_MESSAGES = [
160
+ 'Volatility elevated',
161
+ 'High ATR detected',
162
+ 'Wide swings',
163
+ 'Market volatile',
164
+ 'Increased range',
165
+ ];
166
+
167
+ const LOW_VOLATILITY_MESSAGES = [
168
+ 'Low volatility',
169
+ 'Tight range',
170
+ 'Compressed action',
171
+ 'Market quiet',
172
+ 'Narrow swings',
173
+ ];
174
+
175
+ // =============================================================================
176
+ // MESSAGE POOLS - Status
177
+ // =============================================================================
178
+
179
+ const SCANNING_MESSAGES = [
180
+ 'Scanning market...',
181
+ 'Analyzing flow...',
182
+ 'Monitoring price action...',
183
+ 'Watching for setups...',
184
+ 'Evaluating conditions...',
185
+ ];
186
+
187
+ const WAITING_MESSAGES = [
188
+ 'Waiting for confirmation...',
189
+ 'Pending trigger...',
190
+ 'Standby mode...',
191
+ 'Awaiting signal...',
192
+ 'Ready to act...',
193
+ ];
194
+
195
+ const IN_POSITION_PROFIT_MESSAGES = [
196
+ 'Running profit',
197
+ 'In the money',
198
+ 'Trade working',
199
+ 'Position profitable',
200
+ 'Gains accumulating',
201
+ ];
202
+
203
+ const IN_POSITION_LOSS_MESSAGES = [
204
+ 'Managing drawdown',
205
+ 'Underwater',
206
+ 'Holding through',
207
+ 'Defending position',
208
+ 'Monitoring risk',
209
+ ];
210
+
211
+ // =============================================================================
212
+ // SMART LOG GENERATORS
213
+ // =============================================================================
214
+
215
+ /**
216
+ * Get a market bias log
217
+ */
218
+ function getMarketBiasLog(direction, delta, ofi) {
219
+ let pool;
220
+ switch (direction) {
221
+ case 'LONG':
222
+ pool = LONG_BIAS_MESSAGES;
223
+ break;
224
+ case 'SHORT':
225
+ pool = SHORT_BIAS_MESSAGES;
226
+ break;
227
+ default:
228
+ pool = FLAT_BIAS_MESSAGES;
229
+ }
230
+
231
+ const message = getVariedMessage(`bias_${direction}`, pool, `${direction} bias`);
232
+ const arrow = direction === 'LONG' ? '▲' : direction === 'SHORT' ? '▼' : '=';
233
+
234
+ let details;
235
+ if (delta !== undefined && ofi !== undefined) {
236
+ const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
237
+ details = `${arrow} Δ=${deltaStr} | OFI=${ofi.toFixed(2)}`;
238
+ }
239
+
240
+ return { message, details };
241
+ }
242
+
243
+ /**
244
+ * Get a momentum log
245
+ */
246
+ function getMomentumLog(momentum, zscore) {
247
+ const isUp = momentum > 0;
248
+ const pool = isUp ? MOMENTUM_UP_MESSAGES : MOMENTUM_DOWN_MESSAGES;
249
+ const category = isUp ? 'mom_up' : 'mom_down';
250
+ const message = getVariedMessage(category, pool, isUp ? 'Momentum up' : 'Momentum down');
251
+ const arrow = isUp ? '▲' : '▼';
252
+
253
+ return {
254
+ message: `${arrow} ${message}`,
255
+ details: `Mom=${momentum.toFixed(1)} | Z=${zscore.toFixed(2)}`,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Get a mean reversion log
261
+ */
262
+ function getMeanReversionLog(zscore) {
263
+ const isOverbought = zscore > 0;
264
+ const pool = isOverbought ? OVERBOUGHT_MESSAGES : OVERSOLD_MESSAGES;
265
+ const category = isOverbought ? 'mr_overbought' : 'mr_oversold';
266
+ const message = getVariedMessage(category, pool, isOverbought ? 'Overbought' : 'Oversold');
267
+ const arrow = isOverbought ? '▼' : '▲'; // Opposite - looking for reversal
268
+
269
+ return {
270
+ message: `${arrow} ${message}`,
271
+ details: `Z-Score: ${zscore.toFixed(2)} | Looking for ${isOverbought ? 'SHORT' : 'LONG'}`,
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Get a signal log
277
+ */
278
+ function getSignalLog(direction, symbol, confidence, strategy) {
279
+ const pool = direction === 'LONG' ? SIGNAL_LONG_MESSAGES : SIGNAL_SHORT_MESSAGES;
280
+ const message = getVariedMessage(`signal_${direction}`, pool, `${direction} signal`);
281
+ const arrow = direction === 'LONG' ? '▲' : '▼';
282
+
283
+ return {
284
+ message: `${arrow} ${message}`,
285
+ details: `${symbol} | ${confidence.toFixed(0)}% | ${strategy}`,
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Get an entry log
291
+ */
292
+ function getEntryLog(direction, symbol, size, price) {
293
+ const pool = direction === 'LONG' ? ENTRY_LONG_MESSAGES : ENTRY_SHORT_MESSAGES;
294
+ const message = getVariedMessage(`entry_${direction}`, pool, `${direction} entry`);
295
+ const arrow = direction === 'LONG' ? '▲' : '▼';
296
+
297
+ return {
298
+ message: `${arrow} ${message}`,
299
+ details: `${size}x ${symbol} @ ${price.toFixed(2)}`,
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Get an exit log
305
+ */
306
+ function getExitLog(isProfit, symbol, size, price, pnl) {
307
+ const pool = isProfit ? EXIT_PROFIT_MESSAGES : EXIT_LOSS_MESSAGES;
308
+ const category = isProfit ? 'exit_profit' : 'exit_loss';
309
+ const message = getVariedMessage(category, pool, isProfit ? 'Profit taken' : 'Loss taken');
310
+ const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
311
+
312
+ return {
313
+ message,
314
+ details: `${size}x ${symbol} @ ${price.toFixed(2)} | ${pnlStr}`,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Get a volatility log
320
+ */
321
+ function getVolatilityLog(isHigh, atr) {
322
+ const pool = isHigh ? HIGH_VOLATILITY_MESSAGES : LOW_VOLATILITY_MESSAGES;
323
+ const category = isHigh ? 'vol_high' : 'vol_low';
324
+ const message = getVariedMessage(category, pool, isHigh ? 'High volatility' : 'Low volatility');
325
+
326
+ return {
327
+ message,
328
+ details: atr ? `ATR: ${atr.toFixed(2)}` : undefined,
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Get a scanning/waiting log
334
+ */
335
+ function getScanningLog(isScanning) {
336
+ const pool = isScanning ? SCANNING_MESSAGES : WAITING_MESSAGES;
337
+ const category = isScanning ? 'scanning' : 'waiting';
338
+ const message = getVariedMessage(category, pool, isScanning ? 'Scanning...' : 'Waiting...');
339
+
340
+ return { message, details: undefined };
341
+ }
342
+
343
+ /**
344
+ * Get position update log with varied messaging
345
+ */
346
+ function getPositionUpdateLog(side, size, unrealizedPnL, entryPrice, currentPrice, holdTime) {
347
+ const arrow = side === 'LONG' ? '▲' : '▼';
348
+ const pnlStr = unrealizedPnL >= 0 ? `+$${unrealizedPnL.toFixed(2)}` : `-$${Math.abs(unrealizedPnL).toFixed(2)}`;
349
+ const isProfit = unrealizedPnL >= 0;
350
+
351
+ const pool = isProfit ? IN_POSITION_PROFIT_MESSAGES : IN_POSITION_LOSS_MESSAGES;
352
+ const category = isProfit ? 'pos_profit' : 'pos_loss';
353
+ const prefix = getVariedMessage(category, pool, isProfit ? 'In profit' : 'Managing');
354
+
355
+ return {
356
+ message: `${arrow} ${side} ${size}x`,
357
+ details: `${prefix} ${pnlStr} @ ${currentPrice.toFixed(2)} | ${holdTime}s`,
358
+ };
359
+ }
360
+
361
+ /**
362
+ * Get market state log based on model values
363
+ */
364
+ function getMarketStateLog(ofi, zscore, momentum, delta) {
365
+ // Determine primary market state
366
+ const absZ = Math.abs(zscore);
367
+ const absOfi = Math.abs(ofi);
368
+ const absMom = Math.abs(momentum);
369
+
370
+ // Priority: Mean reversion > OFI pressure > Momentum
371
+ if (absZ > 1.5) {
372
+ return getMeanReversionLog(zscore);
373
+ } else if (absOfi > 0.3) {
374
+ const direction = ofi > 0 ? 'LONG' : 'SHORT';
375
+ return getMarketBiasLog(direction, delta, ofi);
376
+ } else if (absMom > 2) {
377
+ return getMomentumLog(momentum, zscore);
378
+ } else {
379
+ return getScanningLog(true);
380
+ }
381
+ }
382
+
383
+ module.exports = {
384
+ getMarketBiasLog,
385
+ getMomentumLog,
386
+ getMeanReversionLog,
387
+ getSignalLog,
388
+ getEntryLog,
389
+ getExitLog,
390
+ getVolatilityLog,
391
+ getScanningLog,
392
+ getPositionUpdateLog,
393
+ getMarketStateLog,
394
+ };
@@ -102,7 +102,11 @@ class AlgoUI {
102
102
  this.config = config;
103
103
  this.W = 96; // Fixed width
104
104
  this.logs = [];
105
- this.maxLogs = 45; // Max visible logs
105
+ // Calculate maxLogs based on terminal height
106
+ // Header (logo + titles): ~12 lines, Stats: ~12 lines, Log header: 2 lines, Bottom: 1 line = 27 fixed
107
+ const terminalHeight = process.stdout.rows || 50;
108
+ const fixedLines = 27;
109
+ this.maxLogs = Math.max(10, terminalHeight - fixedLines); // Min 10 logs
106
110
  this.spinnerFrame = 0;
107
111
  this.firstDraw = true;
108
112
  this.isDrawing = false;