hedgequantx 2.9.197 → 2.9.199

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.197",
3
+ "version": "2.9.199",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/m/s1.js CHANGED
@@ -113,6 +113,13 @@ class HQXUltraScalpingStrategy extends EventEmitter {
113
113
  this.recentTrades = [];
114
114
  this.winStreak = 0;
115
115
  this.lossStreak = 0;
116
+
117
+ // === CRITICAL: Cooldown & Risk Management ===
118
+ this.lastSignalTime = 0;
119
+ this.signalCooldownMs = 30000; // 30 seconds minimum between signals
120
+ this.maxConsecutiveLosses = 3; // Stop trading after 3 consecutive losses
121
+ this.minConfidenceThreshold = 0.65; // Minimum 65% confidence (was 55%)
122
+ this.tradingEnabled = true;
116
123
  }
117
124
 
118
125
  /**
@@ -266,6 +273,33 @@ class HQXUltraScalpingStrategy extends EventEmitter {
266
273
  // SIGNAL GENERATION
267
274
  // ===========================================================================
268
275
  _generateSignal(contractId, currentPrice, zscore, vpin, kyleLambda, kalmanEstimate, regime, volParams, ofi, bars) {
276
+ // CRITICAL: Check if trading is enabled
277
+ if (!this.tradingEnabled) {
278
+ this.emit('log', { type: 'debug', message: `Trading disabled (${this.lossStreak} consecutive losses)` });
279
+ return null;
280
+ }
281
+
282
+ // CRITICAL: Check cooldown
283
+ const now = Date.now();
284
+ const timeSinceLastSignal = now - this.lastSignalTime;
285
+ if (timeSinceLastSignal < this.signalCooldownMs) {
286
+ // Silent - don't spam logs
287
+ return null;
288
+ }
289
+
290
+ // CRITICAL: Check consecutive losses
291
+ if (this.lossStreak >= this.maxConsecutiveLosses) {
292
+ this.tradingEnabled = false;
293
+ this.emit('log', { type: 'info', message: `Trading paused: ${this.lossStreak} consecutive losses. Waiting for cooldown...` });
294
+ // Auto re-enable after 2 minutes
295
+ setTimeout(() => {
296
+ this.tradingEnabled = true;
297
+ this.lossStreak = 0;
298
+ this.emit('log', { type: 'info', message: 'Trading re-enabled after cooldown' });
299
+ }, 120000);
300
+ return null;
301
+ }
302
+
269
303
  const absZscore = Math.abs(zscore);
270
304
  if (absZscore < volParams.zscoreThreshold) return null;
271
305
  if (vpin > this.vpinToxicThreshold) return null;
@@ -275,7 +309,13 @@ class HQXUltraScalpingStrategy extends EventEmitter {
275
309
  else if (zscore > volParams.zscoreThreshold) direction = 'short';
276
310
  else return null;
277
311
 
278
- const ofiConfirms = (direction === 'long' && ofi > 0.1) || (direction === 'short' && ofi < -0.1);
312
+ // CRITICAL: OFI must confirm direction (stronger filter)
313
+ const ofiConfirms = (direction === 'long' && ofi > 0.15) || (direction === 'short' && ofi < -0.15);
314
+ if (!ofiConfirms) {
315
+ this.emit('log', { type: 'debug', message: `Signal rejected: OFI (${(ofi * 100).toFixed(1)}%) doesn't confirm ${direction}` });
316
+ return null;
317
+ }
318
+
279
319
  const kalmanDiff = currentPrice - kalmanEstimate;
280
320
  const kalmanConfirms = (direction === 'long' && kalmanDiff < 0) || (direction === 'short' && kalmanDiff > 0);
281
321
 
@@ -293,7 +333,15 @@ class HQXUltraScalpingStrategy extends EventEmitter {
293
333
  scores.kalman * 0.15 + scores.volatility * 0.10 + scores.ofi * 0.20;
294
334
 
295
335
  const confidence = Math.min(1.0, scores.composite + volParams.confidenceBonus);
296
- if (confidence < 0.55) return null;
336
+
337
+ // CRITICAL: Higher confidence threshold (65% minimum)
338
+ if (confidence < this.minConfidenceThreshold) {
339
+ this.emit('log', { type: 'debug', message: `Signal rejected: confidence ${(confidence * 100).toFixed(1)}% < ${this.minConfidenceThreshold * 100}%` });
340
+ return null;
341
+ }
342
+
343
+ // Update last signal time
344
+ this.lastSignalTime = now;
297
345
 
298
346
  const stopTicks = Math.round(this.baseStopTicks * volParams.stopMultiplier);
299
347
  const targetTicks = Math.round(this.baseTargetTicks * volParams.targetMultiplier);
@@ -386,13 +434,36 @@ class HQXUltraScalpingStrategy extends EventEmitter {
386
434
  }
387
435
 
388
436
  /**
389
- * Record trade result
437
+ * Record trade result - CRITICAL for risk management
438
+ * @param {number} pnl - Trade P&L (positive or negative)
390
439
  */
