hedgequantx 2.6.145 → 2.6.146

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.145",
3
+ "version": "2.6.146",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -32,7 +32,21 @@ const USE_HFT_STRATEGY = true;
32
32
  const aiService = require('../../services/ai');
33
33
  const StrategySupervisor = require('../../services/ai/strategy-supervisor');
34
34
 
35
-
35
+ /**
36
+ * Format price to avoid floating point errors
37
+ * Uses the tick size to round properly
38
+ * @param {number} price - Raw price
39
+ * @param {number} tickSize - Tick size (default 0.25)
40
+ * @returns {string} - Formatted price string
41
+ */
42
+ const formatPrice = (price, tickSize = 0.25) => {
43
+ if (price === null || price === undefined || isNaN(price)) return '--';
44
+ // Round to nearest tick, then format
45
+ const rounded = Math.round(price / tickSize) * tickSize;
46
+ // Determine decimal places from tick size
47
+ const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
48
+ return rounded.toFixed(decimals);
49
+ };
36
50
 
37
51
  /**
38
52
  * Check if service supports fast path (Rithmic direct)
@@ -456,8 +470,9 @@ const launchAlgo = async (service, account, contract, config) => {
456
470
  stats.entryLatencies.push(fillLatencyMs);
457
471
  stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
458
472
  const side = position.side === 0 ? 'LONG' : 'SHORT';
473
+ const priceStr = formatPrice(position.entryPrice, tickSize || 0.25);
459
474
  // Use 'filled' type for colored FILL icon
460
- ui.addLog('filled', `${side} ${position.size}x ${symbolName} @ ${parseFloat(position.entryPrice.toFixed(6))} | ${fillLatencyMs}ms`);
475
+ ui.addLog('filled', `${side} ${position.size}x ${symbolName} @ ${priceStr} | ${fillLatencyMs}ms`);
461
476
  });
462
477
 
463
478
  positionManager.on('exitFilled', ({ orderTag, exitPrice, pnlTicks, holdDurationMs }) => {
@@ -493,10 +508,12 @@ const launchAlgo = async (service, account, contract, config) => {
493
508
 
494
509
  if (pnlDollars >= 0) {
495
510
  stats.wins++;
496
- ui.addLog('win', `+$${pnlDollars.toFixed(2)} @ ${parseFloat(exitPrice.toFixed(6))} | ${holdSec}s`);
511
+ const priceStr = formatPrice(exitPrice, tickSize || 0.25);
512
+ ui.addLog('win', `+$${pnlDollars.toFixed(2)} @ ${priceStr} | ${holdSec}s`);
497
513
  } else {
498
514
  stats.losses++;
499
- ui.addLog('loss', `-$${Math.abs(pnlDollars).toFixed(2)} @ ${parseFloat(exitPrice.toFixed(6))} | ${holdSec}s`);
515
+ const priceStr = formatPrice(exitPrice, tickSize || 0.25);
516
+ ui.addLog('loss', `-$${Math.abs(pnlDollars).toFixed(2)} @ ${priceStr} | ${holdSec}s`);
500
517
  }
501
518
  } else {
502
519
  // Log with ticks only if tickValue unavailable
@@ -521,7 +538,8 @@ const launchAlgo = async (service, account, contract, config) => {
521
538
 
522
539
  positionManager.on('breakevenActivated', ({ orderTag, position, breakevenPrice, pnlTicks }) => {
523
540
  // Use 'be' type for yellow BE icon
524
- ui.addLog('be', `Breakeven @ ${parseFloat(breakevenPrice.toFixed(6))} | +${pnlTicks} ticks`)
541
+ const priceStr = formatPrice(breakevenPrice, tickSize || 0.25);
542
+ ui.addLog('be', `Breakeven @ ${priceStr} | +${pnlTicks} ticks`)
525
543
  });
526
544
 
527
545
  positionManager.on('exitOrderFired', ({ orderTag, exitReason, latencyMs }) => {
@@ -1557,7 +1575,8 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1557
1575
  stats.entryLatencies.push(fillLatencyMs);
1558
1576
  stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
1559
1577
  const side = position.side === 0 ? 'LONG' : 'SHORT';
1560
- ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${parseFloat(position.entryPrice.toFixed(6))} | ${fillLatencyMs}ms`);
1578
+ const priceStr = formatPrice(position.entryPrice, tickSize || 0.25);
1579
+ ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${priceStr} | ${fillLatencyMs}ms`);
1561
1580
  stats.symbolStats[symbolName].position = position.side === 0 ? position.size : -position.size;
1562
1581
  ui.render(stats);
1563
1582
  });
@@ -1597,11 +1616,13 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1597
1616
  if (pnlDollars >= 0) {
1598
1617
  stats.wins++;
1599
1618
  stats.symbolStats[symbolName].wins++;
1600
- ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${parseFloat(exitPrice.toFixed(6))} | ${holdSec}s`);
1619
+ const priceStr = formatPrice(exitPrice, tickSize || 0.25);
1620
+ ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${priceStr} | ${holdSec}s`);
1601
1621
  } else {
1602
1622
  stats.losses++;
1603
1623
  stats.symbolStats[symbolName].losses++;
1604
- ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${parseFloat(exitPrice.toFixed(6))} | ${holdSec}s`);
1624
+ const priceStr = formatPrice(exitPrice, tickSize || 0.25);
1625
+ ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${priceStr} | ${holdSec}s`);
1605
1626
  // Symbol can trade again - no disable, continue until Target/Risk reached
1606
1627
  }
1607
1628
  } else if (pnlTicks !== null) {
@@ -1632,7 +1653,8 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1632
1653
  });
1633
1654
 
1634
1655
  pm.on('breakevenActivated', ({ breakevenPrice, pnlTicks }) => {
1635
- ui.addLog('be', `[${symbolName}] BE @ ${parseFloat(breakevenPrice.toFixed(6))} | +${pnlTicks} ticks`);
1656
+ const priceStr = formatPrice(breakevenPrice, tickSize || 0.25);
1657
+ ui.addLog('be', `[${symbolName}] BE @ ${priceStr} | +${pnlTicks} ticks`);
1636
1658
  });
1637
1659
 
1638
1660
  // ═══════════════════════════════════════════════════════════════════════
@@ -2080,9 +2102,22 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
2080
2102
  await Promise.race([disconnectPromise, timeoutPromise]);
2081
2103
  } catch {}
2082
2104
 
2083
- // Cleanup keyboard
2105
+ // Cleanup keyboard FIRST - restore stdin to normal mode
2084
2106
  try { if (cleanupKeys) cleanupKeys(); } catch {}
2085
2107
 
2108
+ // Force stdin back to normal mode (critical for waitForEnter to work)
2109
+ try {
2110
+ if (process.stdin.isTTY) {
2111
+ process.stdin.setRawMode(false);
2112
+ }
2113
+ // Remove all listeners to prevent interference
2114
+ process.stdin.removeAllListeners('keypress');
2115
+ process.stdin.resume();
2116
+ } catch {}
2117
+
2118
+ // Small delay to let stdin settle
2119
+ await new Promise(r => setTimeout(r, 100));
2120
+
2086
2121
  // Calculate duration before closeLog
2087
2122
  const durationMs = Date.now() - stats.startTime;
2088
2123
  const hours = Math.floor(durationMs / 3600000);
@@ -2099,17 +2134,13 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
2099
2134
 
2100
2135
  try { ui.cleanup(); } catch {}
2101
2136
 
2102
- try {
2103
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
2104
- process.stdin.resume();
2105
- } catch {}
2106
-
2107
2137
  // ═══════════════════════════════════════════════════════════════════════════
2108
2138
  // SESSION SUMMARY (duration already calculated above)
2109
2139
  // ═══════════════════════════════════════════════════════════════════════════
2110
2140
  // Render multi-symbol summary with same style as single-symbol
2111
2141
  renderMultiSymbolSummary(stats, stopReason, stats.symbolStats);
2112
2142
 
2143
+ // Wait for user to press Enter before returning to menu
2113
2144
  await prompts.waitForEnter();
2114
2145
  };
2115
2146
 
@@ -1,401 +1,592 @@
1
1
  /**
2
2
  * =============================================================================
3
- * Smart Logging System - Non-repetitive, contextual, varied messages
3
+ * HQX INSTITUTIONAL SMART LOGGING SYSTEM
4
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
5
+ * HedgeFund-grade execution logs with full market microstructure context
6
+ *
7
+ * SHOWS:
8
+ * - Order Flow Imbalance (OFI) with directional bias
9
+ * - Cumulative Delta & Volume Profile
10
+ * - Z-Score deviation from fair value
11
+ * - Momentum vectors & acceleration
12
+ * - Liquidity depth & spread dynamics
13
+ * - Position Greeks: Ticks to SL/TP, R-multiple, hold time
14
+ * - Flow alignment score (position vs market flow)
15
+ * - Tick-by-tick execution latency
9
16
  */
10
17
 
11
18
  'use strict';
12
19
 
13
- // Track recently used messages to avoid repetition
20
+ // =============================================================================
21
+ // STATE TRACKING
22
+ // =============================================================================
23
+
14
24
  const recentMessages = new Map();
15
25
  const MAX_RECENT = 5;
16
26
 
17
- /**
18
- * Get a message from a pool, avoiding recent ones
19
- */
27
+ // Market microstructure state
28
+ const microstructure = {
29
+ ofi: 0, // Order Flow Imbalance (-1 to +1)
30
+ cumDelta: 0, // Cumulative Delta
31
+ delta: 0, // Current bar delta
32
+ zscore: 0, // Z-Score from VWAP/mean
33
+ momentum: 0, // Price momentum
34
+ acceleration: 0, // Momentum change rate
35
+ spread: 0, // Bid-ask spread in ticks
36
+ bidDepth: 0, // Bid side liquidity
37
+ askDepth: 0, // Ask side liquidity
38
+ imbalance: 0, // Book imbalance ratio
39
+ vwap: 0, // Volume Weighted Avg Price
40
+ poc: 0, // Point of Control
41
+ atr: 0, // Average True Range
42
+ tps: 0, // Ticks per second
43
+ buyVol: 0, // Buy volume
44
+ sellVol: 0, // Sell volume
45
+ lastUpdate: Date.now(),
46
+ };
47
+
48
+ // Position state for Greeks calculation
49
+ const position = {
50
+ side: null, // 'LONG' | 'SHORT'
51
+ symbol: '',
52
+ size: 0,
53
+ entryPrice: 0,
54
+ currentPrice: 0,
55
+ stopLoss: 0,
56
+ takeProfit: 0,
57
+ entryTime: 0,
58
+ unrealizedPnl: 0,
59
+ tickSize: 0.25,
60
+ tickValue: 12.50,
61
+ };
62
+
63
+ // =============================================================================
64
+ // HELPER FUNCTIONS
65
+ // =============================================================================
66
+
20
67
  function getVariedMessage(category, pool, defaultMsg) {
21
68
  const recent = recentMessages.get(category) || [];
22
-
23
- // Filter out recently used messages
24
69
  const available = pool.filter(msg => !recent.includes(msg));
25
-
26
- // If all messages were recently used, reset
27
70
  if (available.length === 0) {
28
71
  recentMessages.set(category, []);
29
72
  return pool[Math.floor(Math.random() * pool.length)] || defaultMsg;
30
73
  }
31
-
32
- // Pick a random available message
33
74
  const chosen = available[Math.floor(Math.random() * available.length)] || defaultMsg;
34
-
35
- // Track it
36
75
  recent.push(chosen);
37
76
  if (recent.length > MAX_RECENT) recent.shift();
38
77
  recentMessages.set(category, recent);
39
-
40
78
  return chosen;
41
79
  }
42
80
 
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
- ];
81
+ function formatNumber(n, decimals = 2) {
82
+ if (n === undefined || n === null || isNaN(n)) return '--';
83
+ const num = parseFloat(n);
84
+ const sign = num >= 0 ? '+' : '';
85
+ return sign + num.toFixed(decimals);
86
+ }
62
87
 
63
- const FLAT_BIAS_MESSAGES = [
64
- 'Market balanced',
65
- 'Two-way flow',
66
- 'Consolidation mode',
67
- 'No clear direction',
68
- 'Mixed signals',
69
- ];
88
+ function formatPnl(pnl) {
89
+ if (pnl === undefined || pnl === null) return '--';
90
+ return pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
91
+ }
70
92
 
71
93
  // =============================================================================
72
- // MESSAGE POOLS - Trading Events
94
+ // INSTITUTIONAL MESSAGE POOLS
73
95
  // =============================================================================
74
96
 
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',
97
+ // Flow Analysis - HF Grade
98
+ const FLOW_AGGRESSIVE_BUY = [
99
+ 'Aggressive lifting offers',
100
+ 'Heavy buy-side absorption',
101
+ 'Institutional accumulation',
102
+ 'Delta surge positive',
103
+ 'Bid stacking detected',
104
+ 'Buy programs sweeping',
81
105
  ];
82
106
 
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',
107
+ const FLOW_AGGRESSIVE_SELL = [
108
+ 'Aggressive hitting bids',
109
+ 'Heavy sell-side distribution',
110
+ 'Institutional liquidation',
111
+ 'Delta surge negative',
112
+ 'Offer stacking detected',
113
+ 'Sell programs sweeping',
89
114
  ];
90
115
 
