hedgequantx 2.9.50 → 2.9.52

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.50",
3
+ "version": "2.9.52",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -0,0 +1,419 @@
1
+ /**
2
+ * =============================================================================
3
+ * Smart Logging System
4
+ * =============================================================================
5
+ * Non-repetitive, contextual, varied log messages
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 ZONE_APPROACH_MESSAGES = [
128
+ 'Approaching key level',
129
+ 'Zone test incoming',
130
+ 'Near decision point',
131
+ 'Level approach detected',
132
+ 'Key zone in range',
133
+ ];
134
+
135
+ const ZONE_CONFIRMED_MESSAGES = [
136
+ 'Zone confirmation',
137
+ 'Level validated',
138
+ 'Support/resistance active',
139
+ 'Zone reaction detected',
140
+ 'Level holding',
141
+ ];
142
+
143
+ const HIGH_VOLATILITY_MESSAGES = [
144
+ 'Volatility elevated',
145
+ 'High ATR detected',
146
+ 'Increased price range',
147
+ 'Market volatile',
148
+ 'Wide swings detected',
149
+ ];
150
+
151
+ const LOW_VOLATILITY_MESSAGES = [
152
+ 'Low volatility',
153
+ 'Tight range detected',
154
+ 'Compressed price action',
155
+ 'Market quiet',
156
+ 'Narrow swings',
157
+ ];
158
+
159
+ // =============================================================================
160
+ // MESSAGE POOLS - Risk
161
+ // =============================================================================
162
+
163
+ const RISK_PASSED_MESSAGES = [
164
+ 'Risk check passed',
165
+ 'Trade approved',
166
+ 'Within risk limits',
167
+ 'Risk validated',
168
+ 'Clear to trade',
169
+ ];
170
+
171
+ const RISK_BLOCKED_MESSAGES = [
172
+ 'Risk limit reached',
173
+ 'Trade blocked',
174
+ 'Exceeds risk threshold',
175
+ 'Risk rejected',
176
+ 'Waiting for conditions',
177
+ ];
178
+
179
+ // =============================================================================
180
+ // MESSAGE POOLS - Status
181
+ // =============================================================================
182
+
183
+ const SCANNING_MESSAGES = [
184
+ 'Scanning market...',
185
+ 'Analyzing flow...',
186
+ 'Monitoring structure...',
187
+ 'Watching for setups...',
188
+ 'Evaluating conditions...',
189
+ ];
190
+
191
+ const WAITING_MESSAGES = [
192
+ 'Waiting for confirmation...',
193
+ 'Pending trigger...',
194
+ 'Standby mode...',
195
+ 'Awaiting signal...',
196
+ 'Ready to act...',
197
+ ];
198
+
199
+ // =============================================================================
200
+ // MESSAGE POOLS - Tick Flow
201
+ // =============================================================================
202
+
203
+ const TICK_FLOW_MESSAGES = [
204
+ 'Processing tick data',
205
+ 'Market data flowing',
206
+ 'Live feed active',
207
+ 'Tick stream healthy',
208
+ 'Data streaming',
209
+ ];
210
+
211
+ const BUILDING_BARS_MESSAGES = [
212
+ 'Building price bars',
213
+ 'Aggregating ticks',
214
+ 'Forming candles',
215
+ 'Bar construction',
216
+ 'Chart building',
217
+ ];
218
+
219
+ const MODEL_ANALYSIS_MESSAGES = [
220
+ 'Running models',
221
+ 'Analyzing patterns',
222
+ 'Computing signals',
223
+ 'Model evaluation',
224
+ 'Strategy analysis',
225
+ ];
226
+
227
+ // =============================================================================
228
+ // SMART LOG GENERATORS
229
+ // =============================================================================
230
+
231
+ /**
232
+ * Get a market bias log
233
+ */
234
+ function getMarketBiasLog(direction, delta, buyPressure) {
235
+ let pool;
236
+ switch (direction) {
237
+ case 'LONG': pool = LONG_BIAS_MESSAGES; break;
238
+ case 'SHORT': pool = SHORT_BIAS_MESSAGES; break;
239
+ default: pool = FLAT_BIAS_MESSAGES;
240
+ }
241
+
242
+ const message = getVariedMessage(`bias_${direction}`, pool, `${direction} bias`);
243
+ const arrow = direction === 'LONG' ? '▲' : direction === 'SHORT' ? '▼' : '=';
244
+
245
+ let details;
246
+ if (delta !== undefined && buyPressure !== undefined) {
247
+ details = `${arrow} delta: ${delta > 0 ? '+' : ''}${delta} | buy: ${buyPressure.toFixed(0)}%`;
248
+ }
249
+
250
+ return { message, details };
251
+ }
252
+
253
+ /**
254
+ * Get a signal log
255
+ */
256
+ function getSignalLog(direction, symbol, confidence, strategy) {
257
+ const pool = direction === 'LONG' ? SIGNAL_LONG_MESSAGES : SIGNAL_SHORT_MESSAGES;
258
+ const message = getVariedMessage(`signal_${direction}`, pool, `${direction} signal`);
259
+ const arrow = direction === 'LONG' ? '▲' : '▼';
260
+
261
+ return {
262
+ message: `${arrow} ${message}`,
263
+ details: `${symbol} | ${confidence.toFixed(0)}% | ${strategy}`,
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Get an entry log
269
+ */
270
+ function getEntryLog(direction, symbol, size, price) {
271
+ const pool = direction === 'LONG' ? ENTRY_LONG_MESSAGES : ENTRY_SHORT_MESSAGES;
272
+ const message = getVariedMessage(`entry_${direction}`, pool, `${direction} entry`);
273
+ const arrow = direction === 'LONG' ? '▲' : '▼';
274
+
275
+ return {
276
+ message: `${arrow} ${message}`,
277
+ details: `${size}x ${symbol} @ ${price.toFixed(2)}`,
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Get an exit log
283
+ */
284
+ function getExitLog(isProfit, symbol, size, price, pnl) {
285
+ const pool = isProfit ? EXIT_PROFIT_MESSAGES : EXIT_LOSS_MESSAGES;
286
+ const category = isProfit ? 'exit_profit' : 'exit_loss';
287
+ const message = getVariedMessage(category, pool, isProfit ? 'Profit taken' : 'Loss taken');
288
+ const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
289
+
290
+ return {
291
+ message,
292
+ details: `${size}x ${symbol} @ ${price.toFixed(2)} | ${pnlStr}`,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Get a zone approach log
298
+ */
299
+ function getZoneApproachLog(zoneType, level) {
300
+ const message = getVariedMessage('zone_approach', ZONE_APPROACH_MESSAGES, 'Zone approach');
301
+ return { message, details: `${zoneType} @ ${level.toFixed(2)}` };
302
+ }
303
+
304
+ /**
305
+ * Get a zone confirmation log
306
+ */
307
+ function getZoneConfirmationLog(zoneType, level) {
308
+ const message = getVariedMessage('zone_confirm', ZONE_CONFIRMED_MESSAGES, 'Zone confirmed');
309
+ return { message, details: `${zoneType} @ ${level.toFixed(2)}` };
310
+ }
311
+
312
+ /**
313
+ * Get a volatility log
314
+ */
315
+ function getVolatilityLog(isHigh, atr) {
316
+ const pool = isHigh ? HIGH_VOLATILITY_MESSAGES : LOW_VOLATILITY_MESSAGES;
317
+ const category = isHigh ? 'vol_high' : 'vol_low';
318
+ const message = getVariedMessage(category, pool, isHigh ? 'High volatility' : 'Low volatility');
319
+ return { message, details: atr ? `ATR: ${atr.toFixed(2)}` : undefined };
320
+ }
321
+
322
+ /**
323
+ * Get a risk check log
324
+ */
325
+ function getRiskCheckLog(passed, reason) {
326
+ const pool = passed ? RISK_PASSED_MESSAGES : RISK_BLOCKED_MESSAGES;
327
+ const category = passed ? 'risk_pass' : 'risk_block';
328
+ const message = getVariedMessage(category, pool, passed ? 'Passed' : 'Blocked');
329
+ return { message, details: reason };
330
+ }
331
+
332
+ /**
333
+ * Get a scanning/waiting log
334
+ */
335
+ function getScanningLog(isScanning = true) {
336
+ const pool = isScanning ? SCANNING_MESSAGES : WAITING_MESSAGES;
337
+ const category = isScanning ? 'scanning' : 'waiting';
338
+ const message = getVariedMessage(category, pool, isScanning ? 'Scanning...' : 'Waiting...');
339
+ return { message };
340
+ }
341
+
342
+ /**
343
+ * Get tick flow log
344
+ */
345
+ function getTickFlowLog(tickCount, ticksPerSecond) {
346
+ const message = getVariedMessage('tick_flow', TICK_FLOW_MESSAGES, 'Tick flow');
347
+ return { message, details: `#${tickCount} | ${ticksPerSecond}/sec` };
348
+ }
349
+
350
+ /**
351
+ * Get building bars log
352
+ */
353
+ function getBuildingBarsLog(barCount) {
354
+ const message = getVariedMessage('building_bars', BUILDING_BARS_MESSAGES, 'Building bars');
355
+ return { message, details: `${barCount} bars` };
356
+ }
357
+
358
+ /**
359
+ * Get model analysis log
360
+ */
361
+ function getModelAnalysisLog(modelValues) {
362
+ const message = getVariedMessage('model_analysis', MODEL_ANALYSIS_MESSAGES, 'Analyzing');
363
+ const details = modelValues
364
+ ? `Z:${modelValues.zscore} | VPIN:${modelValues.vpin} | OFI:${modelValues.ofi}`
365
+ : undefined;
366
+ return { message, details };
367
+ }
368
+
369
+ /**
370
+ * Get position update log with varied messaging
371
+ */
372
+ function getPositionUpdateLog(side, size, unrealizedPnL, distanceToStop, distanceToTarget, holdTime) {
373
+ const arrow = side === 'LONG' ? '▲' : '▼';
374
+ const pnlStr = unrealizedPnL >= 0 ? `+$${unrealizedPnL.toFixed(2)}` : `-$${Math.abs(unrealizedPnL).toFixed(2)}`;
375
+
376
+ // Vary the message based on P&L status
377
+ let prefix;
378
+ if (unrealizedPnL > 0 && distanceToTarget < distanceToStop) {
379
+ const targetPct = Math.round((1 - distanceToTarget / (distanceToStop + distanceToTarget)) * 100);
380
+ prefix = targetPct > 75 ? 'Near target' : targetPct > 50 ? 'Running profit' : 'In profit';
381
+ } else if (unrealizedPnL < 0 && distanceToStop < distanceToTarget) {
382
+ const risk = Math.round((1 - distanceToStop / (distanceToStop + distanceToTarget)) * 100);
383
+ prefix = risk > 75 ? 'Stop risk' : risk > 50 ? 'Underwater' : 'Managing loss';
384
+ } else if (Math.abs(unrealizedPnL) < 5) {
385
+ prefix = 'Near entry';
386
+ } else {
387
+ prefix = unrealizedPnL > 0 ? 'In profit' : 'Managing';
388
+ }
389
+
390
+ return {
391
+ message: `${arrow} ${side} ${size}x`,
392
+ details: `${prefix}: ${pnlStr} | SL: ${distanceToStop.toFixed(0)}t | TP: ${distanceToTarget.toFixed(0)}t | ${holdTime}s`,
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Get price change log
398
+ */
399
+ function getPriceChangeLog(direction, price, change) {
400
+ const arrow = direction === 'UP' ? '▲' : '▼';
401
+ return { message: `${arrow} ${price.toFixed(2)}`, details: `${direction} ${change.toFixed(2)}` };
402
+ }
403
+
404
+ module.exports = {
405
+ getMarketBiasLog,
406
+ getSignalLog,
407
+ getEntryLog,
408
+ getExitLog,
409
+ getZoneApproachLog,
410
+ getZoneConfirmationLog,
411
+ getVolatilityLog,
412
+ getRiskCheckLog,
413
+ getScanningLog,
414
+ getTickFlowLog,
415
+ getBuildingBarsLog,
416
+ getModelAnalysisLog,
417
+ getPositionUpdateLog,
418
+ getPriceChangeLog,
419
+ };
@@ -9,6 +9,7 @@ const { AlgoUI, renderSessionSummary } = require('./ui');
9
9
  const { loadStrategy } = require('../../lib/m');
10
10
  const { MarketDataFeed } = require('../../lib/data');
11
11
  const { SupervisionEngine } = require('../../services/ai-supervision');
12
+ const smartLogs = require('../../lib/smart-logs');
12
13
 
13
14
  /**
14
15
  * Execute algo strategy with market data
@@ -37,7 +38,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
37
38
  const accountName = showName
38
39
  ? (account.accountName || account.rithmicAccountId || account.accountId)
39
40
  : 'HQX *****';
40
- const symbolName = contract.name;
41
+ const symbolName = contract.name; // Display name: "Micro E-mini S&P 500"
42
+ const symbolCode = contract.symbol || contract.id; // Rithmic symbol: "MESH6"
41
43
  const contractId = contract.id;
42
44
  const tickSize = contract.tickSize || 0.25;
43
45
 
@@ -93,18 +95,24 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
93
95
 
94
96
  // Handle strategy signals
95
97
  strategy.on('signal', async (signal) => {
96
- ui.addLog('info', `SIGNAL DETECTED: ${signal.direction?.toUpperCase()}`);
98
+ const dir = signal.direction?.toUpperCase() || 'UNKNOWN';
99
+ const signalLog = smartLogs.getSignalLog(dir, symbolCode, (signal.confidence || 0) * 100, strategyName);
100
+ ui.addLog('info', `${signalLog.message}`);
101
+ ui.addLog('info', signalLog.details);
97
102
 
98
103
  if (!running) {
99
- ui.addLog('info', 'Signal ignored: not running');
104
+ const riskLog = smartLogs.getRiskCheckLog(false, 'Algo stopped');
105
+ ui.addLog('info', riskLog.message);
100
106
  return;
101
107
  }
102
108
  if (pendingOrder) {
103
- ui.addLog('info', 'Signal ignored: order pending');
109
+ const riskLog = smartLogs.getRiskCheckLog(false, 'Order pending');
110
+ ui.addLog('info', riskLog.message);
104
111
  return;
105
112
  }
106
113
  if (currentPosition !== 0) {
107
- ui.addLog('info', `Signal ignored: position open (${currentPosition})`);
114
+ const riskLog = smartLogs.getRiskCheckLog(false, `Position open (${currentPosition})`);
115
+ ui.addLog('info', riskLog.message);
108
116
  return;
109
117
  }
110
118
 
@@ -114,7 +122,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
114
122
  aiContext.recentSignals.push({ ...signal, timestamp: Date.now() });
115
123
  if (aiContext.recentSignals.length > 10) aiContext.recentSignals.shift();
116
124
 
117
- ui.addLog('info', `Signal: ${direction.toUpperCase()} @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
125
+ const riskLog = smartLogs.getRiskCheckLog(true, `${direction.toUpperCase()} @ ${entry.toFixed(2)}`);
126
+ ui.addLog('info', `${riskLog.message} - ${riskLog.details}`);
118
127
 
119
128
  // Multi-Agent AI Supervision
120
129
  if (supervisionEnabled && supervisionEngine) {
@@ -173,8 +182,9 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
173
182
  if (orderResult.success) {
174
183
  currentPosition = direction === 'long' ? orderSize : -orderSize;
175
184
  stats.trades++;
176
- ui.addLog('fill_' + (direction === 'long' ? 'buy' : 'sell'),
177
- `OPENED ${direction.toUpperCase()} ${orderSize}x @ market`);
185
+ const entryLog = smartLogs.getEntryLog(direction.toUpperCase(), symbolCode, orderSize, entry);
186
+ ui.addLog('fill_' + (direction === 'long' ? 'buy' : 'sell'), entryLog.message);
187
+ ui.addLog('info', entryLog.details);
178
188
 
179
189
  // Bracket orders
180
190
  if (stopLoss && takeProfit) {
@@ -203,6 +213,10 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
203
213
  let lastAsk = null;
204
214
  let ticksPerSecond = 0;
205
215
  let lastTickSecond = Math.floor(Date.now() / 1000);
216
+ let lastLogSecond = 0;
217
+ let buyVolume = 0;
218
+ let sellVolume = 0;
219
+ let barCount = 0;
206
220
 
207
221
  marketFeed.on('tick', (tick) => {
208
222
  tickCount++;
@@ -220,32 +234,69 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
220
234
  aiContext.recentTicks.push(tick);
221
235
  if (aiContext.recentTicks.length > aiContext.maxTicks) aiContext.recentTicks.shift();
222
236
 
223
- // Smart logs for tick flow
224
237
  const price = tick.price || tick.tradePrice;
225
238
  const bid = tick.bid || tick.bidPrice;
226
239
  const ask = tick.ask || tick.askPrice;
240
+ const volume = tick.volume || tick.size || 1;
241
+
242
+ // Track buy/sell volume
243
+ if (tick.side === 'buy' || tick.aggressor === 1) buyVolume += volume;
244
+ else if (tick.side === 'sell' || tick.aggressor === 2) sellVolume += volume;
245
+ else if (price && lastPrice) {
246
+ if (price > lastPrice) buyVolume += volume;
247
+ else if (price < lastPrice) sellVolume += volume;
248
+ }
227
249
 
228
250
  // Log first tick
229
251
  if (tickCount === 1) {
230
- ui.addLog('info', `First tick received @ ${price?.toFixed(2) || 'N/A'}`);
231
- ui.addLog('info', `Tick type: ${tick.type || 'unknown'}`);
252
+ ui.addLog('connected', `First tick @ ${price?.toFixed(2) || 'N/A'}`);
232
253
  }
233
254
 
234
- // Log price changes
235
- if (price && lastPrice && price !== lastPrice) {
236
- const direction = price > lastPrice ? 'UP' : 'DOWN';
237
- const change = Math.abs(price - lastPrice).toFixed(2);
238
- if (tickCount <= 10 || tickCount % 50 === 0) {
239
- ui.addLog('info', `Price ${direction} ${change} -> ${price.toFixed(2)}`);
255
+ // === SMART LOGS EVERY SECOND ===
256
+ if (currentSecond !== lastLogSecond && tickCount > 1) {
257
+ lastLogSecond = currentSecond;
258
+
259
+ const totalVol = buyVolume + sellVolume;
260
+ const buyPressure = totalVol > 0 ? (buyVolume / totalVol) * 100 : 50;
261
+ const delta = buyVolume - sellVolume;
262
+
263
+ // Determine market bias
264
+ let bias = 'FLAT';
265
+ if (buyPressure > 55) bias = 'LONG';
266
+ else if (buyPressure < 45) bias = 'SHORT';
267
+
268
+ // Get smart log for market bias
269
+ const biasLog = smartLogs.getMarketBiasLog(bias, delta, buyPressure);
270
+ ui.addLog('info', `${biasLog.message} ${biasLog.details || ''}`);
271
+
272
+ // Get model values if available
273
+ const modelValues = strategy.getModelValues?.(contractId);
274
+ if (modelValues) {
275
+ barCount = modelValues.bars || barCount;
276
+ if (barCount >= 50) {
277
+ const modelLog = smartLogs.getModelAnalysisLog(modelValues);
278
+ ui.addLog('info', `${modelLog.message} ${modelLog.details || ''}`);
279
+ } else {
280
+ const barLog = smartLogs.getBuildingBarsLog(barCount);
281
+ ui.addLog('info', `${barLog.message} (${barLog.details})`);
282
+ }
240
283
  }
241
- }
242
-
243
- // Log bid/ask spread
244
- if (bid && ask && (bid !== lastBid || ask !== lastAsk)) {
245
- const spread = (ask - bid).toFixed(2);
246
- if (tickCount <= 5) {
247
- ui.addLog('info', `Spread: ${spread} (Bid: ${bid.toFixed(2)} / Ask: ${ask.toFixed(2)})`);
284
+
285
+ // Scanning log every 3 seconds
286
+ if (currentSecond % 3 === 0 && currentPosition === 0) {
287
+ const scanLog = smartLogs.getScanningLog(true);
288
+ ui.addLog('info', scanLog.message);
289
+ }
290
+
291
+ // Tick flow log every 5 seconds
292
+ if (currentSecond % 5 === 0) {
293
+ const tickLog = smartLogs.getTickFlowLog(tickCount, ticksPerSecond);
294
+ ui.addLog('info', `${tickLog.message} ${tickLog.details}`);
248
295
  }
296
+
297
+ // Reset volume counters
298
+ buyVolume = 0;
299
+ sellVolume = 0;
249
300
  }
250
301
 
251
302
  lastPrice = price;
@@ -255,19 +306,12 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
255
306
  strategy.processTick({
256
307
  contractId: tick.contractId || contractId,
257
308
  price: price, bid: bid, ask: ask,
258
- volume: tick.volume || tick.size || 1,
309
+ volume: volume,
259
310
  side: tick.side || tick.lastTradeSide || 'unknown',
260
311
  timestamp: tick.timestamp || Date.now()
261
312
  });
262
313
 
263
314
  stats.latency = Date.now() - latencyStart;
264
-
265
- // Periodic status logs
266
- if (tickCount === 10) ui.addLog('info', `Receiving ticks... (${ticksPerSecond}/sec)`);
267
- if (tickCount === 50) ui.addLog('info', `50 ticks processed, strategy analyzing...`);
268
- if (tickCount % 200 === 0) {
269
- ui.addLog('info', `Tick #${tickCount} @ ${price?.toFixed(2) || 'N/A'} | ${ticksPerSecond}/sec`);
270
- }
271
315
  });
272
316
 
273
317
  marketFeed.on('connected', () => {
@@ -287,7 +331,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
287
331
  throw new Error('Rithmic credentials not available');
288
332
  }
289
333
  await marketFeed.connect(rithmicCredentials);
290
- await marketFeed.subscribe(symbolName, contract.exchange || 'CME');
334
+ await marketFeed.subscribe(symbolCode, contract.exchange || 'CME');
335
+ ui.addLog('info', `Symbol code: ${symbolCode}`);
291
336
  } catch (e) {
292
337
  ui.addLog('error', `Failed to connect: ${e.message}`);
293
338
  }
@@ -14,6 +14,7 @@ const { getActiveAgentCount, getSupervisionConfig, getActiveAgents } = require('
14
14
  const { launchCopyTrading } = require('./copy-executor');
15
15
  const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../../services/ai-supervision');
16
16
  const { getAvailableStrategies } = require('../../lib/m');
17
+ const { getLastCopyTradingConfig, saveCopyTradingConfig } = require('../../services/algo-config');
17
18
 
18
19
  /**
19
20
  * Copy Trading Menu
@@ -54,6 +55,98 @@ const copyTradingMenu = async () => {
54
55
 
55
56
  spinner.succeed(`Found ${activeAccounts.length} active accounts`);
56
57
 
58
+ // Check for saved config
59
+ const lastConfig = getLastCopyTradingConfig();
60
+
61
+ if (lastConfig) {
62
+ // Try to find matching accounts
63
+ const matchingLead = activeAccounts.find(acc =>
64
+ acc.accountId === lastConfig.leadAccountId ||
65
+ acc.accountName === lastConfig.leadAccountName
66
+ );
67
+
68
+ if (matchingLead && lastConfig.followerAccountIds?.length > 0) {
69
+ const matchingFollowers = lastConfig.followerAccountIds
70
+ .map(id => activeAccounts.find(acc => acc.accountId === id || acc.accountName === id))
71
+ .filter(Boolean);
72
+
73
+ if (matchingFollowers.length > 0) {
74
+ console.log();
75
+ console.log(chalk.cyan(' Last configuration found:'));
76
+ console.log(chalk.gray(` Lead: ${lastConfig.leadAccountName} (${lastConfig.leadPropfirm})`));
77
+ console.log(chalk.gray(` Followers: ${matchingFollowers.length} account(s)`));
78
+ console.log(chalk.gray(` Symbol: ${lastConfig.symbol}`));
79
+ console.log(chalk.gray(` Strategy: ${lastConfig.strategyName}`));
80
+ console.log(chalk.gray(` Lead contracts: ${lastConfig.leadContracts} | Follower: ${lastConfig.followerContracts}`));
81
+ console.log(chalk.gray(` Target: $${lastConfig.dailyTarget} | Risk: $${lastConfig.maxRisk}`));
82
+ console.log();
83
+
84
+ const reuseConfig = await prompts.confirmPrompt('Use last configuration?', true);
85
+
86
+ if (reuseConfig) {
87
+ // Load contracts to find symbol
88
+ const leadService = matchingLead.service || connections.getServiceForAccount(matchingLead.accountId);
89
+ const contractsResult = await leadService.getContracts();
90
+ const contract = contractsResult.success
91
+ ? contractsResult.contracts.find(c => c.symbol === lastConfig.symbol)
92
+ : null;
93
+
94
+ // Find strategy
95
+ const strategies = getAvailableStrategies();
96
+ const strategy = strategies.find(s => s.id === lastConfig.strategyId);
97
+
98
+ if (contract && strategy) {
99
+ console.log(chalk.green(' ✓ Configuration loaded'));
100
+
101
+ // Check for AI Supervision
102
+ const agentCount = getActiveAgentCount();
103
+ let supervisionConfig = null;
104
+
105
+ if (agentCount > 0) {
106
+ console.log();
107
+ console.log(chalk.cyan(` ${agentCount} AI Agent(s) available for supervision`));
108
+ const enableAI = await prompts.confirmPrompt('Enable AI Supervision?', true);
109
+
110
+ if (enableAI) {
111
+ const agents = getActiveAgents();
112
+ const preflightResults = await runPreflightCheck(agents);
113
+ const lines = formatPreflightResults(preflightResults, 60);
114
+ for (const line of lines) console.log(line);
115
+ const summary = getPreflightSummary(preflightResults);
116
+ console.log();
117
+ console.log(` ${summary.text}`);
118
+
119
+ if (!preflightResults.success) {
120
+ console.log(chalk.red(' Cannot start algo - fix agent connections first.'));
121
+ await prompts.waitForEnter();
122
+ return;
123
+ }
124
+ supervisionConfig = getSupervisionConfig();
125
+ }
126
+ }
127
+
128
+ const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
129
+ if (!confirm) return;
130
+
131
+ await launchCopyTrading({
132
+ lead: { account: matchingLead, contracts: lastConfig.leadContracts },
133
+ followers: matchingFollowers.map(f => ({ account: f, contracts: lastConfig.followerContracts })),
134
+ contract,
135
+ strategy,
136
+ dailyTarget: lastConfig.dailyTarget,
137
+ maxRisk: lastConfig.maxRisk,
138
+ showNames: lastConfig.showNames,
139
+ supervisionConfig
140
+ });
141
+ return;
142
+ } else {
143
+ console.log(chalk.yellow(' Symbol or strategy no longer available, please reconfigure'));
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
57
150
  // Step 1: Select LEAD Account
58
151
  console.log();
59
152
  console.log(chalk.cyan.bold(' STEP 1: SELECT LEAD ACCOUNT'));
@@ -204,6 +297,22 @@ const copyTradingMenu = async () => {
204
297
  const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
205
298
  if (!confirm) return;
206
299
 
300
+ // Save config for next time
301
+ saveCopyTradingConfig({
302
+ leadAccountId: leadAccount.accountId || leadAccount.rithmicAccountId,
303
+ leadAccountName: leadAccount.accountName || leadAccount.rithmicAccountId || leadAccount.accountId,
304
+ leadPropfirm: leadAccount.propfirm || leadAccount.platform || 'Unknown',
305
+ followerAccountIds: followers.map(f => f.accountId || f.rithmicAccountId),
306
+ symbol: contract.symbol,
307
+ strategyId: strategy.id,
308
+ strategyName: strategy.name,
309
+ leadContracts,
310
+ followerContracts,
311
+ dailyTarget,
312
+ maxRisk,
313
+ showNames
314
+ });
315
+
207
316
  await launchCopyTrading({
208
317
  lead: { account: leadAccount, contracts: leadContracts },
209
318
  followers: followers.map(f => ({ account: f, contracts: followerContracts })),
@@ -13,6 +13,7 @@ const { executeAlgo } = require('./algo-executor');
13
13
  const { getActiveAgentCount, getSupervisionConfig, getActiveAgents } = require('../ai-agents');
14
14
  const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../../services/ai-supervision');
15
15
  const { getAvailableStrategies } = require('../../lib/m');
16
+ const { getLastOneAccountConfig, saveOneAccountConfig } = require('../../services/algo-config');
16
17
 
17
18
 
18
19
 
@@ -54,37 +55,112 @@ const oneAccountMenu = async (service) => {
54
55
 
55
56
  spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
56
57
 
57
- // Select account - display RAW API fields
58
- const options = activeAccounts.map(acc => {
59
- // Use what API returns: rithmicAccountId or accountName for Rithmic
60
- const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
61
- const balance = acc.balance !== null && acc.balance !== undefined
62
- ? ` - $${acc.balance.toLocaleString()}`
63
- : '';
64
- return {
65
- label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
66
- value: acc
67
- };
68
- });
69
- options.push({ label: '< Back', value: 'back' });
70
-
71
- const selectedAccount = await prompts.selectOption('Select Account:', options);
72
- if (!selectedAccount || selectedAccount === 'back') return;
73
-
74
- // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
75
- const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
76
-
77
- // Select symbol
78
- const contract = await selectSymbol(accountService, selectedAccount);
79
- if (!contract) return;
80
-
81
- // Select strategy
82
- const strategy = await selectStrategy();
83
- if (!strategy) return;
58
+ // Check for saved config
59
+ const lastConfig = getLastOneAccountConfig();
60
+ let selectedAccount = null;
61
+ let contract = null;
62
+ let strategy = null;
63
+ let config = null;
64
+ let accountService = null;
65
+
66
+ if (lastConfig) {
67
+ // Try to find matching account and offer to reuse config
68
+ const matchingAccount = activeAccounts.find(acc =>
69
+ acc.accountId === lastConfig.accountId ||
70
+ acc.rithmicAccountId === lastConfig.accountId ||
71
+ acc.accountName === lastConfig.accountName
72
+ );
73
+
74
+ if (matchingAccount) {
75
+ console.log();
76
+ console.log(chalk.cyan(' Last configuration found:'));
77
+ console.log(chalk.gray(` Account: ${lastConfig.accountName} (${lastConfig.propfirm})`));
78
+ console.log(chalk.gray(` Symbol: ${lastConfig.symbol}`));
79
+ console.log(chalk.gray(` Strategy: ${lastConfig.strategyName}`));
80
+ console.log(chalk.gray(` Contracts: ${lastConfig.contracts} | Target: $${lastConfig.dailyTarget} | Risk: $${lastConfig.maxRisk}`));
81
+ console.log();
82
+
83
+ const reuseConfig = await prompts.confirmPrompt('Use last configuration?', true);
84
+
85
+ if (reuseConfig) {
86
+ selectedAccount = matchingAccount;
87
+ accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
88
+
89
+ // Load contracts to find the saved symbol
90
+ const contractsResult = await accountService.getContracts();
91
+ if (contractsResult.success) {
92
+ contract = contractsResult.contracts.find(c => c.symbol === lastConfig.symbol);
93
+ }
94
+
95
+ // Find strategy
96
+ const strategies = getAvailableStrategies();
97
+ strategy = strategies.find(s => s.id === lastConfig.strategyId);
98
+
99
+ // Restore config
100
+ if (contract && strategy) {
101
+ config = {
102
+ contracts: lastConfig.contracts,
103
+ dailyTarget: lastConfig.dailyTarget,
104
+ maxRisk: lastConfig.maxRisk,
105
+ showName: lastConfig.showName
106
+ };
107
+ console.log(chalk.green(' ✓ Configuration loaded'));
108
+ } else {
109
+ console.log(chalk.yellow(' Symbol or strategy no longer available, please reconfigure'));
110
+ selectedAccount = null;
111
+ }
112
+ }
113
+ }
114
+ }
84
115
 
85
- // Configure algo
86
- const config = await configureAlgo(selectedAccount, contract, strategy);
87
- if (!config) return;
116
+ // If no saved config used, go through normal selection
117
+ if (!selectedAccount) {
118
+ // Select account - display RAW API fields
119
+ const options = activeAccounts.map(acc => {
120
+ // Use what API returns: rithmicAccountId or accountName for Rithmic
121
+ const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
122
+ const balance = acc.balance !== null && acc.balance !== undefined
123
+ ? ` - $${acc.balance.toLocaleString()}`
124
+ : '';
125
+ return {
126
+ label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
127
+ value: acc
128
+ };
129
+ });
130
+ options.push({ label: '< Back', value: 'back' });
131
+
132
+ selectedAccount = await prompts.selectOption('Select Account:', options);
133
+ if (!selectedAccount || selectedAccount === 'back') return;
134
+
135
+ // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
136
+ accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
137
+
138
+ // Select symbol
139
+ contract = await selectSymbol(accountService, selectedAccount);
140
+ if (!contract) return;
141
+
142
+ // Select strategy
143
+ strategy = await selectStrategy();
144
+ if (!strategy) return;
145
+
146
+ // Configure algo
147
+ config = await configureAlgo(selectedAccount, contract, strategy);
148
+ if (!config) return;
149
+
150
+ // Save config for next time
151
+ saveOneAccountConfig({
152
+ accountId: selectedAccount.accountId || selectedAccount.rithmicAccountId,
153
+ accountName: selectedAccount.accountName || selectedAccount.rithmicAccountId || selectedAccount.accountId,
154
+ propfirm: selectedAccount.propfirm || selectedAccount.platform || 'Unknown',
155
+ symbol: contract.symbol,
156
+ strategyId: strategy.id,
157
+ strategyName: strategy.name,
158
+ contracts: config.contracts,
159
+ dailyTarget: config.dailyTarget,
160
+ maxRisk: config.maxRisk,
161
+ showName: config.showName
162
+ });
163
+ }
88
164
 
89
165
  // Check for AI Supervision BEFORE asking to start
90
166
  const agentCount = getActiveAgentCount();
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Algo Config Storage - Persist algo configuration between sessions
3
+ * Saves to ~/.hqx/algo-config.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.hqx');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'algo-config.json');
12
+
13
+ /**
14
+ * Ensure config directory exists
15
+ */
16
+ const ensureConfigDir = () => {
17
+ if (!fs.existsSync(CONFIG_DIR)) {
18
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
19
+ }
20
+ };
21
+
22
+ /**
23
+ * Load saved config
24
+ * @returns {Object|null} Saved config or null
25
+ */
26
+ const loadConfig = () => {
27
+ try {
28
+ if (fs.existsSync(CONFIG_FILE)) {
29
+ const data = fs.readFileSync(CONFIG_FILE, 'utf8');
30
+ return JSON.parse(data);
31
+ }
32
+ } catch (e) {
33
+ // Ignore errors, return null
34
+ }
35
+ return null;
36
+ };
37
+
38
+ /**
39
+ * Save config
40
+ * @param {Object} config - Config to save
41
+ */
42
+ const saveConfig = (config) => {
43
+ try {
44
+ ensureConfigDir();
45
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
46
+ } catch (e) {
47
+ // Ignore save errors
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Get last config for one-account mode
53
+ * @returns {Object|null}
54
+ */
55
+ const getLastOneAccountConfig = () => {
56
+ const config = loadConfig();
57
+ return config?.oneAccount || null;
58
+ };
59
+
60
+ /**
61
+ * Save one-account config
62
+ * @param {Object} data - { accountId, accountName, propfirm, symbol, strategyId, contracts, dailyTarget, maxRisk, showName }
63
+ */
64
+ const saveOneAccountConfig = (data) => {
65
+ const config = loadConfig() || {};
66
+ config.oneAccount = {
67
+ ...data,
68
+ savedAt: Date.now()
69
+ };
70
+ saveConfig(config);
71
+ };
72
+
73
+ /**
74
+ * Get last config for copy-trading mode
75
+ * @returns {Object|null}
76
+ */
77
+ const getLastCopyTradingConfig = () => {
78
+ const config = loadConfig();
79
+ return config?.copyTrading || null;
80
+ };
81
+
82
+ /**
83
+ * Save copy-trading config
84
+ * @param {Object} data - { masterAccountId, followerAccountIds, symbol, strategyId, contracts, dailyTarget, maxRisk }
85
+ */
86
+ const saveCopyTradingConfig = (data) => {
87
+ const config = loadConfig() || {};
88
+ config.copyTrading = {
89
+ ...data,
90
+ savedAt: Date.now()
91
+ };
92
+ saveConfig(config);
93
+ };
94
+
95
+ module.exports = {
96
+ loadConfig,
97
+ saveConfig,
98
+ getLastOneAccountConfig,
99
+ saveOneAccountConfig,
100
+ getLastCopyTradingConfig,
101
+ saveCopyTradingConfig,
102
+ };