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.
@@ -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.230",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {