hedgequantx 2.6.58 → 2.6.60
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
package/src/config/settings.js
CHANGED
|
@@ -143,11 +143,15 @@ const FAST_SCALPING = {
|
|
|
143
143
|
MAX_HOLD_MS: 60000, // 60 seconds failsafe (force exit if stuck)
|
|
144
144
|
|
|
145
145
|
// Exit targets (in ticks) - defaults, override per symbol
|
|
146
|
-
TARGET_TICKS: 16, // Take profit target
|
|
147
|
-
STOP_TICKS: 20, // Stop loss
|
|
146
|
+
TARGET_TICKS: 16, // Take profit target (+$80 on NQ)
|
|
147
|
+
STOP_TICKS: 20, // Stop loss (-$100 on NQ)
|
|
148
|
+
|
|
149
|
+
// Breakeven (BE) - move SL to entry price after profit threshold
|
|
150
|
+
BREAKEVEN_ACTIVATION_TICKS: 6, // Activate BE after +6 ticks profit (+$30 on NQ)
|
|
151
|
+
BREAKEVEN_OFFSET_TICKS: 1, // BE at entry + 1 tick (small profit lock)
|
|
148
152
|
|
|
149
153
|
// Trailing stop (activates after MIN_HOLD + profit threshold)
|
|
150
|
-
TRAILING_ACTIVATION_TICKS: 8, // Start trailing after +8 ticks profit
|
|
154
|
+
TRAILING_ACTIVATION_TICKS: 8, // Start trailing after +8 ticks profit (+$40 on NQ)
|
|
151
155
|
TRAILING_DISTANCE_TICKS: 4, // Trail 4 ticks behind high/low
|
|
152
156
|
|
|
153
157
|
// Position monitoring
|
|
@@ -315,68 +315,57 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
315
315
|
|
|
316
316
|
positionManager.start();
|
|
317
317
|
|
|
318
|
-
// Listen for position manager events
|
|
318
|
+
// Listen for position manager events - CLEAN LOGS (no verbose order status)
|
|
319
319
|
positionManager.on('entryFilled', ({ orderTag, position, fillLatencyMs }) => {
|
|
320
320
|
stats.entryLatencies.push(fillLatencyMs);
|
|
321
321
|
stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
|
|
322
|
-
|
|
322
|
+
const side = position.side === 0 ? 'LONG' : 'SHORT';
|
|
323
|
+
algoLogger.info(ui, 'FILLED', `${side} ${position.size}x ${symbolName} @ ${position.entryPrice} | ${fillLatencyMs}ms`);
|
|
323
324
|
});
|
|
324
325
|
|
|
325
326
|
positionManager.on('exitFilled', ({ orderTag, exitPrice, pnlTicks, holdDurationMs }) => {
|
|
327
|
+
const holdSec = (holdDurationMs / 1000).toFixed(1);
|
|
326
328
|
// Calculate PnL in dollars only if tickValue is available from API
|
|
327
329
|
if (pnlTicks !== null && tickValue !== null) {
|
|
328
330
|
const pnlDollars = pnlTicks * tickValue;
|
|
329
331
|
stats.sessionPnl += pnlDollars; // Track session P&L
|
|
330
332
|
if (pnlDollars >= 0) {
|
|
331
333
|
stats.wins++;
|
|
332
|
-
algoLogger.
|
|
334
|
+
algoLogger.info(ui, 'WIN', `+$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
333
335
|
} else {
|
|
334
336
|
stats.losses++;
|
|
335
|
-
algoLogger.
|
|
337
|
+
algoLogger.info(ui, 'LOSS', `-$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
336
338
|
}
|
|
337
339
|
} else {
|
|
338
340
|
// Log with ticks only if tickValue unavailable
|
|
339
341
|
if (pnlTicks !== null && pnlTicks >= 0) {
|
|
340
342
|
stats.wins++;
|
|
341
|
-
algoLogger.info(ui, '
|
|
343
|
+
algoLogger.info(ui, 'WIN', `+${pnlTicks} ticks | ${holdSec}s`);
|
|
342
344
|
} else if (pnlTicks !== null) {
|
|
343
345
|
stats.losses++;
|
|
344
|
-
algoLogger.info(ui, '
|
|
346
|
+
algoLogger.info(ui, 'LOSS', `${pnlTicks} ticks | ${holdSec}s`);
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
stats.trades++;
|
|
348
350
|
currentPosition = 0;
|
|
349
351
|
stats.position = 0; // Reset UI position display
|
|
350
352
|
pendingOrder = false;
|
|
351
|
-
algoLogger.info(ui, 'HOLD TIME', `${(holdDurationMs / 1000).toFixed(1)}s`);
|
|
352
353
|
});
|
|
353
354
|
|
|
354
355
|
positionManager.on('holdComplete', ({ orderTag, position }) => {
|
|
355
|
-
algoLogger.info(ui, '
|
|
356
|
+
algoLogger.info(ui, 'READY', `Hold complete - monitoring exit`);
|
|
356
357
|
});
|
|
357
358
|
|
|
358
|
-
positionManager.on('
|
|
359
|
-
algoLogger.info(ui, '
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// DEBUG: Listen for raw order notifications from Rithmic
|
|
363
|
-
service.on('orderAccepted', (data) => {
|
|
364
|
-
algoLogger.info(ui, 'ORDER ACCEPTED', `tag=${data.orderTag} basket=${data.basketId}`);
|
|
359
|
+
positionManager.on('breakevenActivated', ({ orderTag, position, breakevenPrice, pnlTicks }) => {
|
|
360
|
+
algoLogger.info(ui, 'BE', `Breakeven activated @ ${breakevenPrice} | +${pnlTicks} ticks`);
|
|
365
361
|
});
|
|
366
362
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const fillQty = data.fillQuantity || data.totalFillQuantity || 0;
|
|
370
|
-
if (fillQty > 0) {
|
|
371
|
-
algoLogger.info(ui, 'ORDER FILL', `tag=${data.orderTag} qty=${fillQty} @ ${data.avgFillPrice}`);
|
|
372
|
-
} else {
|
|
373
|
-
algoLogger.info(ui, 'ORDER STATUS', `tag=${data.orderTag} status=${status}`);
|
|
374
|
-
}
|
|
363
|
+
positionManager.on('exitOrderFired', ({ orderTag, exitReason, latencyMs }) => {
|
|
364
|
+
// Don't log here - exitFilled will log the result
|
|
375
365
|
});
|
|
376
366
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
});
|
|
367
|
+
// NOTE: Removed verbose DEBUG logs (ORDER ACCEPTED, ORDER STATUS, ORDER FILL, FILL CONFIRMED)
|
|
368
|
+
// Entry/Exit fills are logged by positionManager events (entryFilled, exitFilled)
|
|
380
369
|
}
|
|
381
370
|
|
|
382
371
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -539,11 +528,11 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
539
528
|
}
|
|
540
529
|
|
|
541
530
|
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
542
|
-
algoLogger.positionSized(ui, contracts, kelly, riskAmount, riskPct);
|
|
543
531
|
|
|
544
532
|
// Place order via API
|
|
545
533
|
pendingOrder = true;
|
|
546
534
|
const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
|
|
535
|
+
const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
|
|
547
536
|
|
|
548
537
|
try {
|
|
549
538
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -559,49 +548,29 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
559
548
|
};
|
|
560
549
|
|
|
561
550
|
// CRITICAL: Use rithmicAccountId (original) not accountId (hash) for Rithmic orders
|
|
562
|
-
// The accountId field is hashed for display, but Rithmic API needs the original
|
|
563
551
|
if (account.rithmicAccountId) {
|
|
564
552
|
orderData.accountId = account.rithmicAccountId;
|
|
565
553
|
}
|
|
566
554
|
|
|
567
|
-
// Log
|
|
568
|
-
algoLogger.info(ui, '
|
|
555
|
+
// Log entry attempt (single line)
|
|
556
|
+
algoLogger.info(ui, 'ENTRY', `${sideStr} ${contracts}x ${symbolName} | risk: $${riskAmount} (${riskPct}%)`);
|
|
569
557
|
|
|
570
558
|
// Fire-and-forget entry (no await on fill)
|
|
571
559
|
const entryResult = service.fastEntry(orderData);
|
|
572
560
|
|
|
573
|
-
// Log the order tag for tracking
|
|
574
|
-
algoLogger.info(ui, 'ORDER TAG', `${entryResult.orderTag} | success=${entryResult.success}`);
|
|
575
|
-
|
|
576
561
|
if (entryResult.success) {
|
|
577
562
|
// Register with position manager for lifecycle tracking
|
|
578
|
-
|
|
579
|
-
const contractInfo = {
|
|
580
|
-
tickSize,
|
|
581
|
-
tickValue,
|
|
582
|
-
contractId,
|
|
583
|
-
};
|
|
563
|
+
const contractInfo = { tickSize, tickValue, contractId };
|
|
584
564
|
positionManager.registerEntry(entryResult, orderData, contractInfo);
|
|
585
565
|
|
|
586
566
|
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
587
|
-
const sideStr = direction === 'long' ? 'BUY' : 'SELL';
|
|
588
|
-
|
|
589
|
-
// Log with latency
|
|
590
|
-
const latencyColor = entryResult.latencyMs < FAST_SCALPING.LATENCY_TARGET_MS
|
|
591
|
-
? chalk.green
|
|
592
|
-
: entryResult.latencyMs < FAST_SCALPING.LATENCY_WARN_MS
|
|
593
|
-
? chalk.yellow
|
|
594
|
-
: chalk.red;
|
|
595
567
|
|
|
568
|
+
// Update avg entry latency
|
|
596
569
|
stats.avgEntryLatency = stats.entryLatencies.length > 0
|
|
597
570
|
? (stats.avgEntryLatency * stats.entryLatencies.length + entryResult.latencyMs) / (stats.entryLatencies.length + 1)
|
|
598
571
|
: entryResult.latencyMs;
|
|
599
572
|
|
|
600
|
-
|
|
601
|
-
algoLogger.info(ui, 'HOLD START', `Min ${FAST_SCALPING.MIN_HOLD_MS / 1000}s before exit`);
|
|
602
|
-
|
|
603
|
-
// Note: NO bracket orders in fast path
|
|
604
|
-
// PositionManager handles exit logic after 10s hold
|
|
573
|
+
// Note: Fill confirmation logged by positionManager.on('entryFilled')
|
|
605
574
|
|
|
606
575
|
} else {
|
|
607
576
|
algoLogger.orderRejected(ui, symbolName, entryResult.error || 'Fast entry failed');
|
|
@@ -58,6 +58,8 @@ const WEIGHTS = {
|
|
|
58
58
|
* @property {number} lowWaterMark - Lowest price since entry (for trailing)
|
|
59
59
|
* @property {string} status - 'pending' | 'active' | 'holding' | 'exiting' | 'closed'
|
|
60
60
|
* @property {boolean} holdComplete - True after MIN_HOLD_MS elapsed
|
|
61
|
+
* @property {boolean} breakevenActive - True after BE threshold reached
|
|
62
|
+
* @property {number|null} breakevenPrice - Price at which BE stop is set
|
|
61
63
|
* @property {Object|null} exitReason - Why position was exited
|
|
62
64
|
* @property {number} tickSize - Tick size from API
|
|
63
65
|
* @property {number} tickValue - Tick value from API
|
|
@@ -214,6 +216,8 @@ class PositionManager extends EventEmitter {
|
|
|
214
216
|
lowWaterMark: null,
|
|
215
217
|
status: 'pending', // Waiting for fill confirmation
|
|
216
218
|
holdComplete: false,
|
|
219
|
+
breakevenActive: false, // BE not yet activated
|
|
220
|
+
breakevenPrice: null, // Will be set when BE activates
|
|
217
221
|
exitReason: null,
|
|
218
222
|
latencyMs,
|
|
219
223
|
// Contract info from API (NOT hardcoded)
|
|
@@ -483,18 +487,64 @@ class PositionManager extends EventEmitter {
|
|
|
483
487
|
return { type: 'target', reason: 'Target reached', pnlTicks };
|
|
484
488
|
}
|
|
485
489
|
|
|
486
|
-
// 2.
|
|
487
|
-
if (
|
|
490
|
+
// 2. BREAKEVEN CHECK - If BE is active, use BE price as stop
|
|
491
|
+
if (position.breakevenActive && position.breakevenPrice !== null) {
|
|
492
|
+
const tickSize = this._getTickSize(position);
|
|
493
|
+
if (tickSize) {
|
|
494
|
+
// Check if price hit breakeven stop
|
|
495
|
+
if (position.side === 0) { // Long
|
|
496
|
+
if (currentPrice <= position.breakevenPrice) {
|
|
497
|
+
return { type: 'breakeven', reason: 'Breakeven stop hit', pnlTicks };
|
|
498
|
+
}
|
|
499
|
+
} else { // Short
|
|
500
|
+
if (currentPrice >= position.breakevenPrice) {
|
|
501
|
+
return { type: 'breakeven', reason: 'Breakeven stop hit', pnlTicks };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 3. STOP HIT - Only if BE not active (original stop)
|
|
508
|
+
if (!position.breakevenActive && pnlTicks <= -stopTicks) {
|
|
488
509
|
return { type: 'stop', reason: 'Stop loss hit', pnlTicks };
|
|
489
510
|
}
|
|
490
511
|
|
|
491
|
-
//
|
|
512
|
+
// 4. ACTIVATE BREAKEVEN - Move stop to entry after profit threshold
|
|
513
|
+
if (!position.breakevenActive && pnlTicks >= FAST_SCALPING.BREAKEVEN_ACTIVATION_TICKS) {
|
|
514
|
+
const tickSize = this._getTickSize(position);
|
|
515
|
+
if (tickSize && position.entryPrice) {
|
|
516
|
+
// Set BE price at entry + offset (small profit lock)
|
|
517
|
+
const offset = FAST_SCALPING.BREAKEVEN_OFFSET_TICKS * tickSize;
|
|
518
|
+
if (position.side === 0) { // Long
|
|
519
|
+
position.breakevenPrice = position.entryPrice + offset;
|
|
520
|
+
} else { // Short
|
|
521
|
+
position.breakevenPrice = position.entryPrice - offset;
|
|
522
|
+
}
|
|
523
|
+
position.breakevenActive = true;
|
|
524
|
+
|
|
525
|
+
log.info('BREAKEVEN ACTIVATED', {
|
|
526
|
+
symbol: position.symbol,
|
|
527
|
+
entryPrice: position.entryPrice,
|
|
528
|
+
bePrice: position.breakevenPrice,
|
|
529
|
+
pnlTicks,
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
this.emit('breakevenActivated', {
|
|
533
|
+
orderTag: position.orderTag,
|
|
534
|
+
position,
|
|
535
|
+
breakevenPrice: position.breakevenPrice,
|
|
536
|
+
pnlTicks,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// 5. VPIN DANGER - Informed traders detected (from strategy)
|
|
492
542
|
const vpin = this._getVPIN(position);
|
|
493
543
|
if (vpin !== null && vpin > MOMENTUM.VPIN_DANGER) {
|
|
494
544
|
return { type: 'vpin', reason: `VPIN spike ${(vpin * 100).toFixed(0)}% - informed traders`, pnlTicks, vpin };
|
|
495
545
|
}
|
|
496
546
|
|
|
497
|
-
//
|
|
547
|
+
// 6. TRAILING STOP (only if in profit above threshold)
|
|
498
548
|
if (pnlTicks >= FAST_SCALPING.TRAILING_ACTIVATION_TICKS) {
|
|
499
549
|
const trailingPnl = this._calculateTrailingPnl(position, currentPrice);
|
|
500
550
|
if (trailingPnl !== null && trailingPnl <= -FAST_SCALPING.TRAILING_DISTANCE_TICKS) {
|
|
@@ -502,7 +552,7 @@ class PositionManager extends EventEmitter {
|
|
|
502
552
|
}
|
|
503
553
|
}
|
|
504
554
|
|
|
505
|
-
//
|
|
555
|
+
// 7. MOMENTUM-BASED EXIT (using strategy's math models)
|
|
506
556
|
const momentum = this._calculateMomentum(position);
|
|
507
557
|
|
|
508
558
|
if (momentum !== null) {
|