hedgequantx 2.9.229 → 2.9.231

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.
@@ -197,69 +197,94 @@ class HQXUltraScalpingStrategy extends EventEmitter {
197
197
  }
198
198
 
199
199
  /**
200
- * Emit status log with QUANT metrics
200
+ * Emit status log with QUANT metrics - shows exactly WHY we're not entering
201
+ * No repetition - only emits if message changed
201
202
  */
202
203
  _emitStatusLog(contractId, currentPrice) {
203
204
  const prices = this.priceBuffer.get(contractId) || [];
204
205
  const volumes = this.volumeBuffer.get(contractId) || [];
205
206
  const bars = this.barHistory.get(contractId) || [];
206
207
 
207
- if (prices.length < 20) return; // Not enough data yet
208
-
209
- // Compute current metrics
210
- const zscore = computeZScore(prices);
211
- const vpin = volumes.length >= 10 ? computeVPIN(volumes, this.vpinWindow) : 0;
212
- const ofi = bars.length >= 10 ? computeOrderFlowImbalance(bars, this.ofiLookback) : 0;
213
-
214
- // Determine market state
215
- const absZ = Math.abs(zscore);
216
- let zState = 'normal';
217
- if (absZ >= 2.0) zState = 'EXTREME';
218
- else if (absZ >= 1.5) zState = 'HIGH';
219
- else if (absZ >= 1.0) zState = 'building';
220
-
221
- // Determine direction bias
222
- let bias = 'neutral';
223
- if (zscore < -1.5 && ofi > 0.1) bias = 'LONG setup';
224
- else if (zscore > 1.5 && ofi < -0.1) bias = 'SHORT setup';
225
- else if (zscore < -1.0) bias = 'oversold';
226
- else if (zscore > 1.0) bias = 'overbought';
227
-
228
208
  // Extract symbol
229
- const sym = (contractId || '').replace(/[A-Z]\d+$/, '');
209
+ const sym = extractBaseSymbol(contractId);
210
+ const priceStr = currentPrice.toFixed(2);
230
211
 
231
- // Build message based on state
232
212
  let message;
233
- if (!this.tradingEnabled) {
234
- message = `[${sym}] ${currentPrice.toFixed(2)} | PAUSED (${this.lossStreak} losses) | Cooldown active`;
235
- } else if (absZ >= 2.0) {
236
- const dir = zscore < 0 ? 'LONG' : 'SHORT';
237
- const ofiPct = (Math.abs(ofi) * 100).toFixed(0);
238
- const ofiConfirm = (zscore < 0 && ofi > 0.15) || (zscore > 0 && ofi < -0.15);
239
- if (ofiConfirm) {
240
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1) ${zState} | ${dir} | OFI ${ofiPct}% confirms`;
241
- } else {
242
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1) ${zState} | ${dir} signal | OFI ${ofiPct}% pending`;
243
- }
244
- } else if (absZ >= 1.5) {
245
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1)}σ ${zState} | ${bias} | Monitoring`;
246
- } else if (absZ >= 1.0) {
247
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1)}σ ${zState} | Awaiting extremity`;
213
+ let state; // Used to detect state changes
214
+
215
+ // Not enough data yet
216
+ if (prices.length < 20) {
217
+ const pct = Math.round((prices.length / 50) * 100);
218
+ state = `warmup-${Math.floor(pct / 10) * 10}`;
219
+ message = `[${sym}] ${priceStr} | Warming up... ${prices.length}/50 bars (${pct}%)`;
220
+ } else if (bars.length < 50) {
221
+ const pct = Math.round((bars.length / 50) * 100);
222
+ state = `building-${Math.floor(pct / 10) * 10}`;
223
+ message = `[${sym}] ${priceStr} | Building history... ${bars.length}/50 bars (${pct}%)`;
248
224
  } else {
249
- // Normal state - show different context messages
225
+ // Compute current metrics
226
+ const zscore = computeZScore(prices);
227
+ const vpin = volumes.length >= 10 ? computeVPIN(volumes, this.vpinWindow) : 0;
228
+ const ofi = bars.length >= 10 ? computeOrderFlowImbalance(bars, this.ofiLookback) : 0;
229
+ const absZ = Math.abs(zscore);
230
+ const ofiPct = (ofi * 100).toFixed(0);
250
231
  const vpinPct = (vpin * 100).toFixed(0);
251
- const contexts = [
252
- `VPIN ${vpinPct}% | Mean reversion scan`,
253
- `OFI balanced | Price discovery`,
254
- `Z normalized | Statistical scan`,
255
- `Tick flow stable | Edge detection`,
256
- `Volatility normal | Alpha scan`,
257
- ];
258
- const ctx = contexts[Math.floor(Date.now() / 5000) % contexts.length];
259
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1) | ${ctx}`;
232
+ const zRounded = Math.round(zscore * 10) / 10; // Round to 0.1
233
+
234
+ // Check cooldown
235
+ const now = Date.now();
236
+ const timeSinceLastSignal = now - this.lastSignalTime;
237
+ const cooldownRemaining = Math.max(0, this.signalCooldownMs - timeSinceLastSignal);
238
+
239
+ // Trading disabled?
240
+ if (!this.tradingEnabled) {
241
+ state = 'paused';
242
+ message = `[${sym}] ${priceStr} | PAUSED - ${this.lossStreak} losses | Cooldown active`;
243
+ }
244
+ // In cooldown?
245
+ else if (cooldownRemaining > 0 && this.lastSignalTime > 0) {
246
+ const secs = Math.ceil(cooldownRemaining / 1000);
247
+ state = `cooldown-${secs}`;
248
+ message = `[${sym}] ${priceStr} | Cooldown ${secs}s | Z:${zRounded}σ OFI:${ofiPct}%`;
249
+ }
250
+ // VPIN toxic?
251
+ else if (vpin > this.vpinToxicThreshold) {
252
+ state = 'vpin-toxic';
253
+ message = `[${sym}] ${priceStr} | VPIN toxic ${vpinPct}% > 70% | No entry - informed traders active`;
254
+ }
255
+ else {
256
+ // Determine what's needed for entry
257
+ const zThreshold = 1.5;
258
+ const needMoreZ = absZ < zThreshold;
259
+ const direction = zscore < 0 ? 'LONG' : 'SHORT';
260
+ const ofiConfirms = (direction === 'LONG' && ofi > 0.15) || (direction === 'SHORT' && ofi < -0.15);
261
+
262
+ // Z-score too low - main reason for no entry
263
+ if (needMoreZ) {
264
+ const needed = (zThreshold - absZ).toFixed(1);
265
+ const dir = zscore < 0 ? 'oversold' : zscore > 0 ? 'overbought' : 'neutral';
266
+ state = `zscore-low-${zRounded}-${ofiPct}`;
267
+ message = `[${sym}] ${priceStr} | Z:${zRounded}σ ${dir} | Need ${needed}σ more for signal | OFI:${ofiPct}%`;
268
+ }
269
+ // Z-score high enough but OFI doesn't confirm
270
+ else if (!ofiConfirms) {
271
+ const ofiNeedStr = direction === 'LONG' ? '>15%' : '<-15%';
272
+ state = `ofi-pending-${zRounded}-${ofiPct}`;
273
+ message = `[${sym}] ${priceStr} | Z:${zRounded}σ ${direction} ready | OFI:${ofiPct}% needs ${ofiNeedStr} to confirm`;
274
+ }
275
+ // All conditions met!
276
+ else {
277
+ state = `signal-${direction}`;
278
+ message = `[${sym}] ${priceStr} | Z:${zRounded}σ | OFI:${ofiPct}% | ${direction} SIGNAL CONDITIONS MET`;
279
+ }
280
+ }
260
281
  }
261
282
 
262
- this.emit('log', { type: 'info', message });
283
+ // Only emit if state changed (no repetition)
284
+ if (state !== this._lastLogState) {
285
+ this._lastLogState = state;
286
+ this.emit('log', { type: 'info', message });
287
+ }
263
288
  }
264
289
 
265
290
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.229",
3
+ "version": "2.9.231",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1,320 +1,242 @@
1
1
  /**
2
- * Smart Logs Engine - Unified Event-Driven Intelligent Logs
3
- * ==========================================================
2
+ * Smart Logs Engine - Professional HF-Grade Adaptive Logs
3
+ * ========================================================
4
4
  *
5
- * UNIFIED SYSTEM for all CLI strategies:
6
- * - Same engine, same event detection logic
7
- * - Strategy-specific vocabulary (HQX-2B: bars/swings/zones, QUANT: ticks/Z-Score/VPIN/OFI)
8
- * - Uses smartLogs.getLiveAnalysisLog() for varied, non-repetitive messages
5
+ * PRINCIPLES:
6
+ * 1. NO repetitive messages - each log must be unique and meaningful
7
+ * 2. Adaptive to real market context - uses actual QUANT metrics
8
+ * 3. Professional HF language - precise, technical, actionable
9
+ * 4. Event-driven only - silence means scanning, no spam
9
10
  *
10
- * Only logs when something SIGNIFICANT happens - no spam, no repetitive messages
11
- *
12
- * COLOR SCHEME:
13
- * - Symbols: cyan (NQ, ES, CL, GC)
14
- * - Prices: white bold
15
- * - Bullish/Long: green
16
- * - Bearish/Short: red
17
- * - Neutral/System: gray/dim
18
- * - Signals: yellow/magenta
19
- * - Risk/Warnings: red bold
20
- * - Values: blue (Z-Score, VPIN, OFI numbers)
11
+ * This replaces the old rotating generic messages with
12
+ * intelligent, context-aware logs that reflect the actual
13
+ * algorithmic decision process.
21
14
  */
22
15
 
23
16
  'use strict';
24
17
 
25
18
  const chalk = require('chalk');
26
- const smartLogs = require('./smart-logs');
27
- const { getContextualMessage } = require('./smart-logs-context');
28
19
 
29
20
  // Color helpers for consistent styling
30
21
  const C = {
31
- // Symbols & identifiers
32
22
  sym: (s) => chalk.cyan.bold(s),
33
-
34
- // Prices
35
23
  price: (p) => chalk.white.bold(p),
36
-
37
- // Direction
38
24
  long: (s) => chalk.green.bold(s),
39
25
  short: (s) => chalk.red.bold(s),
40
26
  bull: (s) => chalk.green(s),
41
27
  bear: (s) => chalk.red(s),
42
-
43
- // Values & metrics
44
28
  val: (v) => chalk.blue(v),
45
29
  valHigh: (v) => chalk.magenta.bold(v),
46
-
47
- // Status
48
30
  ok: (s) => chalk.green(s),
49
31
  warn: (s) => chalk.yellow(s),
50
32
  danger: (s) => chalk.red.bold(s),
51
-
52
- // System/neutral
53
33
  dim: (s) => chalk.dim(s),
54
34
  info: (s) => chalk.gray(s),
55
-
56
- // Special
57
35
  signal: (s) => chalk.yellow.bold(s),
58
- zone: (s) => chalk.magenta(s),
59
- regime: (s) => chalk.cyan(s),
60
36
  };
61
37
 
62
38
  const CONFIG = {
63
- SESSION_LOG_INTERVAL: 10,
64
- // HQX-2B thresholds
65
- PRICE_CHANGE_TICKS: 4,
66
- DELTA_CHANGE_THRESHOLD: 200,
67
- // QUANT thresholds
68
39
  Z_EXTREME: 2.0,
69
40
  Z_HIGH: 1.5,
70
41
  Z_BUILDING: 1.0,
42
+ OFI_STRONG: 0.20,
71
43
  OFI_THRESHOLD: 0.15,
72
- VPIN_TOXIC: 0.6,
73
- QUANT_WARMUP_TICKS: 250,
74
- // Heartbeat interval - frequent updates in scanning mode
75
- HEARTBEAT_MS: 5000, // 5 seconds
76
- };
77
-
78
- const SYMBOLS = {
79
- NQ: 'NQ', MNQ: 'MNQ', ES: 'ES', MES: 'MES', YM: 'YM', MYM: 'MYM',
80
- CL: 'CL', MCL: 'MCL', GC: 'GC', MGC: 'MGC', SI: 'SI', SIL: 'SIL',
81
- RTY: 'RTY', M2K: 'M2K', ZB: 'ZB', ZN: 'ZN',
44
+ VPIN_TOXIC: 0.65,
45
+ VPIN_ELEVATED: 0.50,
82
46
  };
83
47
 
84
48
  function getSym(s) {
85
49
  if (!s) return 'FUT';
86
50
  const b = s.split(':')[0].replace(/[FGHJKMNQUVXZ]\d{1,2}$/, '').toUpperCase();
87
- return SYMBOLS[b] || b;
51
+ const map = { ENQ: 'NQ', EP: 'ES', RTY: 'RTY', EMD: 'EMD', MGC: 'GC', MCL: 'CL' };
52
+ return map[b] || b;
88
53
  }
89
54
 
90
55
  /**
91
- * Unified Smart Logs Engine
92
- * Works with any strategy - adapts vocabulary based on strategyId
56
+ * Professional HF Smart Logs Engine
93
57
  */
94
58
  class SmartLogsEngine {
95
59
  constructor(strategyId, symbol) {
96
- this.strategyId = strategyId || 'hqx-2b';
60
+ this.strategyId = strategyId || 'ultra-scalping';
97
61
  this.symbolCode = symbol;
98
- this.counter = 0;
99
- this.lastState = null;
100
-
101
- // State tracking for event detection (both strategies)
102
- this.lastBias = null;
103
- this.warmupLogged = false;
62
+ this.lastLogHash = null;
63
+ this.lastLogTime = 0;
64
+ this.eventCounter = 0;
104
65
 
105
- // HQX-2B specific
106
- this.lastBars = 0;
107
- this.lastSwings = 0;
108
- this.lastZones = 0;
109
- this.lastNearZone = false;
110
-
111
- // QUANT specific
112
- this.lastZRegime = null;
113
- this.lastVpinToxic = false;
66
+ // State tracking for change detection
67
+ this.prev = {
68
+ zRegime: null,
69
+ ofiDir: null,
70
+ vpinLevel: null,
71
+ position: 0,
72
+ ready: false,
73
+ };
114
74
  }
115
75
 
116
76
  setSymbol(s) { this.symbolCode = s; }
117
77
 
118
78
  /**
119
- * Get log message - unified entry point
120
- * Detects strategy and routes to appropriate handler
79
+ * Main entry - returns log only when meaningful event occurs
80
+ * Returns null for silence (professional: no news = scanning)
121
81
  */
122
82
  getLog(state = {}) {
123
- this.counter++;
124
83
  const sym = getSym(this.symbolCode);
125
- const price = state.price > 0 ? state.price.toFixed(2) : '-.--';
126
- const { position = 0, delta = 0 } = state;
127
-
128
- // Active position - same for all strategies
129
- if (position !== 0) {
130
- const isLong = position > 0;
131
- const side = isLong ? C.long('LONG') : C.short('SHORT');
132
- const flowFavor = (isLong && delta > 0) || (!isLong && delta < 0);
133
- const flowLabel = flowFavor ? C.ok('FAVOR') : C.danger('ADVERSE');
134
- const deltaStr = delta > 0 ? C.bull(`+${delta}`) : C.bear(`${delta}`);
84
+ const price = state.price > 0 ? state.price.toFixed(2) : null;
85
+ const { position = 0, zScore = 0, vpin = 0, ofi = 0, tickCount = 0, bars = 0 } = state;
86
+
87
+ // Not enough data - still warming up
88
+ const dataPoints = tickCount || bars || 0;
89
+ if (dataPoints < 50 || !price) {
90
+ // Only log warmup progress at milestones
91
+ if (!this.prev.ready && dataPoints > 0) {
92
+ const pct = Math.min(100, Math.round((dataPoints / 50) * 100));
93
+ const milestone = Math.floor(pct / 25) * 25;
94
+ const lastMilestone = this._lastWarmupMilestone || 0;
95
+ if (milestone > lastMilestone) {
96
+ this._lastWarmupMilestone = milestone;
97
+ return {
98
+ type: 'system',
99
+ message: `[${C.sym(sym)}] Calibrating QUANT models... ${C.val(pct + '%')} (${dataPoints} samples)`,
100
+ logToSession: false
101
+ };
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+
107
+ // Mark as ready
108
+ if (!this.prev.ready) {
109
+ this.prev.ready = true;
135
110
  return {
136
- type: 'trade',
137
- message: `[${C.sym(sym)}] ${side} ACTIVE @ ${C.price(price)} | Delta: ${deltaStr} | Flow: ${flowLabel}`,
111
+ type: 'system',
112
+ message: `[${C.sym(sym)}] ${C.price(price)} | ${C.ok('QUANT models calibrated')} | Scanning for alpha`,
138
113
  logToSession: true
139
114
  };
140
115
  }
141
-
142
- // Route to strategy-specific handler
143
- if (this.strategyId === 'ultra-scalping') {
144
- return this._getQuantLog(state, sym, price);
145
- } else {
146
- return this._getHqx2bLog(state, sym, price);
147
- }
148
- }
149
-
150
- /**
151
- * HQX-2B Liquidity Sweep - Bar/Swing/Zone based events
152
- */
153
- _getHqx2bLog(state, sym, price) {
154
- const { bars = 0, swings = 0, zones = 0, nearZone = false, trend = 'neutral', delta = 0 } = state;
155
116
 
156
- let event = null;
157
- let logType = 'analysis';
158
- let message = null;
159
-
160
- // EVENT 1: Warmup complete (10+ bars)
161
- if (bars >= 10 && !this.warmupLogged) {
162
- this.warmupLogged = true;
163
- event = 'warmup';
164
- const warmupMsg = getContextualMessage(this.symbolCode, this.strategyId, 'warmup');
165
- message = `[${C.sym(sym)}] ${C.ok('2B ready')} | ${C.val(bars)} bars | ${C.dim(warmupMsg)}`;
166
- logType = 'system';
167
- }
168
- // EVENT 2: New zone created
169
- else if (zones > this.lastZones && zones > 0) {
170
- event = 'new_zone';
171
- const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
172
- message = `[${C.sym(sym)}] ${C.price(price)} | ${C.zone('Zone #' + zones)} | ${C.signal(signalMsg)}`;
173
- logType = 'signal';
174
- }
175
- // EVENT 3: New swing detected
176
- else if (swings > this.lastSwings && swings > 0) {
177
- event = 'new_swing';
178
- const scanMsg = getContextualMessage(this.symbolCode, this.strategyId, 'scanning');
179
- message = `[${C.sym(sym)}] ${C.price(price)} | ${C.info('Swing #' + swings)} | ${C.dim(scanMsg)}`;
180
- }
181
- // EVENT 4: Zone approach (price near zone)
182
- else if (nearZone && !this.lastNearZone && zones > 0) {
183
- event = 'zone_approach';
184
- const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
185
- message = `[${C.sym(sym)}] ${C.price(price)} | ${C.warn('Zone approach')} | ${C.signal(signalMsg)}`;
186
- logType = 'signal';
187
- }
188
- // EVENT 5: Bias flip
189
- else if (this.lastBias && trend !== this.lastBias && trend !== 'neutral' && this.lastBias !== 'neutral') {
190
- event = 'bias_flip';
191
- const arrow = trend === 'bullish' ? C.bull('▲') : C.bear('▼');
192
- const oldBias = this.lastBias === 'bullish' ? C.bull(this.lastBias) : C.bear(this.lastBias);
193
- const newBias = trend === 'bullish' ? C.bull(trend) : C.bear(trend);
194
- const flipMsg = getContextualMessage(this.symbolCode, this.strategyId, trend);
195
- message = `[${C.sym(sym)}] ${arrow} ${oldBias} → ${newBias} | ${C.dim(flipMsg)}`;
196
- }
197
-
198
- // Update state tracking
199
- this.lastBars = bars;
200
- this.lastSwings = swings;
201
- this.lastZones = zones;
202
- this.lastNearZone = nearZone;
203
- this.lastBias = trend;
204
-
205
- if (event && message) {
206
- return { type: logType, message, logToSession: event === 'new_zone' || event === 'bias_flip' };
117
+ // Active position - always show
118
+ if (position !== 0) {
119
+ const isLong = position > 0;
120
+ const side = isLong ? C.long('LONG') : C.short('SHORT');
121
+ const deltaFavor = (isLong && ofi > 0) || (!isLong && ofi < 0);
122
+ const flowLabel = deltaFavor ? C.ok('ALIGNED') : C.warn('ADVERSE');
123
+ const ofiStr = (ofi * 100).toFixed(0);
124
+ const zStr = zScore.toFixed(2);
125
+
126
+ // Only log position updates when something changes
127
+ const posHash = `pos-${position}-${Math.round(ofi * 10)}`;
128
+ if (posHash !== this.lastLogHash) {
129
+ this.lastLogHash = posHash;
130
+ return {
131
+ type: 'trade',
132
+ message: `[${C.sym(sym)}] ${side} @ ${C.price(price)} | OFI:${ofiStr}% ${flowLabel} | Z:${zStr}σ`,
133
+ logToSession: true
134
+ };
135
+ }
136
+ return null;
207
137
  }
208
- return null;
209
- }
210
-
211
- /**
212
- * QUANT (HQX Ultra Scalping) - Tick/Z-Score/VPIN/OFI based events
213
- */
214
- _getQuantLog(state, sym, price) {
215
- const { tickCount = 0, zScore = 0, vpin = 0, ofi = 0 } = state;
216
- const ticks = tickCount || state.bars || 0;
217
138
 
139
+ // Compute current regimes
218
140
  const absZ = Math.abs(zScore);
219
- const vpinToxic = vpin > CONFIG.VPIN_TOXIC;
220
- const zRegime = absZ >= CONFIG.Z_EXTREME ? 'extreme' : absZ >= CONFIG.Z_HIGH ? 'high' : absZ >= CONFIG.Z_BUILDING ? 'building' : 'neutral';
221
- const bias = ofi > CONFIG.OFI_THRESHOLD ? 'bullish' : ofi < -CONFIG.OFI_THRESHOLD ? 'bearish' : 'neutral';
222
-
141
+ const zRegime = absZ >= CONFIG.Z_EXTREME ? 'extreme' :
142
+ absZ >= CONFIG.Z_HIGH ? 'high' :
143
+ absZ >= CONFIG.Z_BUILDING ? 'building' : 'neutral';
144
+ const ofiDir = ofi > CONFIG.OFI_STRONG ? 'strong-bull' :
145
+ ofi > CONFIG.OFI_THRESHOLD ? 'bull' :
146
+ ofi < -CONFIG.OFI_STRONG ? 'strong-bear' :
147
+ ofi < -CONFIG.OFI_THRESHOLD ? 'bear' : 'neutral';
148
+ const vpinLevel = vpin > CONFIG.VPIN_TOXIC ? 'toxic' :
149
+ vpin > CONFIG.VPIN_ELEVATED ? 'elevated' : 'normal';
150
+
151
+ // Detect events (changes in regime)
223
152
  let event = null;
224
- let logType = 'analysis';
225
153
  let message = null;
226
-
227
- // Helper for Z-Score color
228
- const zColor = (z) => {
229
- const absVal = Math.abs(z);
230
- const formatted = `${z.toFixed(1)}σ`;
231
- if (absVal >= CONFIG.Z_EXTREME) return C.valHigh(formatted);
232
- if (absVal >= CONFIG.Z_HIGH) return C.warn(formatted);
233
- if (absVal >= CONFIG.Z_BUILDING) return C.val(formatted);
234
- return C.dim(formatted);
235
- };
236
-
237
- // EVENT 1: Warmup complete (250 ticks for QUANT models)
238
- if (ticks >= CONFIG.QUANT_WARMUP_TICKS && !this.warmupLogged) {
239
- this.warmupLogged = true;
240
- event = 'warmup';
241
- const warmupMsg = getContextualMessage(this.symbolCode, this.strategyId, 'warmup');
242
- message = `[${C.sym(sym)}] ${C.ok('QUANT ready')} | ${C.val(ticks)} ticks | ${C.dim(warmupMsg)}`;
243
- logType = 'system';
244
- }
245
- // EVENT 2: Z-Score regime change
246
- else if (this.lastZRegime !== null && zRegime !== this.lastZRegime) {
154
+ let logType = 'analysis';
155
+
156
+ const zColor = absZ >= CONFIG.Z_EXTREME ? C.valHigh :
157
+ absZ >= CONFIG.Z_HIGH ? C.warn :
158
+ absZ >= CONFIG.Z_BUILDING ? C.val : C.dim;
159
+ const zStr = zColor(`${zScore.toFixed(2)}σ`);
160
+ const ofiPct = (ofi * 100).toFixed(0);
161
+ const vpinPct = (vpin * 100).toFixed(0);
162
+
163
+ // EVENT 1: Z-Score regime change (most important)
164
+ if (zRegime !== this.prev.zRegime && this.prev.zRegime !== null) {
247
165
  event = 'z_regime';
248
- // Get instrument-specific market context message
249
- const marketCtx = bias === 'bullish' ? 'bullish' : bias === 'bearish' ? 'bearish' : 'neutral';
250
- const instrumentMsg = getContextualMessage(this.symbolCode, this.strategyId, marketCtx);
166
+ const dir = zScore < 0 ? 'LONG' : 'SHORT';
251
167
 
252
168
  if (zRegime === 'extreme') {
253
169
  logType = 'signal';
254
- const dir = zScore < 0 ? C.long('LONG') : C.short('SHORT');
255
- const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
256
- message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.signal('EXTREME')} | ${dir} | ${C.signal(signalMsg)}`;
170
+ const ofiConfirm = (zScore < 0 && ofi > CONFIG.OFI_THRESHOLD) ||
171
+ (zScore > 0 && ofi < -CONFIG.OFI_THRESHOLD);
172
+ if (ofiConfirm) {
173
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z:${zStr} ${C.signal('EXTREME')} | ${C.long(dir)} | OFI:${ofiPct}% ${C.ok('CONFIRMS')}`;
174
+ } else {
175
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z:${zStr} ${C.signal('EXTREME')} | ${C.warn(dir + ' pending')} | OFI:${ofiPct}% awaiting`;
176
+ }
257
177
  } else if (zRegime === 'high') {
258
178
  logType = 'signal';
259
- message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.warn('HIGH')} | ${C.dim(instrumentMsg)}`;
260
- } else if (zRegime === 'building') {
261
- message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.info('building')} | ${C.dim(instrumentMsg)}`;
262
- } else {
263
- const scanMsg = getContextualMessage(this.symbolCode, this.strategyId, 'scanning');
264
- message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${C.ok('normalized')} | ${C.dim(scanMsg)}`;
179
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z:${zStr} ${C.warn('building')} | ${dir} setup forming | OFI:${ofiPct}%`;
180
+ } else if (zRegime === 'building' && this.prev.zRegime === 'neutral') {
181
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z:${zStr} | Deviation detected | Monitoring`;
182
+ } else if (zRegime === 'neutral' && (this.prev.zRegime === 'high' || this.prev.zRegime === 'extreme')) {
183
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z:${C.ok('normalized')} | Mean reversion complete`;
265
184
  }
