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.
@@ -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
- // Emit status log every second
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 || '').replace(/[A-Z]\d+$/, '');
209
+ const sym = extractBaseSymbol(contractId);
210
+ const priceStr = currentPrice.toFixed(2);
215
211
 
216
- // Build message based on state
217
212
  let message;
218
- if (!this.tradingEnabled) {
219
- message = `[${sym}] ${currentPrice.toFixed(2)} | PAUSED (${this.lossStreak} losses) | Cooldown active`;
220
- } else if (absZ >= 2.0) {
221
- const dir = zscore < 0 ? 'LONG' : 'SHORT';
222
- const ofiPct = (Math.abs(ofi) * 100).toFixed(0);
223
- const ofiConfirm = (zscore < 0 && ofi > 0.15) || (zscore > 0 && ofi < -0.15);
224
- if (ofiConfirm) {
225
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1) ${zState} | ${dir} | OFI ${ofiPct}% confirms`;
226
- } else {
227
- message = `[${sym}] ${currentPrice.toFixed(2)} | Z: ${zscore.toFixed(1) ${zState} | ${dir} signal | OFI ${ofiPct}% pending`;
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
- // 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);
235
231
  const vpinPct = (vpin * 100).toFixed(0);
236
- const contexts = [
237
- `VPIN ${vpinPct}% | Mean reversion scan`,
238
- `OFI balanced | Price discovery`,
239
- `Z normalized | Statistical scan`,
240
- `Tick flow stable | Edge detection`,
241
- `Volatility normal | Alpha scan`,
242
- ];
243
- const ctx = contexts[Math.floor(Date.now() / 5000) % contexts.length];
244
- 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
+ }
245
281
  }
246
282
 
247
- 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
+ }
248
288
  }
249
289
 
250
290
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.228",
3
+ "version": "2.9.230",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {