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.
- package/dist/lib/m/ultra-scalping.js +75 -50
- package/package.json +1 -1
- package/src/lib/smart-logs-engine.js +155 -233
|
@@ -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
|
|
209
|
+
const sym = extractBaseSymbol(contractId);
|
|
210
|
+
const priceStr = currentPrice.toFixed(2);
|
|
230
211
|
|
|
231
|
-
// Build message based on state
|
|
232
212
|
let message;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
//
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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,320 +1,242 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Smart Logs Engine -
|
|
3
|
-
*
|
|
2
|
+
* Smart Logs Engine - Professional HF-Grade Adaptive Logs
|
|
3
|
+
* ========================================================
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 || '
|
|
60
|
+
this.strategyId = strategyId || 'ultra-scalping';
|
|
97
61
|
this.symbolCode = symbol;
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
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
|
-
//
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
*
|
|
120
|
-
*
|
|
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,
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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: '
|
|
137
|
-
message: `[${C.sym(sym)}] ${
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
228
|
-
const zColor =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
260
|
-
} else if (zRegime === 'building') {
|
|
261
|
-
message = `[${C.sym(sym)}] ${C.price(price)} | Z
|
|
262
|
-
} else {
|
|
263
|
-
|
|
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
|
|
268
|
-
else if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
277
|
-
else if (
|
|
278
|
-
event = '
|
|
279
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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.
|
|
290
|
-
this.
|
|
291
|
-
this.
|
|
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
|
-
|
|
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.
|
|
305
|
-
this.
|
|
306
|
-
this.
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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 };
|