91
- const ENTRY_LONG_MESSAGES = [
92
- 'Long position opened',
93
- 'Buy order filled',
94
- 'Entered long',
95
- 'Long initiated',
96
- 'Position: LONG',
116
+ const FLOW_BALANCED = [
117
+ 'Two-sided flow equilibrium',
118
+ 'Balanced book rotation',
119
+ 'Fair value discovery',
120
+ 'Neutral order flow',
121
+ 'Market making zone',
97
122
  ];
98
123
 
99
- const ENTRY_SHORT_MESSAGES = [
100
- 'Short position opened',
101
- 'Sell order filled',
102
- 'Entered short',
103
- 'Short initiated',
104
- 'Position: SHORT',
124
+ // Mean Reversion - HF Grade
125
+ const MR_OVERBOUGHT = [
126
+ 'Extended above VWAP band',
127
+ 'Z-score overbought extreme',
128
+ 'Mean reversion fade setup',
129
+ 'Exhaustion at upper band',
130
+ 'Stretched deviation short',
105
131
  ];
106
132
 
107
- const EXIT_PROFIT_MESSAGES = [
108
- 'Target reached',
109
- 'Profit taken',
110
- 'Winner closed',
111
- 'TP hit',
112
- 'Profit locked',
133
+ const MR_OVERSOLD = [
134
+ 'Extended below VWAP band',
135
+ 'Z-score oversold extreme',
136
+ 'Mean reversion bounce setup',
137
+ 'Capitulation at lower band',
138
+ 'Stretched deviation long',
113
139
  ];
114
140
 
115
- const EXIT_LOSS_MESSAGES = [
116
- 'Stop triggered',
117
- 'Loss taken',
118
- 'Loser closed',
119
- 'SL hit',
120
- 'Risk contained',
141
+ // Momentum - HF Grade
142
+ const MOM_ACCELERATING_UP = [
143
+ 'Momentum acceleration +',
144
+ 'Velocity vector bullish',
145
+ 'Trend thrust positive',
146
+ 'Breakout momentum confirmed',
147
+ 'Impulse wave up',
121
148
  ];
122
149
 
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',
150
+ const MOM_ACCELERATING_DOWN = [
151
+ 'Momentum acceleration -',
152
+ 'Velocity vector bearish',
153
+ 'Trend thrust negative',
154
+ 'Breakdown momentum confirmed',
155
+ 'Impulse wave down',
133
156
  ];
134
157
 
135
- const MOMENTUM_DOWN_MESSAGES = [
136
- 'Momentum building down',
137
- 'Price declining',
138
- 'Bearish momentum',
139
- 'Downward thrust',
140
- 'Selling momentum',
158
+ // Volatility - HF Grade
159
+ const VOL_EXPANSION = [
160
+ 'Volatility expansion cycle',
161
+ 'ATR breakout detected',
162
+ 'Range expansion phase',
163
+ 'Vol regime shift high',
164
+ 'Gamma event potential',
141
165
  ];
142
166
 
143
- const OVERBOUGHT_MESSAGES = [
144
- 'Overbought condition',
145
- 'Extended to upside',
146
- 'Mean reversion setup (short)',
147
- 'Exhaustion detected',
148
- 'Stretched high',
167
+ const VOL_COMPRESSION = [
168
+ 'Volatility compression cycle',
169
+ 'ATR contraction detected',
170
+ 'Range compression phase',
171
+ 'Vol regime shift low',
172
+ 'Coiling for breakout',
149
173
  ];
150
174
 
151
- const OVERSOLD_MESSAGES = [
152
- 'Oversold condition',
153
- 'Extended to downside',
154
- 'Mean reversion setup (long)',
155
- 'Capitulation detected',
156
- 'Stretched low',
175
+ // Position Status - HF Grade
176
+ const POS_PROFIT_RUNNING = [
177
+ 'P&L positive running',
178
+ 'Trade thesis confirmed',
179
+ 'Alpha capture in progress',
180
+ 'Edge realization phase',
181
+ 'Position working target',
157
182
  ];
158
183
 
159
- const HIGH_VOLATILITY_MESSAGES = [
160
- 'Volatility elevated',
161
- 'High ATR detected',
162
- 'Wide swings',
163
- 'Market volatile',
164
- 'Increased range',
184
+ const POS_LOSS_MANAGING = [
185
+ 'Adverse excursion mgmt',
186
+ 'Drawdown within params',
187
+ 'Risk budget consuming',
188
+ 'Position under pressure',
189
+ 'Defending stop level',
165
190
  ];
166
191
 
167
- const LOW_VOLATILITY_MESSAGES = [
168
- 'Low volatility',
169
- 'Tight range',
170
- 'Compressed action',
171
- 'Market quiet',
172
- 'Narrow swings',
192
+ const POS_NEAR_TARGET = [
193
+ 'Approaching profit target',
194
+ 'Near TP execution zone',
195
+ 'Exit liquidity scanning',
196
+ 'Target strike imminent',
197
+ 'Profit capture pending',
173
198
  ];
174
199
 
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...',
200
+ const POS_NEAR_STOP = [
201
+ 'Near stop execution zone',
202
+ 'SL liquidity scanning',
203
+ 'Risk threshold proximity',
204
+ 'Stop strike imminent',
205
+ 'Loss containment pending',
185
206
  ];
186
207
 
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',
208
+ // Scanning - HF Grade
209
+ const SCANNING_ACTIVE = [
210
+ 'Scanning microstructure',
211
+ 'Analyzing order book',
212
+ 'Evaluating flow signals',
213
+ 'Pattern recognition active',
214
+ 'Setup detection running',
209
215
  ];
210
216
 
211
217
  // =============================================================================
212
- // SMART LOG GENERATORS
218
+ // MAIN LOG GENERATORS - HF GRADE
213
219
  // =============================================================================
214
220
 
215
221
  /**
216
- * Get a market bias log
222
+ * Update microstructure state from strategy model values
217
223
  */
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)}`;
224
+ function updateMicrostructure(data) {
225
+ if (data.ofi !== undefined) microstructure.ofi = data.ofi;
226
+ if (data.delta !== undefined) microstructure.delta = data.delta;
227
+ if (data.cumDelta !== undefined) microstructure.cumDelta = data.cumDelta;
228
+ if (data.zscore !== undefined) microstructure.zscore = data.zscore;
229
+ if (data.momentum !== undefined) {
230
+ microstructure.acceleration = data.momentum - microstructure.momentum;
231
+ microstructure.momentum = data.momentum;
238
232
  }
239
-
240
- return { message, details };
233
+ if (data.spread !== undefined) microstructure.spread = data.spread;
234
+ if (data.atr !== undefined) microstructure.atr = data.atr;
235
+ if (data.tps !== undefined) microstructure.tps = data.tps;
236
+ if (data.buyVol !== undefined) microstructure.buyVol = data.buyVol;
237
+ if (data.sellVol !== undefined) microstructure.sellVol = data.sellVol;
238
+ if (data.vwap !== undefined) microstructure.vwap = data.vwap;
239
+ microstructure.lastUpdate = Date.now();
241
240
  }
242
241
 
243
242
  /**
244
- * Get a momentum log
243
+ * Update position state
245
244
  */
246
- function getMomentumLog(momentum, zscore) {
247
- // Handle undefined/NaN values
248
- const mom = typeof momentum === 'number' && !isNaN(momentum) ? momentum : 0;
249
- const z = typeof zscore === 'number' && !isNaN(zscore) ? zscore : 0;
250
-
251
- const isUp = mom > 0;
252
- const pool = isUp ? MOMENTUM_UP_MESSAGES : MOMENTUM_DOWN_MESSAGES;
253
- const category = isUp ? 'mom_up' : 'mom_down';
254
- const message = getVariedMessage(category, pool, isUp ? 'Momentum up' : 'Momentum down');
255
- const arrow = isUp ? '▲' : '▼';
256
-
257
- return {
258
- message: `${arrow} ${message}`,
259
- details: `Mom=${mom.toFixed(1)} | Z=${z.toFixed(2)}`,
260
- };
245
+ function updatePosition(data) {
246
+ if (data.side !== undefined) position.side = data.side;
247
+ if (data.symbol !== undefined) position.symbol = data.symbol;
248
+ if (data.size !== undefined) position.size = data.size;
249
+ if (data.entryPrice !== undefined) position.entryPrice = data.entryPrice;
250
+ if (data.currentPrice !== undefined) position.currentPrice = data.currentPrice;
251
+ if (data.stopLoss !== undefined) position.stopLoss = data.stopLoss;
252
+ if (data.takeProfit !== undefined) position.takeProfit = data.takeProfit;
253
+ if (data.entryTime !== undefined) position.entryTime = data.entryTime;
254
+ if (data.unrealizedPnl !== undefined) position.unrealizedPnl = data.unrealizedPnl;
255
+ if (data.tickSize !== undefined) position.tickSize = data.tickSize;
256
+ if (data.tickValue !== undefined) position.tickValue = data.tickValue;
261
257
  }
262
258
 
263
259
  /**
264
- * Get a mean reversion log
260
+ * Clear position state when flat
265
261
  */
266
- function getMeanReversionLog(zscore) {
267
- // Handle undefined/NaN values
268
- const z = typeof zscore === 'number' && !isNaN(zscore) ? zscore : 0;
269
-
270
- const isOverbought = z > 0;
271
- const pool = isOverbought ? OVERBOUGHT_MESSAGES : OVERSOLD_MESSAGES;
272
- const category = isOverbought ? 'mr_overbought' : 'mr_oversold';
273
- const message = getVariedMessage(category, pool, isOverbought ? 'Overbought' : 'Oversold');
274
- const arrow = isOverbought ? '▼' : '▲'; // Opposite - looking for reversal
275
-
276
- return {
277
- message: `${arrow} ${message}`,
278
- details: `Z-Score: ${z.toFixed(2)} | Looking for ${isOverbought ? 'SHORT' : 'LONG'}`,
279
- };
262
+ function clearPosition() {
263
+ position.side = null;
264
+ position.symbol = '';
265
+ position.size = 0;
266
+ position.entryPrice = 0;
267
+ position.currentPrice = 0;
268
+ position.stopLoss = 0;
269
+ position.takeProfit = 0;
270
+ position.entryTime = 0;
271
+ position.unrealizedPnl = 0;
280
272
  }
281
273
 
282
274
  /**
283
- * Get a signal log
275
+ * GET MARKET FLOW LOG - When NOT in position
276
+ * Shows: OFI | Delta | Z-Score | Momentum | Volume Ratio
284
277
  */
285
- function getSignalLog(direction, symbol, confidence, strategy) {
286
- const pool = direction === 'LONG' ? SIGNAL_LONG_MESSAGES : SIGNAL_SHORT_MESSAGES;
287
- const message = getVariedMessage(`signal_${direction}`, pool, `${direction} signal`);
288
- const arrow = direction === 'LONG' ? '▲' : '▼';
289
-
290
- return {
291
- message: `${arrow} ${message}`,
292
- details: `${symbol} | ${confidence.toFixed(0)}% | ${strategy}`,
293
- };
278
+ function getMarketFlowLog() {
279
+ const { ofi, delta, zscore, momentum, buyVol, sellVol, tps, atr } = microstructure;
280
+
281
+ const absOfi = Math.abs(ofi || 0);
282
+ const absZ = Math.abs(zscore || 0);
283
+ const absMom = Math.abs(momentum || 0);
284
+
285
+ // Determine primary signal
286
+ let pool, category, signal;
287
+
288
+ // Priority: Mean Reversion > OFI > Momentum
289
+ if (absZ > 2.0) {
290
+ pool = zscore > 0 ? MR_OVERBOUGHT : MR_OVERSOLD;
291
+ category = zscore > 0 ? 'mr_short' : 'mr_long';
292
+ signal = 'MEAN_REV';
293
+ } else if (absOfi > 0.4) {
294
+ pool = ofi > 0 ? FLOW_AGGRESSIVE_BUY : FLOW_AGGRESSIVE_SELL;
295
+ category = ofi > 0 ? 'flow_buy' : 'flow_sell';
296
+ signal = ofi > 0 ? 'BUY_FLOW' : 'SELL_FLOW';
297
+ } else if (absMom > 3) {
298
+ pool = momentum > 0 ? MOM_ACCELERATING_UP : MOM_ACCELERATING_DOWN;
299
+ category = momentum > 0 ? 'mom_up' : 'mom_down';
300
+ signal = momentum > 0 ? 'MOM_UP' : 'MOM_DOWN';
301
+ } else {
302
+ pool = SCANNING_ACTIVE;
303
+ category = 'scanning';
304
+ signal = 'SCANNING';
305
+ }
306
+
307
+ const message = getVariedMessage(category, pool, 'Analyzing...');
308
+
309
+ // Build HF-grade metrics string
310
+ const ofiStr = `OFI:${formatNumber(ofi, 2)}`;
311
+ const deltaStr = `Δ:${formatNumber(delta, 0)}`;
312
+ const zStr = `Z:${formatNumber(zscore, 2)}`;
313
+ const momStr = `Mom:${formatNumber(momentum, 1)}`;
314
+
315
+ // Volume ratio
316
+ const totalVol = (buyVol || 0) + (sellVol || 0);
317
+ const buyPct = totalVol > 0 ? Math.round((buyVol / totalVol) * 100) : 50;
318
+ const volStr = `B/S:${buyPct}/${100 - buyPct}`;
319
+
320
+ const details = `${ofiStr} | ${deltaStr} | ${zStr} | ${momStr} | ${volStr}`;
321
+
322
+ return { message, details, signal };
294
323
  }
295
324
 
296
325
  /**
297
- * Get an entry log
326
+ * GET POSITION CONTEXT LOG - When IN position
327
+ * Shows: Side | P&L | Ticks to SL/TP | Hold Time | R-Multiple | Flow Alignment
298
328
  */
299
- function getEntryLog(direction, symbol, size, price) {
300
- const pool = direction === 'LONG' ? ENTRY_LONG_MESSAGES : ENTRY_SHORT_MESSAGES;
301
- const message = getVariedMessage(`entry_${direction}`, pool, `${direction} entry`);
302
- const arrow = direction === 'LONG' ? '▲' : '▼';
303
-
304
- return {
305
- message: `${arrow} ${message}`,
306
- details: `${size}x ${symbol} @ ${price.toFixed(2)}`,
307
- };
329
+ function getPositionContextLog() {
330
+ const { side, size, entryPrice, currentPrice, stopLoss, takeProfit, entryTime, unrealizedPnl, tickSize, tickValue } = position;
331
+ const { ofi, momentum, delta } = microstructure;
332
+
333
+ if (!side) return getMarketFlowLog();
334
+
335
+ // Calculate position Greeks
336
+ const holdMs = Date.now() - (entryTime || Date.now());
337
+ const holdSec = Math.floor(holdMs / 1000);
338
+ const holdStr = holdSec >= 60 ? `${Math.floor(holdSec / 60)}m${holdSec % 60}s` : `${holdSec}s`;
339
+
340
+ // Ticks from entry
341
+ const ticksFromEntry = tickSize > 0
342
+ ? Math.round((currentPrice - entryPrice) / tickSize) * (side === 'LONG' ? 1 : -1)
343
+ : 0;
344
+
345
+ // Distance to SL/TP in ticks
346
+ let ticksToSL = 0, ticksToTP = 0, riskTicks = 0, rewardTicks = 0;
347
+ if (side === 'LONG' && stopLoss && takeProfit && tickSize > 0) {
348
+ ticksToSL = Math.round((currentPrice - stopLoss) / tickSize);
349
+ ticksToTP = Math.round((takeProfit - currentPrice) / tickSize);
350
+ riskTicks = Math.round((entryPrice - stopLoss) / tickSize);
351
+ rewardTicks = Math.round((takeProfit - entryPrice) / tickSize);
352
+ } else if (side === 'SHORT' && stopLoss && takeProfit && tickSize > 0) {
353
+ ticksToSL = Math.round((stopLoss - currentPrice) / tickSize);
354
+ ticksToTP = Math.round((currentPrice - takeProfit) / tickSize);
355
+ riskTicks = Math.round((stopLoss - entryPrice) / tickSize);
356
+ rewardTicks = Math.round((entryPrice - takeProfit) / tickSize);
357
+ }
358
+
359
+ // R-Multiple (how many R's of profit/loss)
360
+ const rMultiple = riskTicks > 0 ? (ticksFromEntry / riskTicks) : 0;
361
+
362
+ // Flow alignment score (-1 to +1)
363
+ let flowScore = 0;
364
+ if (side === 'LONG') {
365
+ flowScore = ((ofi || 0) + (momentum > 0 ? 0.3 : momentum < 0 ? -0.3 : 0) + (delta > 0 ? 0.2 : delta < 0 ? -0.2 : 0));
366
+ } else {
367
+ flowScore = (-(ofi || 0) + (momentum < 0 ? 0.3 : momentum > 0 ? -0.3 : 0) + (delta < 0 ? 0.2 : delta > 0 ? -0.2 : 0));
368
+ }
369
+ flowScore = Math.max(-1, Math.min(1, flowScore));
370
+
371
+ // Determine status message
372
+ let pool, category;
373
+ const pnl = unrealizedPnl || 0;
374
+
375
+ if (ticksToTP > 0 && ticksToTP <= 4) {
376
+ pool = POS_NEAR_TARGET;
377
+ category = 'near_tp';
378
+ } else if (ticksToSL > 0 && ticksToSL <= 4) {
379
+ pool = POS_NEAR_STOP;
380
+ category = 'near_sl';
381
+ } else if (pnl > 0) {
382
+ pool = POS_PROFIT_RUNNING;
383
+ category = 'pos_profit';
384
+ } else {
385
+ pool = POS_LOSS_MANAGING;
386
+ category = 'pos_loss';
387
+ }
388
+
389
+ const statusMsg = getVariedMessage(category, pool, pnl >= 0 ? 'Running profit' : 'Managing risk');
390
+
391
+ // Build message
392
+ const arrow = side === 'LONG' ? '▲' : '▼';
393
+ const message = `${arrow} ${side} ${size}x | ${statusMsg}`;
394
+
395
+ // Build HF-grade details
396
+ const pnlStr = formatPnl(pnl);
397
+ const ticksStr = ticksFromEntry >= 0 ? `+${ticksFromEntry}t` : `${ticksFromEntry}t`;
398
+ const slStr = ticksToSL > 0 ? `SL:${ticksToSL}t` : 'SL:--';
399
+ const tpStr = ticksToTP > 0 ? `TP:${ticksToTP}t` : 'TP:--';
400
+ const rStr = `R:${rMultiple >= 0 ? '+' : ''}${rMultiple.toFixed(2)}`;
401
+ const flowStr = `Flow:${flowScore >= 0 ? '+' : ''}${flowScore.toFixed(2)}`;
402
+
403
+ const details = `${pnlStr} (${ticksStr}) | ${slStr} ${tpStr} | ${rStr} | ${flowStr} | ${holdStr}`;
404
+
405
+ return { message, details };
308
406
  }
309
407
 
310
408
  /**
311
- * Get an exit log
409
+ * GET VOLATILITY LOG
410
+ * Shows: ATR | Volatility Regime | Range Context
312
411
  */
313
- function getExitLog(isProfit, symbol, size, price, pnl) {
314
- const pool = isProfit ? EXIT_PROFIT_MESSAGES : EXIT_LOSS_MESSAGES;
315
- const category = isProfit ? 'exit_profit' : 'exit_loss';
316
- const message = getVariedMessage(category, pool, isProfit ? 'Profit taken' : 'Loss taken');
317
- const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
318
-
319
- return {
320
- message,
321
- details: `${size}x ${symbol} @ ${price.toFixed(2)} | ${pnlStr}`,
322
- };
412
+ function getVolatilityLog() {
413
+ const { atr, tps } = microstructure;
414
+
415
+ const isHigh = tps > 40 || atr > 2.0;
416
+ const isLow = tps < 10 || atr < 0.5;
417
+
418
+ let pool, category;
419
+ if (isHigh) {
420
+ pool = VOL_EXPANSION;
421
+ category = 'vol_high';
422
+ } else if (isLow) {
423
+ pool = VOL_COMPRESSION;
424
+ category = 'vol_low';
425
+ } else {
426
+ return { message: 'Normal volatility regime', details: `ATR:${atr?.toFixed(2) || '--'} | ${tps || '--'} tps` };
427
+ }
428
+
429
+ const message = getVariedMessage(category, pool, isHigh ? 'Vol expansion' : 'Vol compression');
430
+ const details = `ATR:${atr?.toFixed(2) || '--'} | ${tps || '--'} ticks/sec`;
431
+
432
+ return { message, details };
323
433
  }
324
434
 
325
435
  /**
326
- * Get a volatility log
436
+ * GET FLOW ANALYSIS LOG - Detailed order flow breakdown
437
+ * Shows: OFI | Delta | Cumulative Delta | Volume Breakdown
327
438
  */
328
- function getVolatilityLog(isHigh, atr) {
329
- const pool = isHigh ? HIGH_VOLATILITY_MESSAGES : LOW_VOLATILITY_MESSAGES;
330
- const category = isHigh ? 'vol_high' : 'vol_low';
331
- const message = getVariedMessage(category, pool, isHigh ? 'High volatility' : 'Low volatility');
332
-
333
- return {
334
- message,
335
- details: atr ? `ATR: ${atr.toFixed(2)}` : undefined,
336
- };
439
+ function getFlowAnalysisLog() {
440
+ const { ofi, delta, cumDelta, buyVol, sellVol } = microstructure;
441
+
442
+ const absOfi = Math.abs(ofi || 0);
443
+ let pool, category;
444
+
445
+ if (absOfi > 0.5) {
446
+ pool = ofi > 0 ? FLOW_AGGRESSIVE_BUY : FLOW_AGGRESSIVE_SELL;
447
+ category = ofi > 0 ? 'flow_agg_buy' : 'flow_agg_sell';
448
+ } else if (absOfi > 0.2) {
449
+ pool = ofi > 0 ? FLOW_AGGRESSIVE_BUY : FLOW_AGGRESSIVE_SELL;
450
+ category = ofi > 0 ? 'flow_mod_buy' : 'flow_mod_sell';
451
+ } else {
452
+ pool = FLOW_BALANCED;
453
+ category = 'flow_balanced';
454
+ }
455
+
456
+ const message = getVariedMessage(category, pool, 'Analyzing flow');
457
+
458
+ // Volume breakdown
459
+ const totalVol = (buyVol || 0) + (sellVol || 0);
460
+ const buyPct = totalVol > 0 ? Math.round((buyVol / totalVol) * 100) : 50;
461
+
462
+ const ofiStr = `OFI:${formatNumber(ofi, 2)}`;
463
+ const deltaStr = `Δ:${formatNumber(delta, 0)}`;
464
+ const cumStr = `ΣΔ:${formatNumber(cumDelta, 0)}`;
465
+ const volStr = `Vol B/S:${buyPct}/${100 - buyPct}%`;
466
+
467
+ const details = `${ofiStr} | ${deltaStr} | ${cumStr} | ${volStr}`;
468
+
469
+ return { message, details };
337
470
  }
338
471
 
339
472
  /**
340
- * Get a scanning/waiting log
473
+ * GET SCANNING LOG
341
474
  */
342
- function getScanningLog(isScanning) {
343
- const pool = isScanning ? SCANNING_MESSAGES : WAITING_MESSAGES;
344
- const category = isScanning ? 'scanning' : 'waiting';
345
- const message = getVariedMessage(category, pool, isScanning ? 'Scanning...' : 'Waiting...');
346
-
475
+ function getScanningLog(isScanning = true) {
476
+ const pool = SCANNING_ACTIVE;
477
+ const message = getVariedMessage('scanning', pool, 'Scanning...');
347
478
  return { message, details: undefined };
348
479
  }
349
480
 
350
481
  /**
351
- * Get position update log with varied messaging
482
+ * MAIN ENTRY POINT: getSmartLog
483
+ * Automatically determines context and returns appropriate HF-grade log
352
484
  */
353
- function getPositionUpdateLog(side, size, unrealizedPnL, entryPrice, currentPrice, holdTime) {
354
- const arrow = side === 'LONG' ? '▲' : '▼';
355
- const pnlStr = unrealizedPnL >= 0 ? `+$${unrealizedPnL.toFixed(2)}` : `-$${Math.abs(unrealizedPnL).toFixed(2)}`;
356
- const isProfit = unrealizedPnL >= 0;
485
+ function getSmartLog(data = {}) {
486
+ // Update states if data provided
487
+ if (data.ofi !== undefined || data.delta !== undefined || data.zscore !== undefined) {
488
+ updateMicrostructure(data);
489
+ }
490
+ if (data.side !== undefined || data.entryPrice !== undefined) {
491
+ updatePosition(data);
492
+ }
357
493
 
358
- const pool = isProfit ? IN_POSITION_PROFIT_MESSAGES : IN_POSITION_LOSS_MESSAGES;
359
- const category = isProfit ? 'pos_profit' : 'pos_loss';
360
- const prefix = getVariedMessage(category, pool, isProfit ? 'In profit' : 'Managing');
361
-
362
- return {
363
- message: `${arrow} ${side} ${size}x`,
364
- details: `${prefix} ${pnlStr} @ ${currentPrice.toFixed(2)} | ${holdTime}s`,
365
- };
494
+ // Return appropriate log based on position state
495
+ if (data.inPosition || position.side) {
496
+ return getPositionContextLog();
497
+ } else {
498
+ return getMarketFlowLog();
499
+ }
366
500
  }
367
501
 
368
502
  /**
369
- * Get market state log based on model values
503
+ * GET MARKET STATE LOG - Legacy compatibility
504
+ * Wrapper around getMarketFlowLog
370
505
  */
371
506
  function getMarketStateLog(ofi, zscore, momentum, delta) {
372
- // Determine primary market state
373
- const absZ = Math.abs(zscore);
374
- const absOfi = Math.abs(ofi);
375
- const absMom = Math.abs(momentum);
376
-
377
- // Priority: Mean reversion > OFI pressure > Momentum
378
- if (absZ > 1.5) {
379
- return getMeanReversionLog(zscore);
380
- } else if (absOfi > 0.3) {
381
- const direction = ofi > 0 ? 'LONG' : 'SHORT';
382
- return getMarketBiasLog(direction, delta, ofi);
383
- } else if (absMom > 2) {
384
- return getMomentumLog(momentum, zscore);
385
- } else {
386
- return getScanningLog(true);
387
- }
507
+ updateMicrostructure({ ofi, zscore, momentum, delta });
508
+ return getMarketFlowLog();
388
509
  }
389
510
 
511
+ // =============================================================================
512
+ // LEGACY API - Backward Compatibility
513
+ // =============================================================================
514
+
515
+ const getMarketBiasLog = (direction, delta, ofi) => {
516
+ updateMicrostructure({ delta, ofi });
517
+ return getMarketFlowLog();
518
+ };
519
+
520
+ const getMomentumLog = (momentum, zscore) => {
521
+ updateMicrostructure({ momentum, zscore });
522
+ return getMarketFlowLog();
523
+ };
524
+
525
+ const getMeanReversionLog = (zscore) => {
526
+ updateMicrostructure({ zscore: zscore * 2.5 }); // Amplify to trigger MR message
527
+ return getMarketFlowLog();
528
+ };
529
+
530
+ const getPositionUpdateLog = (side, size, unrealizedPnL, entryPrice, currentPrice, holdTime) => {
531
+ updatePosition({ side, size, unrealizedPnl: unrealizedPnL, entryPrice, currentPrice, entryTime: Date.now() - holdTime * 1000 });
532
+ return getPositionContextLog();
533
+ };
534
+
535
+ const getSignalLog = (direction, symbol, confidence, strategy) => {
536
+ const pool = direction === 'LONG' ? FLOW_AGGRESSIVE_BUY : FLOW_AGGRESSIVE_SELL;
537
+ const message = getVariedMessage(`signal_${direction}`, pool, `${direction} signal`);
538
+ const arrow = direction === 'LONG' ? '▲' : '▼';
539
+ return {
540
+ message: `${arrow} ${message}`,
541
+ details: `${symbol} | Conf:${confidence.toFixed(0)}% | ${strategy}`,
542
+ };
543
+ };
544
+
545
+ const getEntryLog = (direction, symbol, size, price) => {
546
+ const arrow = direction === 'LONG' ? '▲' : '▼';
547
+ return {
548
+ message: `${arrow} ${direction} initiated`,
549
+ details: `${size}x ${symbol} @ ${price.toFixed(2)}`,
550
+ };
551
+ };
552
+
553
+ const getExitLog = (isProfit, symbol, size, price, pnl) => {
554
+ return {
555
+ message: isProfit ? 'Profit captured' : 'Loss contained',
556
+ details: `${size}x ${symbol} @ ${price.toFixed(2)} | ${formatPnl(pnl)}`,
557
+ };
558
+ };
559
+
560
+ const getVolatilityLog_legacy = (isHigh, atr) => {
561
+ updateMicrostructure({ atr });
562
+ return getVolatilityLog();
563
+ };
564
+
565
+ // =============================================================================
566
+ // EXPORTS
567
+ // =============================================================================
568
+
390
569
  module.exports = {
570
+ // HF-Grade API
571
+ getSmartLog,
572
+ getMarketFlowLog,
573
+ getPositionContextLog,
574
+ getFlowAnalysisLog,
575
+ getVolatilityLog,
576
+ getScanningLog,
577
+
578
+ // State management
579
+ updateMicrostructure,
580
+ updatePosition,
581
+ clearPosition,
582
+
583
+ // Legacy API (backward compatible)
584
+ getMarketStateLog,
391
585
  getMarketBiasLog,
392
586
  getMomentumLog,
393
587
  getMeanReversionLog,
588
+ getPositionUpdateLog,
394
589
  getSignalLog,
395
590
  getEntryLog,
396
591
  getExitLog,
397
- getVolatilityLog,
398
- getScanningLog,
399
- getPositionUpdateLog,
400
- getMarketStateLog,
401
592
  };