391
440
  recordTradeResult(pnl) {
441
+ // Only record actual trades (not P&L updates)
442
+ // A trade is considered closed when P&L changes significantly
443
+ const lastTrade = this.recentTrades[this.recentTrades.length - 1];
444
+ if (lastTrade && Math.abs(pnl - lastTrade.pnl) < 0.5) {
445
+ // Same P&L, ignore duplicate
446
+ return;
447
+ }
448
+
392
449
  this.recentTrades.push({ pnl, timestamp: Date.now() });
393
450
  if (this.recentTrades.length > 100) this.recentTrades.shift();
394
- if (pnl > 0) { this.winStreak++; this.lossStreak = 0; }
395
- else { this.lossStreak++; this.winStreak = 0; }
451
+
452
+ if (pnl > 0) {
453
+ this.winStreak++;
454
+ this.lossStreak = 0;
455
+ this.tradingEnabled = true; // Re-enable on win
456
+ this.emit('log', { type: 'info', message: `WIN +$${pnl.toFixed(2)} | Streak: ${this.winStreak}` });
457
+ } else if (pnl < 0) {
458
+ this.lossStreak++;
459
+ this.winStreak = 0;
460
+ this.emit('log', { type: 'info', message: `LOSS $${pnl.toFixed(2)} | Streak: -${this.lossStreak}` });
461
+
462
+ // Check if we need to pause trading
463
+ if (this.lossStreak >= this.maxConsecutiveLosses) {
464
+ this.emit('log', { type: 'info', message: `Max losses reached (${this.lossStreak}). Pausing...` });
465
+ }
466
+ }
396
467
  }
397
468
 
398
469
  /**
@@ -401,6 +472,97 @@ class HQXUltraScalpingStrategy extends EventEmitter {
401
472
  getBarHistory(contractId) {
402
473
  return this.barHistory.get(contractId) || [];
403
474
  }
475
+
476
+ /**
477
+ * Get analysis state for logging/debugging
478
+ * @param {string} contractId - Contract ID
479
+ * @param {number} currentPrice - Current price
480
+ * @returns {Object} Current strategy state
481
+ */
482
+ getAnalysisState(contractId, currentPrice) {
483
+ const prices = this.priceBuffer.get(contractId);
484
+ const volumes = this.volumeBuffer.get(contractId);
485
+ const bars = this.barHistory.get(contractId);
486
+
487
+ if (!prices || !volumes || !bars || bars.length < 20) {
488
+ return {
489
+ ready: false,
490
+ barsProcessed: bars?.length || 0,
491
+ swingsDetected: 0,
492
+ activeZones: 0,
493
+ };
494
+ }
495
+
496
+ const zscore = computeZScore(prices);
497
+ const vpin = computeVPIN(volumes, this.vpinWindow);
498
+ const ofi = computeOrderFlowImbalance(bars, this.ofiLookback);
499
+
500
+ return {
501
+ ready: bars.length >= 50,
502
+ barsProcessed: bars.length,
503
+ swingsDetected: 0,
504
+ activeZones: 0,
505
+ zScore: zscore,
506
+ vpin: vpin,
507
+ ofi: ofi,
508
+ tradingEnabled: this.tradingEnabled,
509
+ lossStreak: this.lossStreak,
510
+ winStreak: this.winStreak,
511
+ cooldownRemaining: Math.max(0, this.signalCooldownMs - (Date.now() - this.lastSignalTime)),
512
+ };
513
+ }
514
+
515
+ /**
516
+ * Preload historical bars for faster warmup
517
+ * @param {string} contractId - Contract ID
518
+ * @param {Array} histBars - Historical bar data [{timestamp, open, high, low, close, volume}, ...]
519
+ */
520
+ preloadBars(contractId, histBars) {
521
+ if (!histBars || histBars.length === 0) return;
522
+
523
+ if (!this.barHistory.has(contractId)) {
524
+ this.initialize(contractId);
525
+ }
526
+
527
+ const bars = this.barHistory.get(contractId);
528
+ const prices = this.priceBuffer.get(contractId);
529
+ const volumes = this.volumeBuffer.get(contractId);
530
+
531
+ for (const bar of histBars) {
532
+ bars.push({
533
+ timestamp: bar.timestamp,
534
+ open: bar.open,
535
+ high: bar.high,
536
+ low: bar.low,
537
+ close: bar.close,
538
+ volume: bar.volume || 1,
539
+ delta: 0,
540
+ tickCount: 1
541
+ });
542
+
543
+ prices.push(bar.close);
544
+
545
+ const barRange = bar.high - bar.low;
546
+ let buyVol = (bar.volume || 1) * 0.5;
547
+ let sellVol = (bar.volume || 1) * 0.5;
548
+ if (barRange > 0) {
549
+ const closePosition = (bar.close - bar.low) / barRange;
550
+ buyVol = (bar.volume || 1) * closePosition;
551
+ sellVol = (bar.volume || 1) * (1 - closePosition);
552
+ }
553
+ volumes.push({ buy: buyVol, sell: sellVol });
554
+ }
555
+
556
+ // Trim to max sizes
557
+ while (bars.length > 500) bars.shift();
558
+ while (prices.length > 200) prices.shift();
559
+ while (volumes.length > 100) volumes.shift();
560
+
561
+ // Set last bar time to now
562
+ this.lastBarTime.set(contractId, Date.now());
563
+
564
+ this.emit('log', { type: 'info', message: `Preloaded ${histBars.length} bars for ${contractId}` });
565
+ }
404
566
 
405
567
  /**
406
568
  * Reset strategy