hedgequantx 2.9.229 → 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 +75 -50
- package/package.json +1 -1
|
@@ -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
|
/**
|