266
185
  }
267
- // EVENT 3: Bias flip (OFI direction change)
268
- else if (this.lastBias !== null && bias !== this.lastBias && bias !== 'neutral' && this.lastBias !== 'neutral') {
269
- event = 'bias_flip';
270
- const arrow = bias === 'bullish' ? C.bull('▲') : C.bear('▼');
271
- const oldBias = this.lastBias === 'bullish' ? C.bull(this.lastBias) : C.bear(this.lastBias);
272
- const newBias = bias === 'bullish' ? C.bull(bias) : C.bear(bias);
273
- const flipMsg = getContextualMessage(this.symbolCode, this.strategyId, bias);
274
- message = `[${C.sym(sym)}] ${arrow} OFI: ${oldBias} → ${newBias} | ${C.dim(flipMsg)}`;
186
+ // EVENT 2: OFI direction flip (significant)
187
+ else if (ofiDir !== this.prev.ofiDir && this.prev.ofiDir !== null &&
188
+ ofiDir !== 'neutral' && this.prev.ofiDir !== 'neutral') {
189
+ event = 'ofi_flip';
190
+ const wasLong = this.prev.ofiDir.includes('bull');
191
+ const nowLong = ofiDir.includes('bull');
192
+ if (wasLong !== nowLong) {
193
+ const arrow = nowLong ? C.bull('▲') : C.bear('▼');
194
+ const newDir = nowLong ? C.bull('BUY') : C.bear('SELL');
195
+ message = `[${C.sym(sym)}] ${C.price(price)} | ${arrow} OFI flip → ${newDir} pressure | ${ofiPct}% | Z:${zStr}`;
196
+ }
275
197
  }
276
- // EVENT 4: VPIN toxicity change
277
- else if (this.lastVpinToxic !== null && vpinToxic !== this.lastVpinToxic) {
278
- event = 'vpin';
279
- const vpinPct = (vpin * 100).toFixed(0);
280
- if (vpinToxic) {
281
- message = `[${C.sym(sym)}] ${C.price(price)} | VPIN: ${C.danger(vpinPct + '%')} ${C.danger('TOXIC')} - informed flow`;
198
+ // EVENT 3: VPIN level change (toxicity warning)
199
+ else if (vpinLevel !== this.prev.vpinLevel && this.prev.vpinLevel !== null) {
200
+ event = 'vpin_change';
201
+ if (vpinLevel === 'toxic') {
282
202
  logType = 'risk';
283
- } else {
284
- message = `[${C.sym(sym)}] ${C.price(price)} | VPIN: ${C.ok(vpinPct + '%')} ${C.ok('clean')} - normal flow`;
203
+ message = `[${C.sym(sym)}] ${C.price(price)} | VPIN:${C.danger(vpinPct + '% TOXIC')} | Informed flow detected | Hold`;
204
+ } else if (vpinLevel === 'elevated' && this.prev.vpinLevel === 'normal') {
205
+ message = `[${C.sym(sym)}] ${C.price(price)} | VPIN:${C.warn(vpinPct + '%')} elevated | Monitoring toxicity`;
206
+ } else if (vpinLevel === 'normal' && this.prev.vpinLevel === 'toxic') {
207
+ message = `[${C.sym(sym)}] ${C.price(price)} | VPIN:${C.ok(vpinPct + '%')} normalized | Flow clean`;
285
208
  }
286
209
  }
287
-
210
+
288
211
  // Update state tracking
289
- this.lastZRegime = zRegime;
290
- this.lastBias = bias;
291
- this.lastVpinToxic = vpinToxic;
292
-
212
+ this.prev.zRegime = zRegime;
213
+ this.prev.ofiDir = ofiDir;
214
+ this.prev.vpinLevel = vpinLevel;
215
+ this.prev.position = position;
216
+
217
+ // Return event or null (silence = professional scanning)
293
218
  if (event && message) {
294
- return { type: logType, message, logToSession: event === 'z_regime' || event === 'bias_flip' };
219
+ this.lastLogHash = `${event}-${zRegime}-${ofiDir}-${vpinLevel}`;
220
+ return { type: logType, message, logToSession: logType === 'signal' || logType === 'risk' };
295
221
  }
296
222
 
297
- // EVENT-DRIVEN ONLY: No spam, no repetitive logs
298
- // Silence = system is scanning, nothing notable happening
299
- // This is professional HF behavior
300
223
  return null;
301
224
  }
302
225
 
303
226
  reset() {
304
- this.lastState = null;
305
- this.counter = 0;
306
- this.lastBias = null;
307
- this.warmupLogged = false;
308
- // HQX-2B
309
- this.lastBars = 0;
310
- this.lastSwings = 0;
311
- this.lastZones = 0;
312
- this.lastNearZone = false;
313
- // QUANT
314
- this.lastZRegime = null;
315
- this.lastVpinToxic = false;
227
+ this.lastLogHash = null;
228
+ this.lastLogTime = 0;
229
+ this.eventCounter = 0;
230
+ this._lastWarmupMilestone = 0;
231
+ this.prev = {
232
+ zRegime: null,
233
+ ofiDir: null,
234
+ vpinLevel: null,
235
+ position: 0,
236
+ ready: false,
237
+ };
316
238
  }
317
239
  }
318
240
 
319
241
  function createEngine(strategyId, symbol) { return new SmartLogsEngine(strategyId, symbol); }
320
- module.exports = { SmartLogsEngine, createEngine, CONFIG };
242
+ module.exports = { SmartLogsEngine, createEngine, CONFIG, C };