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/dist/lib/m/hqx-2b.js +1099 -833
- package/dist/lib/m/s1-models.js +173 -0
- package/dist/lib/m/ultra-scalping.js +558 -678
- package/package.json +1 -1
- package/src/lib/m/s1.js +167 -5
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
395
|
-
|
|
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
|