hedgequantx 2.9.228 → 2.9.230
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 +98 -58
- package/package.json +1 -1
|
@@ -136,6 +136,26 @@ class HQXUltraScalpingStrategy extends EventEmitter {
|
|
|
136
136
|
this.tickBuffer.set(contractId, []);
|
|
137
137
|
this.lastBarTime.set(contractId, 0);
|
|
138
138
|
this.kalmanStates.set(contractId, { estimate: 0, errorCovariance: 1.0 });
|
|
139
|
+
|
|
140
|
+
// Start status log interval - emits every second regardless of tick flow
|
|
141
|
+
this._currentContractId = contractId;
|
|
142
|
+
this._lastPrice = 0;
|
|
143
|
+
if (this._statusInterval) clearInterval(this._statusInterval);
|
|
144
|
+
this._statusInterval = setInterval(() => {
|
|
145
|
+
if (this._lastPrice > 0) {
|
|
146
|
+
this._emitStatusLog(this._currentContractId, this._lastPrice);
|
|
147
|
+
}
|
|
148
|
+
}, 1000);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Stop the strategy and clean up interval
|
|
153
|
+
*/
|
|
154
|
+
stop() {
|
|
155
|
+
if (this._statusInterval) {
|
|
156
|
+
clearInterval(this._statusInterval);
|
|
157
|
+
this._statusInterval = null;
|
|
158
|
+
}
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
/**
|
|
@@ -152,15 +172,10 @@ class HQXUltraScalpingStrategy extends EventEmitter {
|
|
|
152
172
|
let ticks = this.tickBuffer.get(contractId);
|
|
153
173
|
ticks.push(tick);
|
|
154
174
|
|
|
155
|
-
// Track total ticks for status log
|
|
175
|
+
// Track total ticks and last price for status log interval
|
|
156
176
|
this._totalTicks = (this._totalTicks || 0) + 1;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const now = Date.now();
|
|
160
|
-
if (!this._lastStatusLog || now - this._lastStatusLog >= 1000) {
|
|
161
|
-
this._lastStatusLog = now;
|
|
162
|
-
this._emitStatusLog(contractId, tick.price);
|
|
163
|
-
}
|
|
177
|
+
this._lastPrice = tick.price;
|
|
178
|
+
this._currentContractId = contractId;
|
|
164
179
|
|
|
165
180
|
// Check if we should form a new bar
|
|
166
181
|
const lastBar = this.lastBarTime.get(contractId);
|
|
@@ -182,69 +197,94 @@ class HQXUltraScalpingStrategy extends EventEmitter {
|
|
|
182
197
|
}
|
|
183
198
|
|
|
184
199
|
/**
|
|
185
|
-
* 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
|
|
186
202
|
*/
|
|
187
203
|
_emitStatusLog(contractId, currentPrice) {
|
|
188
204
|
const prices = this.priceBuffer.get(contractId) || [];
|
|
189
205
|
const volumes = this.volumeBuffer.get(contractId) || [];
|
|
190
206
|
const bars = this.barHistory.get(contractId) || [];
|
|
191
207
|
|
|
192
|
-
if (prices.length < 20) return; // Not enough data yet
|
|
193
|
-
|
|
194
|
-
// Compute current metrics
|
|
195
|
-
const zscore = computeZScore(prices);
|
|
196
|
-
const vpin = volumes.length >= 10 ? computeVPIN(volumes, this.vpinWindow) : 0;
|
|
197
|
-
const ofi = bars.length >= 10 ? computeOrderFlowImbalance(bars, this.ofiLookback) : 0;
|
|
198
|
-
|
|
199
|
-
// Determine market state
|
|
200
|
-
const absZ = Math.abs(zscore);
|
|
201
|
-
let zState = 'normal';
|
|
202
|
-
if (absZ >= 2.0) zState = 'EXTREME';
|
|
203
|
-
else if (absZ >= 1.5) zState = 'HIGH';
|
|
204
|
-
else if (absZ >= 1.0) zState = 'building';
|
|
205
|
-
|
|
206
|
-
// Determine direction bias
|
|
207
|
-
let bias = 'neutral';
|
|
208
|
-
if (zscore < -1.5 && ofi > 0.1) bias = 'LONG setup';
|
|
209
|
-
else if (zscore > 1.5 && ofi < -0.1) bias = 'SHORT setup';
|
|
210
|
-
else if (zscore < -1.0) bias = 'oversold';
|
|
211
|
-
else if (zscore > 1.0) bias = 'overbought';
|
|
212
|
-
|
|
213
208
|
// Extract symbol
|
|
214
|
-
const sym = (contractId
|
|
209
|
+
const sym = extractBaseSymbol(contractId);
|
|
210
|
+
const priceStr = currentPrice.toFixed(2);
|
|
215
211
|
|
|
216
|
-
// Build message based on state
|
|
217
212
|
let message;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
} else if (absZ >= 1.5) {
|
|
230
|
-
message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1)}σ ${zState} | ${bias} | Monitoring`;
|
|
231
|
-
} else if (absZ >= 1.0) {
|
|
232
|
-
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}%)`;
|
|
233
224
|
} else {
|
|
234
|
-
//
|
|
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);
|
|
235
231
|
const vpinPct = (vpin * 100).toFixed(0);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
}
|
|
245
281
|
}
|
|
246
282
|
|
|
247
|
-
|
|
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
|
+
}
|
|
248
288
|
}
|
|
249
289
|
|
|
250
290
|
/**
|