backtest-kit 4.0.2 → 5.1.0
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/build/index.cjs +1025 -146
- package/build/index.mjs +1025 -148
- package/package.json +1 -1
- package/types.d.ts +624 -30
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, singlerun } from 'functools-kit';
|
|
3
|
+
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, BehaviorSubject, waitForNext, singlerun } from 'functools-kit';
|
|
4
4
|
import * as fs from 'fs/promises';
|
|
5
5
|
import fs__default, { stat, opendir, readFile } from 'fs/promises';
|
|
6
6
|
import path, { join, dirname } from 'path';
|
|
@@ -69,6 +69,10 @@ const coreServices$1 = {
|
|
|
69
69
|
actionCoreService: Symbol('actionCoreService'),
|
|
70
70
|
frameCoreService: Symbol('frameCoreService'),
|
|
71
71
|
};
|
|
72
|
+
const metaServices$1 = {
|
|
73
|
+
priceMetaService: Symbol('priceMetaService'),
|
|
74
|
+
timeMetaService: Symbol('timeMetaService'),
|
|
75
|
+
};
|
|
72
76
|
const globalServices$1 = {
|
|
73
77
|
sizingGlobalService: Symbol('sizingGlobalService'),
|
|
74
78
|
riskGlobalService: Symbol('riskGlobalService'),
|
|
@@ -134,6 +138,7 @@ const TYPES = {
|
|
|
134
138
|
...connectionServices$1,
|
|
135
139
|
...schemaServices$1,
|
|
136
140
|
...coreServices$1,
|
|
141
|
+
...metaServices$1,
|
|
137
142
|
...globalServices$1,
|
|
138
143
|
...commandServices$1,
|
|
139
144
|
...logicPrivateServices$1,
|
|
@@ -450,9 +455,17 @@ const GLOBAL_CONFIG = {
|
|
|
450
455
|
* Allows to commitAverageBuy if currentPrice is not the lowest price since entry, but still lower than priceOpen.
|
|
451
456
|
* This can help improve average entry price in cases where price has rebounded after entry but is still below priceOpen, without waiting for a new lower price.
|
|
452
457
|
*
|
|
453
|
-
* Default:
|
|
458
|
+
* Default: false (DCA logic enabled only when antirecord is broken)
|
|
454
459
|
*/
|
|
455
460
|
CC_ENABLE_DCA_EVERYWHERE: false,
|
|
461
|
+
/**
|
|
462
|
+
* Enables PPPL (Partial Profit, Partial Loss) logic even if this breaks a direction of exits
|
|
463
|
+
* Allows to take partial profit or loss on a position even if it results in a mix of profit and loss exits
|
|
464
|
+
* This can help lock in profits or cut losses on part of the position without waiting for a perfect exit scenario.
|
|
465
|
+
*
|
|
466
|
+
* Default: false (PPPL logic is only applied when it does not break the direction of exits, ensuring clearer profit/loss outcomes)
|
|
467
|
+
*/
|
|
468
|
+
CC_ENABLE_PPPL_EVERYWHERE: false,
|
|
456
469
|
/**
|
|
457
470
|
* Cost of entering a position (in USD).
|
|
458
471
|
* This is used as a default value for calculating position size and risk management when cost data is not provided by the strategy
|
|
@@ -3530,19 +3543,6 @@ const beginTime = (run) => (...args) => {
|
|
|
3530
3543
|
return fn();
|
|
3531
3544
|
};
|
|
3532
3545
|
|
|
3533
|
-
/**
|
|
3534
|
-
* Retrieves the current timestamp for debugging purposes.
|
|
3535
|
-
* If an execution context is active (e.g., during a backtest), it returns the timestamp from the context to ensure consistency with the simulated time.
|
|
3536
|
-
* Can be empty (undefined) if not called from strategy async context, as it's intended for debugging and not critical for logic.
|
|
3537
|
-
* @return {number | undefined} The current timestamp in milliseconds from the execution context, or undefined if not available.
|
|
3538
|
-
*/
|
|
3539
|
-
const getDebugTimestamp = () => {
|
|
3540
|
-
if (ExecutionContextService.hasContext()) {
|
|
3541
|
-
return bt.executionContextService.context.when.getTime();
|
|
3542
|
-
}
|
|
3543
|
-
return undefined;
|
|
3544
|
-
};
|
|
3545
|
-
|
|
3546
3546
|
const INTERVAL_MINUTES$6 = {
|
|
3547
3547
|
"1m": 1,
|
|
3548
3548
|
"3m": 3,
|
|
@@ -4250,7 +4250,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4250
4250
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
4251
4251
|
timestamp: currentTime,
|
|
4252
4252
|
_isScheduled: false,
|
|
4253
|
-
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4253
|
+
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4254
4254
|
};
|
|
4255
4255
|
// Валидируем сигнал перед возвратом
|
|
4256
4256
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4274,7 +4274,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4274
4274
|
pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
|
|
4275
4275
|
timestamp: currentTime,
|
|
4276
4276
|
_isScheduled: true,
|
|
4277
|
-
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4277
|
+
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4278
4278
|
};
|
|
4279
4279
|
// Валидируем сигнал перед возвратом
|
|
4280
4280
|
VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
|
|
@@ -4294,7 +4294,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4294
4294
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
4295
4295
|
timestamp: currentTime,
|
|
4296
4296
|
_isScheduled: false,
|
|
4297
|
-
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4297
|
+
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4298
4298
|
};
|
|
4299
4299
|
// Валидируем сигнал перед возвратом
|
|
4300
4300
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4364,7 +4364,7 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
|
|
|
4364
4364
|
self.params.logger.debug("ClientStrategy dispose");
|
|
4365
4365
|
await self.params.onDispose(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName, self.params.method.context.frameName, self.params.execution.context.backtest);
|
|
4366
4366
|
};
|
|
4367
|
-
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
4367
|
+
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
4368
4368
|
// Initialize partial array if not present
|
|
4369
4369
|
if (!signal._partial)
|
|
4370
4370
|
signal._partial = [];
|
|
@@ -4393,7 +4393,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4393
4393
|
entryCountAtClose,
|
|
4394
4394
|
currentPrice,
|
|
4395
4395
|
costBasisAtClose: remainingCostBasis,
|
|
4396
|
-
|
|
4396
|
+
timestamp,
|
|
4397
4397
|
});
|
|
4398
4398
|
self.params.logger.info("PARTIAL_PROFIT_FN executed", {
|
|
4399
4399
|
signalId: signal.id,
|
|
@@ -4403,7 +4403,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4403
4403
|
});
|
|
4404
4404
|
return true;
|
|
4405
4405
|
};
|
|
4406
|
-
const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
4406
|
+
const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
4407
4407
|
// Initialize partial array if not present
|
|
4408
4408
|
if (!signal._partial)
|
|
4409
4409
|
signal._partial = [];
|
|
@@ -4431,7 +4431,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4431
4431
|
currentPrice,
|
|
4432
4432
|
entryCountAtClose,
|
|
4433
4433
|
costBasisAtClose: remainingCostBasis,
|
|
4434
|
-
|
|
4434
|
+
timestamp,
|
|
4435
4435
|
});
|
|
4436
4436
|
self.params.logger.warn("PARTIAL_LOSS_FN executed", {
|
|
4437
4437
|
signalId: signal.id,
|
|
@@ -4828,10 +4828,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4828
4828
|
});
|
|
4829
4829
|
return true;
|
|
4830
4830
|
};
|
|
4831
|
-
const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
|
|
4831
|
+
const AVERAGE_BUY_FN = (self, signal, currentPrice, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
|
|
4832
4832
|
// Ensure _entry is initialized (handles signals loaded from disk without _entry)
|
|
4833
4833
|
if (!signal._entry || signal._entry.length === 0) {
|
|
4834
|
-
signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4834
|
+
signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
|
|
4835
4835
|
}
|
|
4836
4836
|
if (signal.position === "long") {
|
|
4837
4837
|
// LONG: new entry must beat the all-time low — strictly below every prior entry price
|
|
@@ -4861,7 +4861,7 @@ const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSI
|
|
|
4861
4861
|
return false;
|
|
4862
4862
|
}
|
|
4863
4863
|
}
|
|
4864
|
-
signal._entry.push({ price: currentPrice, cost,
|
|
4864
|
+
signal._entry.push({ price: currentPrice, cost, timestamp });
|
|
4865
4865
|
self.params.logger.info("AVERAGE_BUY_FN executed", {
|
|
4866
4866
|
signalId: signal.id,
|
|
4867
4867
|
position: signal.position,
|
|
@@ -6646,16 +6646,16 @@ class ClientStrategy {
|
|
|
6646
6646
|
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
6647
6647
|
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
6648
6648
|
*/
|
|
6649
|
-
async getPositionEntries(symbol) {
|
|
6649
|
+
async getPositionEntries(symbol, timestamp) {
|
|
6650
6650
|
this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
|
|
6651
6651
|
if (!this._pendingSignal) {
|
|
6652
6652
|
return null;
|
|
6653
6653
|
}
|
|
6654
6654
|
const entries = this._pendingSignal._entry;
|
|
6655
6655
|
if (!entries || entries.length === 0) {
|
|
6656
|
-
return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }];
|
|
6656
|
+
return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
|
|
6657
6657
|
}
|
|
6658
|
-
return entries.map(({ price, cost }) => ({ price, cost }));
|
|
6658
|
+
return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
|
|
6659
6659
|
}
|
|
6660
6660
|
/**
|
|
6661
6661
|
* Performs a single tick of strategy execution.
|
|
@@ -7428,10 +7428,12 @@ class ClientStrategy {
|
|
|
7428
7428
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7429
7429
|
return false;
|
|
7430
7430
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7431
|
-
if (
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7431
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7432
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
|
|
7433
|
+
return false;
|
|
7434
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
|
|
7435
|
+
return false;
|
|
7436
|
+
}
|
|
7435
7437
|
const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
|
|
7436
7438
|
if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
|
|
7437
7439
|
return false;
|
|
@@ -7489,7 +7491,7 @@ class ClientStrategy {
|
|
|
7489
7491
|
* // success3 = false (skipped, would exceed 100%)
|
|
7490
7492
|
* ```
|
|
7491
7493
|
*/
|
|
7492
|
-
async partialProfit(symbol, percentToClose, currentPrice, backtest) {
|
|
7494
|
+
async partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp) {
|
|
7493
7495
|
this.params.logger.debug("ClientStrategy partialProfit", {
|
|
7494
7496
|
symbol,
|
|
7495
7497
|
percentToClose,
|
|
@@ -7515,7 +7517,7 @@ class ClientStrategy {
|
|
|
7515
7517
|
throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7516
7518
|
}
|
|
7517
7519
|
// Validation: currentPrice must be moving toward TP (profit direction)
|
|
7518
|
-
{
|
|
7520
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7519
7521
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7520
7522
|
if (this._pendingSignal.position === "long") {
|
|
7521
7523
|
// For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
|
|
@@ -7553,7 +7555,7 @@ class ClientStrategy {
|
|
|
7553
7555
|
return false;
|
|
7554
7556
|
}
|
|
7555
7557
|
// Execute partial close logic
|
|
7556
|
-
const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
|
|
7558
|
+
const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
|
|
7557
7559
|
// If partial was not executed (exceeded 100%), return false without persistence
|
|
7558
7560
|
if (!wasExecuted) {
|
|
7559
7561
|
return false;
|
|
@@ -7609,10 +7611,12 @@ class ClientStrategy {
|
|
|
7609
7611
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7610
7612
|
return false;
|
|
7611
7613
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7612
|
-
if (
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7614
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7615
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
|
|
7616
|
+
return false;
|
|
7617
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
|
|
7618
|
+
return false;
|
|
7619
|
+
}
|
|
7616
7620
|
const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
|
|
7617
7621
|
if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
|
|
7618
7622
|
return false;
|
|
@@ -7670,7 +7674,7 @@ class ClientStrategy {
|
|
|
7670
7674
|
* // success3 = false (skipped, would exceed 100%)
|
|
7671
7675
|
* ```
|
|
7672
7676
|
*/
|
|
7673
|
-
async partialLoss(symbol, percentToClose, currentPrice, backtest) {
|
|
7677
|
+
async partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp) {
|
|
7674
7678
|
this.params.logger.debug("ClientStrategy partialLoss", {
|
|
7675
7679
|
symbol,
|
|
7676
7680
|
percentToClose,
|
|
@@ -7696,7 +7700,7 @@ class ClientStrategy {
|
|
|
7696
7700
|
throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7697
7701
|
}
|
|
7698
7702
|
// Validation: currentPrice must be moving toward SL (loss direction)
|
|
7699
|
-
{
|
|
7703
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7700
7704
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7701
7705
|
if (this._pendingSignal.position === "long") {
|
|
7702
7706
|
// For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
|
|
@@ -7734,7 +7738,7 @@ class ClientStrategy {
|
|
|
7734
7738
|
return false;
|
|
7735
7739
|
}
|
|
7736
7740
|
// Execute partial close logic
|
|
7737
|
-
const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
|
|
7741
|
+
const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
|
|
7738
7742
|
// If partial was not executed (exceeded 100%), return false without persistence
|
|
7739
7743
|
if (!wasExecuted) {
|
|
7740
7744
|
return false;
|
|
@@ -8490,7 +8494,7 @@ class ClientStrategy {
|
|
|
8490
8494
|
* @param backtest - Whether running in backtest mode
|
|
8491
8495
|
* @returns Promise<boolean> - true if entry added, false if rejected by direction check
|
|
8492
8496
|
*/
|
|
8493
|
-
async averageBuy(symbol, currentPrice, backtest, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
|
|
8497
|
+
async averageBuy(symbol, currentPrice, backtest, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
|
|
8494
8498
|
this.params.logger.debug("ClientStrategy averageBuy", {
|
|
8495
8499
|
symbol,
|
|
8496
8500
|
currentPrice,
|
|
@@ -8505,7 +8509,7 @@ class ClientStrategy {
|
|
|
8505
8509
|
throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
8506
8510
|
}
|
|
8507
8511
|
// Execute averaging logic
|
|
8508
|
-
const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, cost);
|
|
8512
|
+
const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, timestamp, cost);
|
|
8509
8513
|
if (!result) {
|
|
8510
8514
|
return false;
|
|
8511
8515
|
}
|
|
@@ -8969,7 +8973,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
8969
8973
|
* @param backtest - Whether running in backtest mode
|
|
8970
8974
|
* @returns Unique string key for memoization
|
|
8971
8975
|
*/
|
|
8972
|
-
const CREATE_KEY_FN$
|
|
8976
|
+
const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
8973
8977
|
const parts = [symbol, strategyName, exchangeName];
|
|
8974
8978
|
if (frameName)
|
|
8975
8979
|
parts.push(frameName);
|
|
@@ -9146,6 +9150,8 @@ class StrategyConnectionService {
|
|
|
9146
9150
|
this.partialConnectionService = inject(TYPES.partialConnectionService);
|
|
9147
9151
|
this.breakevenConnectionService = inject(TYPES.breakevenConnectionService);
|
|
9148
9152
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
9153
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
9154
|
+
this.priceMetaService = inject(TYPES.priceMetaService);
|
|
9149
9155
|
/**
|
|
9150
9156
|
* Retrieves memoized ClientStrategy instance for given symbol-strategy pair with exchange and frame isolation.
|
|
9151
9157
|
*
|
|
@@ -9159,7 +9165,7 @@ class StrategyConnectionService {
|
|
|
9159
9165
|
* @param backtest - Whether running in backtest mode
|
|
9160
9166
|
* @returns Configured ClientStrategy instance
|
|
9161
9167
|
*/
|
|
9162
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9168
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9163
9169
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
9164
9170
|
return new ClientStrategy({
|
|
9165
9171
|
symbol,
|
|
@@ -9246,6 +9252,20 @@ class StrategyConnectionService {
|
|
|
9246
9252
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9247
9253
|
return await strategy.getTotalCostClosed(symbol);
|
|
9248
9254
|
};
|
|
9255
|
+
/**
|
|
9256
|
+
* Returns the effective (DCA-averaged) entry price for the current pending signal.
|
|
9257
|
+
*
|
|
9258
|
+
* This is the harmonic mean of all _entry prices, which is the correct
|
|
9259
|
+
* cost-basis price used in all PNL calculations.
|
|
9260
|
+
* With no DCA entries, equals the original priceOpen.
|
|
9261
|
+
*
|
|
9262
|
+
* Returns null if no pending signal exists.
|
|
9263
|
+
*
|
|
9264
|
+
* @param backtest - Whether running in backtest mode
|
|
9265
|
+
* @param symbol - Trading pair symbol
|
|
9266
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9267
|
+
* @returns Promise resolving to effective entry price or null
|
|
9268
|
+
*/
|
|
9249
9269
|
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
9250
9270
|
this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
|
|
9251
9271
|
symbol,
|
|
@@ -9255,6 +9275,19 @@ class StrategyConnectionService {
|
|
|
9255
9275
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9256
9276
|
return await strategy.getPositionAveragePrice(symbol);
|
|
9257
9277
|
};
|
|
9278
|
+
/**
|
|
9279
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
9280
|
+
*
|
|
9281
|
+
* 1 = original entry only (no DCA).
|
|
9282
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
9283
|
+
*
|
|
9284
|
+
* Returns null if no pending signal exists.
|
|
9285
|
+
*
|
|
9286
|
+
* @param backtest - Whether running in backtest mode
|
|
9287
|
+
* @param symbol - Trading pair symbol
|
|
9288
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9289
|
+
* @returns Promise resolving to entry count or null
|
|
9290
|
+
*/
|
|
9258
9291
|
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
9259
9292
|
this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
|
|
9260
9293
|
symbol,
|
|
@@ -9264,6 +9297,19 @@ class StrategyConnectionService {
|
|
|
9264
9297
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9265
9298
|
return await strategy.getPositionInvestedCount(symbol);
|
|
9266
9299
|
};
|
|
9300
|
+
/**
|
|
9301
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
9302
|
+
*
|
|
9303
|
+
* Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
|
|
9304
|
+
* 1 entry = $100, 2 entries = $200, etc.
|
|
9305
|
+
*
|
|
9306
|
+
* Returns null if no pending signal exists.
|
|
9307
|
+
*
|
|
9308
|
+
* @param backtest - Whether running in backtest mode
|
|
9309
|
+
* @param symbol - Trading pair symbol
|
|
9310
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9311
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
9312
|
+
*/
|
|
9267
9313
|
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
9268
9314
|
this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
|
|
9269
9315
|
symbol,
|
|
@@ -9273,6 +9319,20 @@ class StrategyConnectionService {
|
|
|
9273
9319
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9274
9320
|
return await strategy.getPositionInvestedCost(symbol);
|
|
9275
9321
|
};
|
|
9322
|
+
/**
|
|
9323
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
9324
|
+
*
|
|
9325
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
9326
|
+
* (delegates to toProfitLossDto).
|
|
9327
|
+
*
|
|
9328
|
+
* Returns null if no pending signal exists.
|
|
9329
|
+
*
|
|
9330
|
+
* @param backtest - Whether running in backtest mode
|
|
9331
|
+
* @param symbol - Trading pair symbol
|
|
9332
|
+
* @param currentPrice - Current market price
|
|
9333
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9334
|
+
* @returns Promise resolving to pnlPercentage or null
|
|
9335
|
+
*/
|
|
9276
9336
|
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
9277
9337
|
this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
|
|
9278
9338
|
symbol,
|
|
@@ -9283,6 +9343,20 @@ class StrategyConnectionService {
|
|
|
9283
9343
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9284
9344
|
return await strategy.getPositionPnlPercent(symbol, currentPrice);
|
|
9285
9345
|
};
|
|
9346
|
+
/**
|
|
9347
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
9348
|
+
*
|
|
9349
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost
|
|
9350
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
9351
|
+
*
|
|
9352
|
+
* Returns null if no pending signal exists.
|
|
9353
|
+
*
|
|
9354
|
+
* @param backtest - Whether running in backtest mode
|
|
9355
|
+
* @param symbol - Trading pair symbol
|
|
9356
|
+
* @param currentPrice - Current market price
|
|
9357
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9358
|
+
* @returns Promise resolving to pnl in dollars or null
|
|
9359
|
+
*/
|
|
9286
9360
|
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
9287
9361
|
this.loggerService.log("strategyConnectionService getPositionPnlCost", {
|
|
9288
9362
|
symbol,
|
|
@@ -9293,6 +9367,27 @@ class StrategyConnectionService {
|
|
|
9293
9367
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9294
9368
|
return await strategy.getPositionPnlCost(symbol, currentPrice);
|
|
9295
9369
|
};
|
|
9370
|
+
/**
|
|
9371
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
9372
|
+
*
|
|
9373
|
+
* The first element is always the original priceOpen (initial entry).
|
|
9374
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
9375
|
+
*
|
|
9376
|
+
* Returns null if no pending signal exists.
|
|
9377
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
9378
|
+
*
|
|
9379
|
+
* @param backtest - Whether running in backtest mode
|
|
9380
|
+
* @param symbol - Trading pair symbol
|
|
9381
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9382
|
+
* @returns Promise resolving to array of entry prices or null
|
|
9383
|
+
*
|
|
9384
|
+
* @example
|
|
9385
|
+
* ```typescript
|
|
9386
|
+
* // No DCA: [43000]
|
|
9387
|
+
* // One DCA: [43000, 42000]
|
|
9388
|
+
* // Two DCA: [43000, 42000, 41500]
|
|
9389
|
+
* ```
|
|
9390
|
+
*/
|
|
9296
9391
|
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
9297
9392
|
this.loggerService.log("strategyConnectionService getPositionLevels", {
|
|
9298
9393
|
symbol,
|
|
@@ -9302,6 +9397,20 @@ class StrategyConnectionService {
|
|
|
9302
9397
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9303
9398
|
return await strategy.getPositionLevels(symbol);
|
|
9304
9399
|
};
|
|
9400
|
+
/**
|
|
9401
|
+
* Returns the list of partial closes for the current pending signal.
|
|
9402
|
+
*
|
|
9403
|
+
* Each entry records a partial profit or loss close event with its type,
|
|
9404
|
+
* percent closed, price at close, cost basis snapshot, and entry count at close.
|
|
9405
|
+
*
|
|
9406
|
+
* Returns null if no pending signal exists.
|
|
9407
|
+
* Returns an empty array if no partial closes have been executed.
|
|
9408
|
+
*
|
|
9409
|
+
* @param backtest - Whether running in backtest mode
|
|
9410
|
+
* @param symbol - Trading pair symbol
|
|
9411
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9412
|
+
* @returns Promise resolving to array of partial close records or null
|
|
9413
|
+
*/
|
|
9305
9414
|
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
9306
9415
|
this.loggerService.log("strategyConnectionService getPositionPartials", {
|
|
9307
9416
|
symbol,
|
|
@@ -9311,6 +9420,27 @@ class StrategyConnectionService {
|
|
|
9311
9420
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9312
9421
|
return await strategy.getPositionPartials(symbol);
|
|
9313
9422
|
};
|
|
9423
|
+
/**
|
|
9424
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
9425
|
+
*
|
|
9426
|
+
* Each entry records the price and cost of a single position entry.
|
|
9427
|
+
* The first element is always the original priceOpen (initial entry).
|
|
9428
|
+
* Each subsequent element is an entry added by averageBuy().
|
|
9429
|
+
*
|
|
9430
|
+
* Returns null if no pending signal exists.
|
|
9431
|
+
* Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
|
|
9432
|
+
*
|
|
9433
|
+
* @param backtest - Whether running in backtest mode
|
|
9434
|
+
* @param symbol - Trading pair symbol
|
|
9435
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9436
|
+
* @returns Promise resolving to array of entry records or null
|
|
9437
|
+
*
|
|
9438
|
+
* @example
|
|
9439
|
+
* ```typescript
|
|
9440
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
9441
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
9442
|
+
* ```
|
|
9443
|
+
*/
|
|
9314
9444
|
this.getPositionEntries = async (backtest, symbol, context) => {
|
|
9315
9445
|
this.loggerService.log("strategyConnectionService getPositionEntries", {
|
|
9316
9446
|
symbol,
|
|
@@ -9318,7 +9448,8 @@ class StrategyConnectionService {
|
|
|
9318
9448
|
backtest,
|
|
9319
9449
|
});
|
|
9320
9450
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9321
|
-
|
|
9451
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9452
|
+
return await strategy.getPositionEntries(symbol, timestamp);
|
|
9322
9453
|
};
|
|
9323
9454
|
/**
|
|
9324
9455
|
* Retrieves the currently active scheduled signal for the strategy.
|
|
@@ -9420,6 +9551,10 @@ class StrategyConnectionService {
|
|
|
9420
9551
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9421
9552
|
await strategy.waitForInit();
|
|
9422
9553
|
const tick = await strategy.tick(symbol, context.strategyName);
|
|
9554
|
+
{
|
|
9555
|
+
this.priceMetaService.next(symbol, tick.currentPrice, context, backtest);
|
|
9556
|
+
this.timeMetaService.next(symbol, tick.createdAt, context, backtest);
|
|
9557
|
+
}
|
|
9423
9558
|
{
|
|
9424
9559
|
await CALL_SIGNAL_EMIT_FN(this, tick, context, backtest, symbol);
|
|
9425
9560
|
}
|
|
@@ -9526,7 +9661,7 @@ class StrategyConnectionService {
|
|
|
9526
9661
|
}
|
|
9527
9662
|
return;
|
|
9528
9663
|
}
|
|
9529
|
-
const key = CREATE_KEY_FN$
|
|
9664
|
+
const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9530
9665
|
if (!this.getStrategy.has(key)) {
|
|
9531
9666
|
return;
|
|
9532
9667
|
}
|
|
@@ -9595,9 +9730,9 @@ class StrategyConnectionService {
|
|
|
9595
9730
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9596
9731
|
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
9597
9732
|
*/
|
|
9598
|
-
this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9733
|
+
this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9599
9734
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9600
|
-
return
|
|
9735
|
+
return await strategy.validatePartialProfit(symbol, percentToClose, currentPrice);
|
|
9601
9736
|
};
|
|
9602
9737
|
/**
|
|
9603
9738
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -9638,7 +9773,8 @@ class StrategyConnectionService {
|
|
|
9638
9773
|
backtest,
|
|
9639
9774
|
});
|
|
9640
9775
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9641
|
-
|
|
9776
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9777
|
+
return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp);
|
|
9642
9778
|
};
|
|
9643
9779
|
/**
|
|
9644
9780
|
* Checks whether `partialLoss` would succeed without executing it.
|
|
@@ -9651,9 +9787,9 @@ class StrategyConnectionService {
|
|
|
9651
9787
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9652
9788
|
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
9653
9789
|
*/
|
|
9654
|
-
this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9790
|
+
this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9655
9791
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9656
|
-
return
|
|
9792
|
+
return await strategy.validatePartialLoss(symbol, percentToClose, currentPrice);
|
|
9657
9793
|
};
|
|
9658
9794
|
/**
|
|
9659
9795
|
* Executes partial close at loss level (moving toward SL).
|
|
@@ -9694,7 +9830,8 @@ class StrategyConnectionService {
|
|
|
9694
9830
|
backtest,
|
|
9695
9831
|
});
|
|
9696
9832
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9697
|
-
|
|
9833
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9834
|
+
return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp);
|
|
9698
9835
|
};
|
|
9699
9836
|
/**
|
|
9700
9837
|
* Checks whether `trailingStop` would succeed without executing it.
|
|
@@ -9707,9 +9844,9 @@ class StrategyConnectionService {
|
|
|
9707
9844
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9708
9845
|
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
9709
9846
|
*/
|
|
9710
|
-
this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9847
|
+
this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9711
9848
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9712
|
-
return
|
|
9849
|
+
return await strategy.validateTrailingStop(symbol, percentShift, currentPrice);
|
|
9713
9850
|
};
|
|
9714
9851
|
/**
|
|
9715
9852
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
@@ -9761,9 +9898,9 @@ class StrategyConnectionService {
|
|
|
9761
9898
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9762
9899
|
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
9763
9900
|
*/
|
|
9764
|
-
this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9901
|
+
this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9765
9902
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9766
|
-
return
|
|
9903
|
+
return await strategy.validateTrailingTake(symbol, percentShift, currentPrice);
|
|
9767
9904
|
};
|
|
9768
9905
|
/**
|
|
9769
9906
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
@@ -9814,9 +9951,9 @@ class StrategyConnectionService {
|
|
|
9814
9951
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9815
9952
|
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
9816
9953
|
*/
|
|
9817
|
-
this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
|
|
9954
|
+
this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
9818
9955
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9819
|
-
return
|
|
9956
|
+
return await strategy.validateBreakeven(symbol, currentPrice);
|
|
9820
9957
|
};
|
|
9821
9958
|
/**
|
|
9822
9959
|
* Delegates to ClientStrategy.breakeven() with current execution context.
|
|
@@ -9893,9 +10030,9 @@ class StrategyConnectionService {
|
|
|
9893
10030
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9894
10031
|
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
9895
10032
|
*/
|
|
9896
|
-
this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
|
|
10033
|
+
this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
|
|
9897
10034
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9898
|
-
return
|
|
10035
|
+
return await strategy.validateAverageBuy(symbol, currentPrice);
|
|
9899
10036
|
};
|
|
9900
10037
|
/**
|
|
9901
10038
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -9916,7 +10053,8 @@ class StrategyConnectionService {
|
|
|
9916
10053
|
backtest,
|
|
9917
10054
|
});
|
|
9918
10055
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9919
|
-
|
|
10056
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10057
|
+
return await strategy.averageBuy(symbol, currentPrice, backtest, timestamp, cost);
|
|
9920
10058
|
};
|
|
9921
10059
|
}
|
|
9922
10060
|
}
|
|
@@ -10657,7 +10795,7 @@ class ClientRisk {
|
|
|
10657
10795
|
* @param backtest - Whether running in backtest mode
|
|
10658
10796
|
* @returns Unique string key for memoization
|
|
10659
10797
|
*/
|
|
10660
|
-
const CREATE_KEY_FN$
|
|
10798
|
+
const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
|
|
10661
10799
|
const parts = [riskName, exchangeName];
|
|
10662
10800
|
if (frameName)
|
|
10663
10801
|
parts.push(frameName);
|
|
@@ -10756,7 +10894,7 @@ class RiskConnectionService {
|
|
|
10756
10894
|
* @param backtest - True if backtest mode, false if live mode
|
|
10757
10895
|
* @returns Configured ClientRisk instance
|
|
10758
10896
|
*/
|
|
10759
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10897
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
10760
10898
|
const schema = this.riskSchemaService.get(riskName);
|
|
10761
10899
|
return new ClientRisk({
|
|
10762
10900
|
...schema,
|
|
@@ -10824,7 +10962,7 @@ class RiskConnectionService {
|
|
|
10824
10962
|
payload,
|
|
10825
10963
|
});
|
|
10826
10964
|
if (payload) {
|
|
10827
|
-
const key = CREATE_KEY_FN$
|
|
10965
|
+
const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10828
10966
|
this.getRisk.clear(key);
|
|
10829
10967
|
}
|
|
10830
10968
|
else {
|
|
@@ -10844,7 +10982,6 @@ const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
|
|
|
10844
10982
|
const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
|
|
10845
10983
|
const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
|
|
10846
10984
|
const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
|
|
10847
|
-
const METHOD_NAME_SIGNAL_SYNC = "ActionBase.signalSync";
|
|
10848
10985
|
const METHOD_NAME_DISPOSE = "ActionBase.dispose";
|
|
10849
10986
|
const DEFAULT_SOURCE = "default";
|
|
10850
10987
|
/**
|
|
@@ -11256,6 +11393,11 @@ class ActionProxy {
|
|
|
11256
11393
|
*/
|
|
11257
11394
|
async signalSync(event) {
|
|
11258
11395
|
if (this._target.signalSync) {
|
|
11396
|
+
console.error("Action::signalSync is unwanted cause exchange integration should be implemented in Broker.useBrokerAdapter as an infrastructure domain layer");
|
|
11397
|
+
console.error("If you need to implement custom logic on signal open/close, please use signal(), signalBacktest(), signalLive()");
|
|
11398
|
+
console.error("If Action::signalSync throws the exchange will not execute the order!");
|
|
11399
|
+
console.error("");
|
|
11400
|
+
console.error("You have been warned!");
|
|
11259
11401
|
await this._target.signalSync(event);
|
|
11260
11402
|
}
|
|
11261
11403
|
}
|
|
@@ -11718,19 +11860,6 @@ class ActionBase {
|
|
|
11718
11860
|
source,
|
|
11719
11861
|
});
|
|
11720
11862
|
}
|
|
11721
|
-
/**
|
|
11722
|
-
* Gate for position open/close via limit order. Default allows all.
|
|
11723
|
-
* Throw to reject — framework retries next tick.
|
|
11724
|
-
*
|
|
11725
|
-
* NOTE: Exceptions are NOT swallowed — they propagate to CREATE_SYNC_FN.
|
|
11726
|
-
*
|
|
11727
|
-
* @param event - Sync event with action "signal-open" or "signal-close"
|
|
11728
|
-
*/
|
|
11729
|
-
signalSync(_event, source = DEFAULT_SOURCE) {
|
|
11730
|
-
bt.loggerService.info(METHOD_NAME_SIGNAL_SYNC, {
|
|
11731
|
-
source,
|
|
11732
|
-
});
|
|
11733
|
-
}
|
|
11734
11863
|
/**
|
|
11735
11864
|
* Cleans up resources and subscriptions when action handler is disposed.
|
|
11736
11865
|
*
|
|
@@ -12300,7 +12429,7 @@ class ClientAction {
|
|
|
12300
12429
|
* @param backtest - Whether running in backtest mode
|
|
12301
12430
|
* @returns Unique string key for memoization
|
|
12302
12431
|
*/
|
|
12303
|
-
const CREATE_KEY_FN$
|
|
12432
|
+
const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12304
12433
|
const parts = [actionName, strategyName, exchangeName];
|
|
12305
12434
|
if (frameName)
|
|
12306
12435
|
parts.push(frameName);
|
|
@@ -12351,7 +12480,7 @@ class ActionConnectionService {
|
|
|
12351
12480
|
* @param backtest - True if backtest mode, false if live mode
|
|
12352
12481
|
* @returns Configured ClientAction instance
|
|
12353
12482
|
*/
|
|
12354
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12483
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12355
12484
|
const schema = this.actionSchemaService.get(actionName);
|
|
12356
12485
|
return new ClientAction({
|
|
12357
12486
|
...schema,
|
|
@@ -12561,7 +12690,7 @@ class ActionConnectionService {
|
|
|
12561
12690
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
12562
12691
|
return;
|
|
12563
12692
|
}
|
|
12564
|
-
const key = CREATE_KEY_FN$
|
|
12693
|
+
const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12565
12694
|
if (!this.getAction.has(key)) {
|
|
12566
12695
|
return;
|
|
12567
12696
|
}
|
|
@@ -12579,7 +12708,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
12579
12708
|
* @param exchangeName - Exchange name
|
|
12580
12709
|
* @returns Unique string key for memoization
|
|
12581
12710
|
*/
|
|
12582
|
-
const CREATE_KEY_FN$
|
|
12711
|
+
const CREATE_KEY_FN$l = (exchangeName) => {
|
|
12583
12712
|
return exchangeName;
|
|
12584
12713
|
};
|
|
12585
12714
|
/**
|
|
@@ -12603,7 +12732,7 @@ class ExchangeCoreService {
|
|
|
12603
12732
|
* @param exchangeName - Name of the exchange to validate
|
|
12604
12733
|
* @returns Promise that resolves when validation is complete
|
|
12605
12734
|
*/
|
|
12606
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
12735
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
|
|
12607
12736
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
12608
12737
|
exchangeName,
|
|
12609
12738
|
});
|
|
@@ -12855,7 +12984,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
12855
12984
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
12856
12985
|
* @returns Unique string key for memoization
|
|
12857
12986
|
*/
|
|
12858
|
-
const CREATE_KEY_FN$
|
|
12987
|
+
const CREATE_KEY_FN$k = (context) => {
|
|
12859
12988
|
const parts = [context.strategyName, context.exchangeName];
|
|
12860
12989
|
if (context.frameName)
|
|
12861
12990
|
parts.push(context.frameName);
|
|
@@ -12887,7 +13016,7 @@ class StrategyCoreService {
|
|
|
12887
13016
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
12888
13017
|
* @returns Promise that resolves when validation is complete
|
|
12889
13018
|
*/
|
|
12890
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
13019
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
|
|
12891
13020
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
12892
13021
|
context,
|
|
12893
13022
|
});
|
|
@@ -13722,7 +13851,7 @@ class SizingGlobalService {
|
|
|
13722
13851
|
* @param context - Context with riskName, exchangeName, frameName
|
|
13723
13852
|
* @returns Unique string key for memoization
|
|
13724
13853
|
*/
|
|
13725
|
-
const CREATE_KEY_FN$
|
|
13854
|
+
const CREATE_KEY_FN$j = (context) => {
|
|
13726
13855
|
const parts = [context.riskName, context.exchangeName];
|
|
13727
13856
|
if (context.frameName)
|
|
13728
13857
|
parts.push(context.frameName);
|
|
@@ -13748,7 +13877,7 @@ class RiskGlobalService {
|
|
|
13748
13877
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
13749
13878
|
* @returns Promise that resolves when validation is complete
|
|
13750
13879
|
*/
|
|
13751
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
13880
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
|
|
13752
13881
|
this.loggerService.log("riskGlobalService validate", {
|
|
13753
13882
|
context,
|
|
13754
13883
|
});
|
|
@@ -13826,7 +13955,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
13826
13955
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13827
13956
|
* @returns Unique string key for memoization
|
|
13828
13957
|
*/
|
|
13829
|
-
const CREATE_KEY_FN$
|
|
13958
|
+
const CREATE_KEY_FN$i = (context) => {
|
|
13830
13959
|
const parts = [context.strategyName, context.exchangeName];
|
|
13831
13960
|
if (context.frameName)
|
|
13832
13961
|
parts.push(context.frameName);
|
|
@@ -13870,7 +13999,7 @@ class ActionCoreService {
|
|
|
13870
13999
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
13871
14000
|
* @returns Promise that resolves when all validations complete
|
|
13872
14001
|
*/
|
|
13873
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14002
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
|
|
13874
14003
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
13875
14004
|
context,
|
|
13876
14005
|
});
|
|
@@ -18405,7 +18534,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
18405
18534
|
* @param backtest - Whether running in backtest mode
|
|
18406
18535
|
* @returns Unique string key for memoization
|
|
18407
18536
|
*/
|
|
18408
|
-
const CREATE_KEY_FN$
|
|
18537
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18409
18538
|
const parts = [symbol, strategyName, exchangeName];
|
|
18410
18539
|
if (frameName)
|
|
18411
18540
|
parts.push(frameName);
|
|
@@ -18644,7 +18773,7 @@ class BacktestMarkdownService {
|
|
|
18644
18773
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
18645
18774
|
* Each combination gets its own isolated storage instance.
|
|
18646
18775
|
*/
|
|
18647
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
18776
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
18648
18777
|
/**
|
|
18649
18778
|
* Processes tick events and accumulates closed signals.
|
|
18650
18779
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -18801,7 +18930,7 @@ class BacktestMarkdownService {
|
|
|
18801
18930
|
payload,
|
|
18802
18931
|
});
|
|
18803
18932
|
if (payload) {
|
|
18804
|
-
const key = CREATE_KEY_FN$
|
|
18933
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
18805
18934
|
this.getStorage.clear(key);
|
|
18806
18935
|
}
|
|
18807
18936
|
else {
|
|
@@ -18863,7 +18992,7 @@ class BacktestMarkdownService {
|
|
|
18863
18992
|
* @param backtest - Whether running in backtest mode
|
|
18864
18993
|
* @returns Unique string key for memoization
|
|
18865
18994
|
*/
|
|
18866
|
-
const CREATE_KEY_FN$
|
|
18995
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18867
18996
|
const parts = [symbol, strategyName, exchangeName];
|
|
18868
18997
|
if (frameName)
|
|
18869
18998
|
parts.push(frameName);
|
|
@@ -19346,7 +19475,7 @@ class LiveMarkdownService {
|
|
|
19346
19475
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19347
19476
|
* Each combination gets its own isolated storage instance.
|
|
19348
19477
|
*/
|
|
19349
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19478
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
|
|
19350
19479
|
/**
|
|
19351
19480
|
* Subscribes to live signal emitter to receive tick events.
|
|
19352
19481
|
* Protected against multiple subscriptions.
|
|
@@ -19564,7 +19693,7 @@ class LiveMarkdownService {
|
|
|
19564
19693
|
payload,
|
|
19565
19694
|
});
|
|
19566
19695
|
if (payload) {
|
|
19567
|
-
const key = CREATE_KEY_FN$
|
|
19696
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19568
19697
|
this.getStorage.clear(key);
|
|
19569
19698
|
}
|
|
19570
19699
|
else {
|
|
@@ -19584,7 +19713,7 @@ class LiveMarkdownService {
|
|
|
19584
19713
|
* @param backtest - Whether running in backtest mode
|
|
19585
19714
|
* @returns Unique string key for memoization
|
|
19586
19715
|
*/
|
|
19587
|
-
const CREATE_KEY_FN$
|
|
19716
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19588
19717
|
const parts = [symbol, strategyName, exchangeName];
|
|
19589
19718
|
if (frameName)
|
|
19590
19719
|
parts.push(frameName);
|
|
@@ -19875,7 +20004,7 @@ class ScheduleMarkdownService {
|
|
|
19875
20004
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19876
20005
|
* Each combination gets its own isolated storage instance.
|
|
19877
20006
|
*/
|
|
19878
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20007
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
19879
20008
|
/**
|
|
19880
20009
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
19881
20010
|
* Protected against multiple subscriptions.
|
|
@@ -20078,7 +20207,7 @@ class ScheduleMarkdownService {
|
|
|
20078
20207
|
payload,
|
|
20079
20208
|
});
|
|
20080
20209
|
if (payload) {
|
|
20081
|
-
const key = CREATE_KEY_FN$
|
|
20210
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20082
20211
|
this.getStorage.clear(key);
|
|
20083
20212
|
}
|
|
20084
20213
|
else {
|
|
@@ -20098,7 +20227,7 @@ class ScheduleMarkdownService {
|
|
|
20098
20227
|
* @param backtest - Whether running in backtest mode
|
|
20099
20228
|
* @returns Unique string key for memoization
|
|
20100
20229
|
*/
|
|
20101
|
-
const CREATE_KEY_FN$
|
|
20230
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20102
20231
|
const parts = [symbol, strategyName, exchangeName];
|
|
20103
20232
|
if (frameName)
|
|
20104
20233
|
parts.push(frameName);
|
|
@@ -20346,7 +20475,7 @@ class PerformanceMarkdownService {
|
|
|
20346
20475
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20347
20476
|
* Each combination gets its own isolated storage instance.
|
|
20348
20477
|
*/
|
|
20349
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20478
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
20350
20479
|
/**
|
|
20351
20480
|
* Subscribes to performance emitter to receive performance events.
|
|
20352
20481
|
* Protected against multiple subscriptions.
|
|
@@ -20513,7 +20642,7 @@ class PerformanceMarkdownService {
|
|
|
20513
20642
|
payload,
|
|
20514
20643
|
});
|
|
20515
20644
|
if (payload) {
|
|
20516
|
-
const key = CREATE_KEY_FN$
|
|
20645
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20517
20646
|
this.getStorage.clear(key);
|
|
20518
20647
|
}
|
|
20519
20648
|
else {
|
|
@@ -20983,7 +21112,7 @@ class WalkerMarkdownService {
|
|
|
20983
21112
|
* @param backtest - Whether running in backtest mode
|
|
20984
21113
|
* @returns Unique string key for memoization
|
|
20985
21114
|
*/
|
|
20986
|
-
const CREATE_KEY_FN$
|
|
21115
|
+
const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
|
|
20987
21116
|
const parts = [exchangeName];
|
|
20988
21117
|
if (frameName)
|
|
20989
21118
|
parts.push(frameName);
|
|
@@ -21350,7 +21479,7 @@ class HeatMarkdownService {
|
|
|
21350
21479
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
21351
21480
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
21352
21481
|
*/
|
|
21353
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21482
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
21354
21483
|
/**
|
|
21355
21484
|
* Subscribes to signal emitter to receive tick events.
|
|
21356
21485
|
* Protected against multiple subscriptions.
|
|
@@ -21545,7 +21674,7 @@ class HeatMarkdownService {
|
|
|
21545
21674
|
payload,
|
|
21546
21675
|
});
|
|
21547
21676
|
if (payload) {
|
|
21548
|
-
const key = CREATE_KEY_FN$
|
|
21677
|
+
const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
|
|
21549
21678
|
this.getStorage.clear(key);
|
|
21550
21679
|
}
|
|
21551
21680
|
else {
|
|
@@ -22576,7 +22705,7 @@ class ClientPartial {
|
|
|
22576
22705
|
* @param backtest - Whether running in backtest mode
|
|
22577
22706
|
* @returns Unique string key for memoization
|
|
22578
22707
|
*/
|
|
22579
|
-
const CREATE_KEY_FN$
|
|
22708
|
+
const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
22580
22709
|
/**
|
|
22581
22710
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
22582
22711
|
*
|
|
@@ -22698,7 +22827,7 @@ class PartialConnectionService {
|
|
|
22698
22827
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
22699
22828
|
* Value: ClientPartial instance with logger and event emitters
|
|
22700
22829
|
*/
|
|
22701
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
22830
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
|
|
22702
22831
|
return new ClientPartial({
|
|
22703
22832
|
signalId,
|
|
22704
22833
|
logger: this.loggerService,
|
|
@@ -22788,7 +22917,7 @@ class PartialConnectionService {
|
|
|
22788
22917
|
const partial = this.getPartial(data.id, backtest);
|
|
22789
22918
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
22790
22919
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
22791
|
-
const key = CREATE_KEY_FN$
|
|
22920
|
+
const key = CREATE_KEY_FN$c(data.id, backtest);
|
|
22792
22921
|
this.getPartial.clear(key);
|
|
22793
22922
|
};
|
|
22794
22923
|
}
|
|
@@ -22804,7 +22933,7 @@ class PartialConnectionService {
|
|
|
22804
22933
|
* @param backtest - Whether running in backtest mode
|
|
22805
22934
|
* @returns Unique string key for memoization
|
|
22806
22935
|
*/
|
|
22807
|
-
const CREATE_KEY_FN$
|
|
22936
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22808
22937
|
const parts = [symbol, strategyName, exchangeName];
|
|
22809
22938
|
if (frameName)
|
|
22810
22939
|
parts.push(frameName);
|
|
@@ -23029,7 +23158,7 @@ class PartialMarkdownService {
|
|
|
23029
23158
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
23030
23159
|
* Each combination gets its own isolated storage instance.
|
|
23031
23160
|
*/
|
|
23032
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23161
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
23033
23162
|
/**
|
|
23034
23163
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
23035
23164
|
* Protected against multiple subscriptions.
|
|
@@ -23239,7 +23368,7 @@ class PartialMarkdownService {
|
|
|
23239
23368
|
payload,
|
|
23240
23369
|
});
|
|
23241
23370
|
if (payload) {
|
|
23242
|
-
const key = CREATE_KEY_FN$
|
|
23371
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
23243
23372
|
this.getStorage.clear(key);
|
|
23244
23373
|
}
|
|
23245
23374
|
else {
|
|
@@ -23255,7 +23384,7 @@ class PartialMarkdownService {
|
|
|
23255
23384
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
23256
23385
|
* @returns Unique string key for memoization
|
|
23257
23386
|
*/
|
|
23258
|
-
const CREATE_KEY_FN$
|
|
23387
|
+
const CREATE_KEY_FN$a = (context) => {
|
|
23259
23388
|
const parts = [context.strategyName, context.exchangeName];
|
|
23260
23389
|
if (context.frameName)
|
|
23261
23390
|
parts.push(context.frameName);
|
|
@@ -23329,7 +23458,7 @@ class PartialGlobalService {
|
|
|
23329
23458
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
23330
23459
|
* @param methodName - Name of the calling method for error tracking
|
|
23331
23460
|
*/
|
|
23332
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
23461
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
|
|
23333
23462
|
this.loggerService.log("partialGlobalService validate", {
|
|
23334
23463
|
context,
|
|
23335
23464
|
methodName,
|
|
@@ -23784,7 +23913,7 @@ class ClientBreakeven {
|
|
|
23784
23913
|
* @param backtest - Whether running in backtest mode
|
|
23785
23914
|
* @returns Unique string key for memoization
|
|
23786
23915
|
*/
|
|
23787
|
-
const CREATE_KEY_FN$
|
|
23916
|
+
const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
23788
23917
|
/**
|
|
23789
23918
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
23790
23919
|
*
|
|
@@ -23870,7 +23999,7 @@ class BreakevenConnectionService {
|
|
|
23870
23999
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
23871
24000
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
23872
24001
|
*/
|
|
23873
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24002
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
|
|
23874
24003
|
return new ClientBreakeven({
|
|
23875
24004
|
signalId,
|
|
23876
24005
|
logger: this.loggerService,
|
|
@@ -23931,7 +24060,7 @@ class BreakevenConnectionService {
|
|
|
23931
24060
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
23932
24061
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
23933
24062
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
23934
|
-
const key = CREATE_KEY_FN$
|
|
24063
|
+
const key = CREATE_KEY_FN$9(data.id, backtest);
|
|
23935
24064
|
this.getBreakeven.clear(key);
|
|
23936
24065
|
};
|
|
23937
24066
|
}
|
|
@@ -23947,7 +24076,7 @@ class BreakevenConnectionService {
|
|
|
23947
24076
|
* @param backtest - Whether running in backtest mode
|
|
23948
24077
|
* @returns Unique string key for memoization
|
|
23949
24078
|
*/
|
|
23950
|
-
const CREATE_KEY_FN$
|
|
24079
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23951
24080
|
const parts = [symbol, strategyName, exchangeName];
|
|
23952
24081
|
if (frameName)
|
|
23953
24082
|
parts.push(frameName);
|
|
@@ -24124,7 +24253,7 @@ class BreakevenMarkdownService {
|
|
|
24124
24253
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24125
24254
|
* Each combination gets its own isolated storage instance.
|
|
24126
24255
|
*/
|
|
24127
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24256
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
24128
24257
|
/**
|
|
24129
24258
|
* Subscribes to breakeven signal emitter to receive events.
|
|
24130
24259
|
* Protected against multiple subscriptions.
|
|
@@ -24313,7 +24442,7 @@ class BreakevenMarkdownService {
|
|
|
24313
24442
|
payload,
|
|
24314
24443
|
});
|
|
24315
24444
|
if (payload) {
|
|
24316
|
-
const key = CREATE_KEY_FN$
|
|
24445
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24317
24446
|
this.getStorage.clear(key);
|
|
24318
24447
|
}
|
|
24319
24448
|
else {
|
|
@@ -24329,7 +24458,7 @@ class BreakevenMarkdownService {
|
|
|
24329
24458
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
24330
24459
|
* @returns Unique string key for memoization
|
|
24331
24460
|
*/
|
|
24332
|
-
const CREATE_KEY_FN$
|
|
24461
|
+
const CREATE_KEY_FN$7 = (context) => {
|
|
24333
24462
|
const parts = [context.strategyName, context.exchangeName];
|
|
24334
24463
|
if (context.frameName)
|
|
24335
24464
|
parts.push(context.frameName);
|
|
@@ -24403,7 +24532,7 @@ class BreakevenGlobalService {
|
|
|
24403
24532
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
24404
24533
|
* @param methodName - Name of the calling method for error tracking
|
|
24405
24534
|
*/
|
|
24406
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
24535
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
|
|
24407
24536
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
24408
24537
|
context,
|
|
24409
24538
|
methodName,
|
|
@@ -24623,7 +24752,7 @@ class ConfigValidationService {
|
|
|
24623
24752
|
* @param backtest - Whether running in backtest mode
|
|
24624
24753
|
* @returns Unique string key for memoization
|
|
24625
24754
|
*/
|
|
24626
|
-
const CREATE_KEY_FN$
|
|
24755
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24627
24756
|
const parts = [symbol, strategyName, exchangeName];
|
|
24628
24757
|
if (frameName)
|
|
24629
24758
|
parts.push(frameName);
|
|
@@ -24792,7 +24921,7 @@ class RiskMarkdownService {
|
|
|
24792
24921
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24793
24922
|
* Each combination gets its own isolated storage instance.
|
|
24794
24923
|
*/
|
|
24795
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24924
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
|
|
24796
24925
|
/**
|
|
24797
24926
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
24798
24927
|
* Protected against multiple subscriptions.
|
|
@@ -24981,7 +25110,7 @@ class RiskMarkdownService {
|
|
|
24981
25110
|
payload,
|
|
24982
25111
|
});
|
|
24983
25112
|
if (payload) {
|
|
24984
|
-
const key = CREATE_KEY_FN$
|
|
25113
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24985
25114
|
this.getStorage.clear(key);
|
|
24986
25115
|
}
|
|
24987
25116
|
else {
|
|
@@ -27669,7 +27798,7 @@ class SyncReportService {
|
|
|
27669
27798
|
* @returns Colon-separated key string for memoization
|
|
27670
27799
|
* @internal
|
|
27671
27800
|
*/
|
|
27672
|
-
const CREATE_KEY_FN$
|
|
27801
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27673
27802
|
const parts = [symbol, strategyName, exchangeName];
|
|
27674
27803
|
if (frameName)
|
|
27675
27804
|
parts.push(frameName);
|
|
@@ -27917,7 +28046,7 @@ class StrategyMarkdownService {
|
|
|
27917
28046
|
*
|
|
27918
28047
|
* @internal
|
|
27919
28048
|
*/
|
|
27920
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28049
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
27921
28050
|
/**
|
|
27922
28051
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
27923
28052
|
*
|
|
@@ -28485,7 +28614,7 @@ class StrategyMarkdownService {
|
|
|
28485
28614
|
this.clear = async (payload) => {
|
|
28486
28615
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
28487
28616
|
if (payload) {
|
|
28488
|
-
const key = CREATE_KEY_FN$
|
|
28617
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28489
28618
|
this.getStorage.clear(key);
|
|
28490
28619
|
}
|
|
28491
28620
|
else {
|
|
@@ -28593,7 +28722,7 @@ class StrategyMarkdownService {
|
|
|
28593
28722
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
28594
28723
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28595
28724
|
*/
|
|
28596
|
-
const CREATE_KEY_FN$
|
|
28725
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28597
28726
|
const parts = [symbol, strategyName, exchangeName];
|
|
28598
28727
|
if (frameName)
|
|
28599
28728
|
parts.push(frameName);
|
|
@@ -28725,7 +28854,7 @@ class ReportStorage {
|
|
|
28725
28854
|
class SyncMarkdownService {
|
|
28726
28855
|
constructor() {
|
|
28727
28856
|
this.loggerService = inject(TYPES.loggerService);
|
|
28728
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28857
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
28729
28858
|
this.subscribe = singleshot(() => {
|
|
28730
28859
|
this.loggerService.log("syncMarkdownService init");
|
|
28731
28860
|
const unsubscribe = syncSubject.subscribe(this.tick);
|
|
@@ -28800,7 +28929,7 @@ class SyncMarkdownService {
|
|
|
28800
28929
|
this.clear = async (payload) => {
|
|
28801
28930
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
28802
28931
|
if (payload) {
|
|
28803
|
-
const key = CREATE_KEY_FN$
|
|
28932
|
+
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28804
28933
|
this.getStorage.clear(key);
|
|
28805
28934
|
}
|
|
28806
28935
|
else {
|
|
@@ -28810,6 +28939,275 @@ class SyncMarkdownService {
|
|
|
28810
28939
|
}
|
|
28811
28940
|
}
|
|
28812
28941
|
|
|
28942
|
+
const LISTEN_TIMEOUT$1 = 120000;
|
|
28943
|
+
/**
|
|
28944
|
+
* Creates a unique memoization key for a price stream.
|
|
28945
|
+
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28946
|
+
*
|
|
28947
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
28948
|
+
* @param strategyName - Strategy identifier
|
|
28949
|
+
* @param exchangeName - Exchange identifier
|
|
28950
|
+
* @param frameName - Frame identifier (omitted when empty)
|
|
28951
|
+
* @param backtest - Whether running in backtest mode
|
|
28952
|
+
* @returns Unique string key for memoization
|
|
28953
|
+
*/
|
|
28954
|
+
const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28955
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
28956
|
+
if (frameName)
|
|
28957
|
+
parts.push(frameName);
|
|
28958
|
+
parts.push(backtest ? "backtest" : "live");
|
|
28959
|
+
return parts.join(":");
|
|
28960
|
+
};
|
|
28961
|
+
/**
|
|
28962
|
+
* Service for tracking the latest market price per symbol-strategy-exchange-frame combination.
|
|
28963
|
+
*
|
|
28964
|
+
* Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
|
|
28965
|
+
* by StrategyConnectionService. Consumers can synchronously read the last known price or
|
|
28966
|
+
* await the first value if none has arrived yet.
|
|
28967
|
+
*
|
|
28968
|
+
* Primary use case: providing the current price outside of a tick execution context,
|
|
28969
|
+
* e.g., when a command is triggered between ticks.
|
|
28970
|
+
*
|
|
28971
|
+
* Features:
|
|
28972
|
+
* - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
|
|
28973
|
+
* - Falls back to ExchangeConnectionService.getAveragePrice when called inside an execution context
|
|
28974
|
+
* - Waits up to LISTEN_TIMEOUT ms for the first price if none is cached yet
|
|
28975
|
+
* - clear() disposes the BehaviorSubject for a single key or all keys
|
|
28976
|
+
*
|
|
28977
|
+
* Architecture:
|
|
28978
|
+
* - Registered as singleton in DI container
|
|
28979
|
+
* - Updated by StrategyConnectionService after each tick
|
|
28980
|
+
* - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
|
|
28981
|
+
*
|
|
28982
|
+
* @example
|
|
28983
|
+
* ```typescript
|
|
28984
|
+
* const price = await backtest.priceMetaService.getCurrentPrice("BTCUSDT", context, false);
|
|
28985
|
+
* ```
|
|
28986
|
+
*/
|
|
28987
|
+
class PriceMetaService {
|
|
28988
|
+
constructor() {
|
|
28989
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
28990
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
28991
|
+
/**
|
|
28992
|
+
* Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
|
|
28993
|
+
*
|
|
28994
|
+
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
28995
|
+
* Instances are cached until clear() is called.
|
|
28996
|
+
*/
|
|
28997
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
28998
|
+
/**
|
|
28999
|
+
* Returns the current market price for the given symbol and context.
|
|
29000
|
+
*
|
|
29001
|
+
* When called inside an execution context (i.e., during a signal handler or action),
|
|
29002
|
+
* delegates to ExchangeConnectionService.getAveragePrice for the live exchange price.
|
|
29003
|
+
* Otherwise, reads the last value from the cached BehaviorSubject. If no value has
|
|
29004
|
+
* been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
|
|
29005
|
+
*
|
|
29006
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29007
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29008
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29009
|
+
* @returns Current market price in quote currency
|
|
29010
|
+
* @throws When no price arrives within LISTEN_TIMEOUT ms
|
|
29011
|
+
*/
|
|
29012
|
+
this.getCurrentPrice = async (symbol, context, backtest) => {
|
|
29013
|
+
this.loggerService.log("priceMetaService getCurrentPrice", {
|
|
29014
|
+
symbol,
|
|
29015
|
+
context,
|
|
29016
|
+
backtest,
|
|
29017
|
+
});
|
|
29018
|
+
if (ExecutionContextService.hasContext() &&
|
|
29019
|
+
MethodContextService.hasContext()) {
|
|
29020
|
+
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
29021
|
+
}
|
|
29022
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29023
|
+
if (source.data) {
|
|
29024
|
+
return source.data;
|
|
29025
|
+
}
|
|
29026
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
29027
|
+
const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
29028
|
+
if (typeof currentPrice === "symbol") {
|
|
29029
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
29030
|
+
}
|
|
29031
|
+
return currentPrice;
|
|
29032
|
+
};
|
|
29033
|
+
/**
|
|
29034
|
+
* Pushes a new price value into the BehaviorSubject for the given key.
|
|
29035
|
+
*
|
|
29036
|
+
* Called by StrategyConnectionService after each strategy tick to keep
|
|
29037
|
+
* the cached price up to date.
|
|
29038
|
+
*
|
|
29039
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29040
|
+
* @param currentPrice - The latest price from the tick
|
|
29041
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29042
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29043
|
+
*/
|
|
29044
|
+
this.next = async (symbol, currentPrice, context, backtest) => {
|
|
29045
|
+
this.loggerService.log("priceMetaService next", {
|
|
29046
|
+
symbol,
|
|
29047
|
+
currentPrice,
|
|
29048
|
+
context,
|
|
29049
|
+
backtest,
|
|
29050
|
+
});
|
|
29051
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29052
|
+
source.next(currentPrice);
|
|
29053
|
+
};
|
|
29054
|
+
/**
|
|
29055
|
+
* Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
|
|
29056
|
+
*
|
|
29057
|
+
* When called without arguments, clears all memoized price streams.
|
|
29058
|
+
* When called with a payload, clears only the stream for the specified key.
|
|
29059
|
+
* Should be called at strategy start (Backtest/Live/Walker) to reset state.
|
|
29060
|
+
*
|
|
29061
|
+
* @param payload - Optional key to clear a single stream; omit to clear all
|
|
29062
|
+
*/
|
|
29063
|
+
this.clear = (payload) => {
|
|
29064
|
+
this.loggerService.log("priceMetaService clear", {
|
|
29065
|
+
payload
|
|
29066
|
+
});
|
|
29067
|
+
if (!payload) {
|
|
29068
|
+
this.getSource.clear();
|
|
29069
|
+
return;
|
|
29070
|
+
}
|
|
29071
|
+
const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29072
|
+
this.getSource.clear(key);
|
|
29073
|
+
};
|
|
29074
|
+
}
|
|
29075
|
+
}
|
|
29076
|
+
|
|
29077
|
+
const LISTEN_TIMEOUT = 120000;
|
|
29078
|
+
/**
|
|
29079
|
+
* Creates a unique memoization key for a timestamp stream.
|
|
29080
|
+
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
29081
|
+
*
|
|
29082
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29083
|
+
* @param strategyName - Strategy identifier
|
|
29084
|
+
* @param exchangeName - Exchange identifier
|
|
29085
|
+
* @param frameName - Frame identifier (omitted when empty)
|
|
29086
|
+
* @param backtest - Whether running in backtest mode
|
|
29087
|
+
* @returns Unique string key for memoization
|
|
29088
|
+
*/
|
|
29089
|
+
const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29090
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29091
|
+
if (frameName)
|
|
29092
|
+
parts.push(frameName);
|
|
29093
|
+
parts.push(backtest ? "backtest" : "live");
|
|
29094
|
+
return parts.join(":");
|
|
29095
|
+
};
|
|
29096
|
+
/**
|
|
29097
|
+
* Service for tracking the latest candle timestamp per symbol-strategy-exchange-frame combination.
|
|
29098
|
+
*
|
|
29099
|
+
* Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
|
|
29100
|
+
* by StrategyConnectionService. Consumers can synchronously read the last known timestamp or
|
|
29101
|
+
* await the first value if none has arrived yet.
|
|
29102
|
+
*
|
|
29103
|
+
* Primary use case: providing the current candle time outside of a tick execution context,
|
|
29104
|
+
* e.g., when a command is triggered between ticks.
|
|
29105
|
+
*
|
|
29106
|
+
* Features:
|
|
29107
|
+
* - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
|
|
29108
|
+
* - Falls back to ExecutionContextService.context.when when called inside an execution context
|
|
29109
|
+
* - Waits up to LISTEN_TIMEOUT ms for the first timestamp if none is cached yet
|
|
29110
|
+
* - clear() disposes the BehaviorSubject for a single key or all keys
|
|
29111
|
+
*
|
|
29112
|
+
* Architecture:
|
|
29113
|
+
* - Registered as singleton in DI container
|
|
29114
|
+
* - Updated by StrategyConnectionService after each tick
|
|
29115
|
+
* - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
|
|
29116
|
+
*
|
|
29117
|
+
* @example
|
|
29118
|
+
* ```typescript
|
|
29119
|
+
* const ts = await backtest.timeMetaService.getTimestamp("BTCUSDT", context, false);
|
|
29120
|
+
* ```
|
|
29121
|
+
*/
|
|
29122
|
+
class TimeMetaService {
|
|
29123
|
+
constructor() {
|
|
29124
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29125
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
29126
|
+
/**
|
|
29127
|
+
* Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
|
|
29128
|
+
*
|
|
29129
|
+
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
29130
|
+
* Instances are cached until clear() is called.
|
|
29131
|
+
*/
|
|
29132
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
29133
|
+
/**
|
|
29134
|
+
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
29135
|
+
*
|
|
29136
|
+
* When called inside an execution context (i.e., during a signal handler or action),
|
|
29137
|
+
* reads the timestamp directly from ExecutionContextService.context.when.
|
|
29138
|
+
* Otherwise, reads the last value from the cached BehaviorSubject. If no value has
|
|
29139
|
+
* been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
|
|
29140
|
+
*
|
|
29141
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29142
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29143
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29144
|
+
* @returns Unix timestamp in milliseconds of the latest processed candle
|
|
29145
|
+
* @throws When no timestamp arrives within LISTEN_TIMEOUT ms
|
|
29146
|
+
*/
|
|
29147
|
+
this.getTimestamp = async (symbol, context, backtest) => {
|
|
29148
|
+
this.loggerService.log("timeMetaService getTimestamp", {
|
|
29149
|
+
symbol,
|
|
29150
|
+
context,
|
|
29151
|
+
backtest,
|
|
29152
|
+
});
|
|
29153
|
+
if (ExecutionContextService.hasContext()) {
|
|
29154
|
+
return this.executionContextService.context.when.getTime();
|
|
29155
|
+
}
|
|
29156
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29157
|
+
if (source.data) {
|
|
29158
|
+
return source.data;
|
|
29159
|
+
}
|
|
29160
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
29161
|
+
const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
29162
|
+
if (typeof timestamp === "symbol") {
|
|
29163
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
29164
|
+
}
|
|
29165
|
+
return timestamp;
|
|
29166
|
+
};
|
|
29167
|
+
/**
|
|
29168
|
+
* Pushes a new timestamp value into the BehaviorSubject for the given key.
|
|
29169
|
+
*
|
|
29170
|
+
* Called by StrategyConnectionService after each strategy tick to keep
|
|
29171
|
+
* the cached timestamp up to date.
|
|
29172
|
+
*
|
|
29173
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29174
|
+
* @param timestamp - The createdAt timestamp from the tick (milliseconds)
|
|
29175
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29176
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29177
|
+
*/
|
|
29178
|
+
this.next = async (symbol, timestamp, context, backtest) => {
|
|
29179
|
+
this.loggerService.log("timeMetaService next", {
|
|
29180
|
+
symbol,
|
|
29181
|
+
timestamp,
|
|
29182
|
+
context,
|
|
29183
|
+
backtest,
|
|
29184
|
+
});
|
|
29185
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29186
|
+
source.next(timestamp);
|
|
29187
|
+
};
|
|
29188
|
+
/**
|
|
29189
|
+
* Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
|
|
29190
|
+
*
|
|
29191
|
+
* When called without arguments, clears all memoized timestamp streams.
|
|
29192
|
+
* When called with a payload, clears only the stream for the specified key.
|
|
29193
|
+
* Should be called at strategy start (Backtest/Live/Walker) to reset state.
|
|
29194
|
+
*
|
|
29195
|
+
* @param payload - Optional key to clear a single stream; omit to clear all
|
|
29196
|
+
*/
|
|
29197
|
+
this.clear = (payload) => {
|
|
29198
|
+
this.loggerService.log("timeMetaService clear", {
|
|
29199
|
+
payload,
|
|
29200
|
+
});
|
|
29201
|
+
if (!payload) {
|
|
29202
|
+
this.getSource.clear();
|
|
29203
|
+
return;
|
|
29204
|
+
}
|
|
29205
|
+
const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29206
|
+
this.getSource.clear(key);
|
|
29207
|
+
};
|
|
29208
|
+
}
|
|
29209
|
+
}
|
|
29210
|
+
|
|
28813
29211
|
{
|
|
28814
29212
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
28815
29213
|
}
|
|
@@ -28842,6 +29240,10 @@ class SyncMarkdownService {
|
|
|
28842
29240
|
provide(TYPES.actionCoreService, () => new ActionCoreService());
|
|
28843
29241
|
provide(TYPES.frameCoreService, () => new FrameCoreService());
|
|
28844
29242
|
}
|
|
29243
|
+
{
|
|
29244
|
+
provide(TYPES.priceMetaService, () => new PriceMetaService());
|
|
29245
|
+
provide(TYPES.timeMetaService, () => new TimeMetaService());
|
|
29246
|
+
}
|
|
28845
29247
|
{
|
|
28846
29248
|
provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
|
|
28847
29249
|
provide(TYPES.riskGlobalService, () => new RiskGlobalService());
|
|
@@ -28933,6 +29335,10 @@ const coreServices = {
|
|
|
28933
29335
|
actionCoreService: inject(TYPES.actionCoreService),
|
|
28934
29336
|
frameCoreService: inject(TYPES.frameCoreService),
|
|
28935
29337
|
};
|
|
29338
|
+
const metaServices = {
|
|
29339
|
+
timeMetaService: inject(TYPES.timeMetaService),
|
|
29340
|
+
priceMetaService: inject(TYPES.priceMetaService),
|
|
29341
|
+
};
|
|
28936
29342
|
const globalServices = {
|
|
28937
29343
|
sizingGlobalService: inject(TYPES.sizingGlobalService),
|
|
28938
29344
|
riskGlobalService: inject(TYPES.riskGlobalService),
|
|
@@ -28997,6 +29403,7 @@ const backtest = {
|
|
|
28997
29403
|
...connectionServices,
|
|
28998
29404
|
...schemaServices,
|
|
28999
29405
|
...coreServices,
|
|
29406
|
+
...metaServices,
|
|
29000
29407
|
...globalServices,
|
|
29001
29408
|
...commandServices,
|
|
29002
29409
|
...logicPrivateServices,
|
|
@@ -31761,6 +32168,11 @@ BrokerBase = makeExtendable(BrokerBase);
|
|
|
31761
32168
|
*/
|
|
31762
32169
|
const Broker = new BrokerAdapter();
|
|
31763
32170
|
|
|
32171
|
+
const POSITION_OVERLAP_LADDER_DEFAULT = {
|
|
32172
|
+
upperPercent: 1.5,
|
|
32173
|
+
lowerPercent: 1.5,
|
|
32174
|
+
};
|
|
32175
|
+
|
|
31764
32176
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
31765
32177
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
31766
32178
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
@@ -31786,6 +32198,8 @@ const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
|
31786
32198
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
31787
32199
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
31788
32200
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
32201
|
+
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
32202
|
+
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
31789
32203
|
/**
|
|
31790
32204
|
* Cancels the scheduled signal without stopping the strategy.
|
|
31791
32205
|
*
|
|
@@ -32062,6 +32476,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32062
32476
|
percentShift,
|
|
32063
32477
|
currentPrice,
|
|
32064
32478
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
32479
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32065
32480
|
position: signal.position,
|
|
32066
32481
|
context: { exchangeName, frameName, strategyName },
|
|
32067
32482
|
backtest: isBacktest,
|
|
@@ -32141,6 +32556,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32141
32556
|
percentShift,
|
|
32142
32557
|
currentPrice,
|
|
32143
32558
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
32559
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32144
32560
|
position: signal.position,
|
|
32145
32561
|
context: { exchangeName, frameName, strategyName },
|
|
32146
32562
|
backtest: isBacktest,
|
|
@@ -32192,6 +32608,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32192
32608
|
currentPrice,
|
|
32193
32609
|
newStopLossPrice,
|
|
32194
32610
|
position: signal.position,
|
|
32611
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32195
32612
|
context: { exchangeName, frameName, strategyName },
|
|
32196
32613
|
backtest: isBacktest,
|
|
32197
32614
|
});
|
|
@@ -32241,6 +32658,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32241
32658
|
percentShift,
|
|
32242
32659
|
currentPrice,
|
|
32243
32660
|
newTakeProfitPrice,
|
|
32661
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32244
32662
|
position: signal.position,
|
|
32245
32663
|
context: { exchangeName, frameName, strategyName },
|
|
32246
32664
|
backtest: isBacktest,
|
|
@@ -32567,6 +32985,31 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
32567
32985
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32568
32986
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32569
32987
|
}
|
|
32988
|
+
/**
|
|
32989
|
+
* Returns the effective (DCA-weighted) entry price for the current pending signal.
|
|
32990
|
+
*
|
|
32991
|
+
* Uses cost-weighted harmonic mean: Σcost / Σ(cost/price).
|
|
32992
|
+
* When partial closes exist, the price is computed iteratively using
|
|
32993
|
+
* costBasisAtClose snapshots from each partial, then blended with any
|
|
32994
|
+
* DCA entries added after the last partial.
|
|
32995
|
+
* With no DCA entries, equals the original priceOpen.
|
|
32996
|
+
*
|
|
32997
|
+
* Returns null if no pending signal exists.
|
|
32998
|
+
*
|
|
32999
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33000
|
+
*
|
|
33001
|
+
* @param symbol - Trading pair symbol
|
|
33002
|
+
* @returns Promise resolving to effective entry price or null
|
|
33003
|
+
*
|
|
33004
|
+
* @example
|
|
33005
|
+
* ```typescript
|
|
33006
|
+
* import { getPositionAveragePrice } from "backtest-kit";
|
|
33007
|
+
*
|
|
33008
|
+
* const avgPrice = await getPositionAveragePrice("BTCUSDT");
|
|
33009
|
+
* // No DCA: avgPrice === priceOpen
|
|
33010
|
+
* // After DCA at lower price: avgPrice < priceOpen
|
|
33011
|
+
* ```
|
|
33012
|
+
*/
|
|
32570
33013
|
async function getPositionAveragePrice(symbol) {
|
|
32571
33014
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32572
33015
|
symbol,
|
|
@@ -32581,6 +33024,28 @@ async function getPositionAveragePrice(symbol) {
|
|
|
32581
33024
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32582
33025
|
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32583
33026
|
}
|
|
33027
|
+
/**
|
|
33028
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
33029
|
+
*
|
|
33030
|
+
* 1 = original entry only (no DCA).
|
|
33031
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
33032
|
+
*
|
|
33033
|
+
* Returns null if no pending signal exists.
|
|
33034
|
+
*
|
|
33035
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33036
|
+
*
|
|
33037
|
+
* @param symbol - Trading pair symbol
|
|
33038
|
+
* @returns Promise resolving to entry count or null
|
|
33039
|
+
*
|
|
33040
|
+
* @example
|
|
33041
|
+
* ```typescript
|
|
33042
|
+
* import { getPositionInvestedCount } from "backtest-kit";
|
|
33043
|
+
*
|
|
33044
|
+
* const count = await getPositionInvestedCount("BTCUSDT");
|
|
33045
|
+
* // No DCA: count === 1
|
|
33046
|
+
* // After one DCA: count === 2
|
|
33047
|
+
* ```
|
|
33048
|
+
*/
|
|
32584
33049
|
async function getPositionInvestedCount(symbol) {
|
|
32585
33050
|
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32586
33051
|
symbol,
|
|
@@ -32595,6 +33060,28 @@ async function getPositionInvestedCount(symbol) {
|
|
|
32595
33060
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32596
33061
|
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32597
33062
|
}
|
|
33063
|
+
/**
|
|
33064
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
33065
|
+
*
|
|
33066
|
+
* Equal to the sum of all _entry costs (Σ entry.cost).
|
|
33067
|
+
* Each entry cost is set at the time of commitAverageBuy (defaults to CC_POSITION_ENTRY_COST).
|
|
33068
|
+
*
|
|
33069
|
+
* Returns null if no pending signal exists.
|
|
33070
|
+
*
|
|
33071
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33072
|
+
*
|
|
33073
|
+
* @param symbol - Trading pair symbol
|
|
33074
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
33075
|
+
*
|
|
33076
|
+
* @example
|
|
33077
|
+
* ```typescript
|
|
33078
|
+
* import { getPositionInvestedCost } from "backtest-kit";
|
|
33079
|
+
*
|
|
33080
|
+
* const cost = await getPositionInvestedCost("BTCUSDT");
|
|
33081
|
+
* // No DCA, default cost: cost === 100
|
|
33082
|
+
* // After one DCA with default cost: cost === 200
|
|
33083
|
+
* ```
|
|
33084
|
+
*/
|
|
32598
33085
|
async function getPositionInvestedCost(symbol) {
|
|
32599
33086
|
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32600
33087
|
symbol,
|
|
@@ -32609,6 +33096,29 @@ async function getPositionInvestedCost(symbol) {
|
|
|
32609
33096
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32610
33097
|
return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32611
33098
|
}
|
|
33099
|
+
/**
|
|
33100
|
+
* Returns the unrealized PNL percentage for the current pending signal at current market price.
|
|
33101
|
+
*
|
|
33102
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
33103
|
+
* (delegates to toProfitLossDto).
|
|
33104
|
+
*
|
|
33105
|
+
* Returns null if no pending signal exists.
|
|
33106
|
+
*
|
|
33107
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33108
|
+
* Automatically fetches current price via getAveragePrice.
|
|
33109
|
+
*
|
|
33110
|
+
* @param symbol - Trading pair symbol
|
|
33111
|
+
* @returns Promise resolving to PNL percentage or null
|
|
33112
|
+
*
|
|
33113
|
+
* @example
|
|
33114
|
+
* ```typescript
|
|
33115
|
+
* import { getPositionPnlPercent } from "backtest-kit";
|
|
33116
|
+
*
|
|
33117
|
+
* const pnlPct = await getPositionPnlPercent("BTCUSDT");
|
|
33118
|
+
* // LONG at 100, current=105: pnlPct ≈ 5
|
|
33119
|
+
* // LONG at 100, current=95: pnlPct ≈ -5
|
|
33120
|
+
* ```
|
|
33121
|
+
*/
|
|
32612
33122
|
async function getPositionPnlPercent(symbol) {
|
|
32613
33123
|
bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
|
|
32614
33124
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32758,6 +33268,29 @@ async function commitPartialLossCost(symbol, dollarAmount) {
|
|
|
32758
33268
|
});
|
|
32759
33269
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
32760
33270
|
}
|
|
33271
|
+
/**
|
|
33272
|
+
* Returns the unrealized PNL in dollars for the current pending signal at current market price.
|
|
33273
|
+
*
|
|
33274
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
33275
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
33276
|
+
*
|
|
33277
|
+
* Returns null if no pending signal exists.
|
|
33278
|
+
*
|
|
33279
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33280
|
+
* Automatically fetches current price via getAveragePrice.
|
|
33281
|
+
*
|
|
33282
|
+
* @param symbol - Trading pair symbol
|
|
33283
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
33284
|
+
*
|
|
33285
|
+
* @example
|
|
33286
|
+
* ```typescript
|
|
33287
|
+
* import { getPositionPnlCost } from "backtest-kit";
|
|
33288
|
+
*
|
|
33289
|
+
* const pnlCost = await getPositionPnlCost("BTCUSDT");
|
|
33290
|
+
* // LONG at 100, invested $100, current=105: pnlCost ≈ 5
|
|
33291
|
+
* // LONG at 100, invested $200 (DCA), current=95: pnlCost ≈ -10
|
|
33292
|
+
* ```
|
|
33293
|
+
*/
|
|
32761
33294
|
async function getPositionPnlCost(symbol) {
|
|
32762
33295
|
bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
|
|
32763
33296
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32844,6 +33377,104 @@ async function getPositionPartials(symbol) {
|
|
|
32844
33377
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32845
33378
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32846
33379
|
}
|
|
33380
|
+
/**
|
|
33381
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
33382
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
33383
|
+
*
|
|
33384
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
33385
|
+
* where step = level * percent / 100.
|
|
33386
|
+
* Returns false if no pending signal exists.
|
|
33387
|
+
*
|
|
33388
|
+
* @param symbol - Trading pair symbol
|
|
33389
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
33390
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
33391
|
+
* @returns Promise<boolean> - true if price overlaps an existing entry level (DCA not recommended)
|
|
33392
|
+
*
|
|
33393
|
+
* @example
|
|
33394
|
+
* ```typescript
|
|
33395
|
+
* import { getPositionEntryOverlap } from "backtest-kit";
|
|
33396
|
+
*
|
|
33397
|
+
* // LONG with levels [43000, 42000], check if 42100 is too close to 42000
|
|
33398
|
+
* const overlap = await getPositionEntryOverlap("BTCUSDT", 42100, { upperPercent: 5, lowerPercent: 5 });
|
|
33399
|
+
* // overlap = true (42100 is within 5% of 42000 = [39900, 44100])
|
|
33400
|
+
* if (!overlap) {
|
|
33401
|
+
* await commitAverageBuy("BTCUSDT");
|
|
33402
|
+
* }
|
|
33403
|
+
* ```
|
|
33404
|
+
*/
|
|
33405
|
+
async function getPositionEntryOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33406
|
+
bt.loggerService.info(GET_POSITION_ENTRY_OVERLAP_METHOD_NAME, {
|
|
33407
|
+
symbol,
|
|
33408
|
+
currentPrice,
|
|
33409
|
+
ladder,
|
|
33410
|
+
});
|
|
33411
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33412
|
+
throw new Error("getPositionEntryOverlap requires an execution context");
|
|
33413
|
+
}
|
|
33414
|
+
if (!MethodContextService.hasContext()) {
|
|
33415
|
+
throw new Error("getPositionEntryOverlap requires a method context");
|
|
33416
|
+
}
|
|
33417
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33418
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33419
|
+
const levels = await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33420
|
+
if (!levels) {
|
|
33421
|
+
return false;
|
|
33422
|
+
}
|
|
33423
|
+
return levels.some((level) => {
|
|
33424
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
33425
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
33426
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
33427
|
+
});
|
|
33428
|
+
}
|
|
33429
|
+
/**
|
|
33430
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
33431
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
33432
|
+
*
|
|
33433
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
33434
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
33435
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
33436
|
+
*
|
|
33437
|
+
* @param symbol - Trading pair symbol
|
|
33438
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
33439
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
33440
|
+
* @returns Promise<boolean> - true if price overlaps an existing partial price (partial not recommended)
|
|
33441
|
+
*
|
|
33442
|
+
* @example
|
|
33443
|
+
* ```typescript
|
|
33444
|
+
* import { getPositionPartialOverlap } from "backtest-kit";
|
|
33445
|
+
*
|
|
33446
|
+
* // Partials at [45000], check if 45100 is too close
|
|
33447
|
+
* const overlap = await getPositionPartialOverlap("BTCUSDT", 45100, { upperPercent: 1.5, lowerPercent: 1.5 });
|
|
33448
|
+
* // overlap = true (45100 is within 1.5% of 45000)
|
|
33449
|
+
* if (!overlap) {
|
|
33450
|
+
* await commitPartialProfit("BTCUSDT", 50);
|
|
33451
|
+
* }
|
|
33452
|
+
* ```
|
|
33453
|
+
*/
|
|
33454
|
+
async function getPositionPartialOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33455
|
+
bt.loggerService.info(GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME, {
|
|
33456
|
+
symbol,
|
|
33457
|
+
currentPrice,
|
|
33458
|
+
ladder,
|
|
33459
|
+
});
|
|
33460
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33461
|
+
throw new Error("getPositionPartialOverlap requires an execution context");
|
|
33462
|
+
}
|
|
33463
|
+
if (!MethodContextService.hasContext()) {
|
|
33464
|
+
throw new Error("getPositionPartialOverlap requires a method context");
|
|
33465
|
+
}
|
|
33466
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33467
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33468
|
+
const partials = await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33469
|
+
if (!partials) {
|
|
33470
|
+
return false;
|
|
33471
|
+
}
|
|
33472
|
+
return partials.some((partial) => {
|
|
33473
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
33474
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
33475
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
33476
|
+
});
|
|
33477
|
+
}
|
|
32847
33478
|
|
|
32848
33479
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
32849
33480
|
/**
|
|
@@ -34070,6 +34701,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
|
|
|
34070
34701
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34071
34702
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34072
34703
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
34704
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34705
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34073
34706
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
34074
34707
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
34075
34708
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
@@ -34257,6 +34890,20 @@ class BacktestInstance {
|
|
|
34257
34890
|
frameName: context.frameName,
|
|
34258
34891
|
backtest: true,
|
|
34259
34892
|
});
|
|
34893
|
+
bt.timeMetaService.clear({
|
|
34894
|
+
symbol,
|
|
34895
|
+
strategyName: context.strategyName,
|
|
34896
|
+
exchangeName: context.exchangeName,
|
|
34897
|
+
frameName: context.frameName,
|
|
34898
|
+
backtest: true,
|
|
34899
|
+
});
|
|
34900
|
+
bt.priceMetaService.clear({
|
|
34901
|
+
symbol,
|
|
34902
|
+
strategyName: context.strategyName,
|
|
34903
|
+
exchangeName: context.exchangeName,
|
|
34904
|
+
frameName: context.frameName,
|
|
34905
|
+
backtest: true,
|
|
34906
|
+
});
|
|
34260
34907
|
}
|
|
34261
34908
|
{
|
|
34262
34909
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
@@ -34860,6 +35507,90 @@ class BacktestUtils {
|
|
|
34860
35507
|
}
|
|
34861
35508
|
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
34862
35509
|
};
|
|
35510
|
+
/**
|
|
35511
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35512
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
35513
|
+
*
|
|
35514
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
35515
|
+
* where step = level * percent / 100.
|
|
35516
|
+
* Returns false if no pending signal exists.
|
|
35517
|
+
*
|
|
35518
|
+
* @param symbol - Trading pair symbol
|
|
35519
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
35520
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35521
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35522
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
35523
|
+
*/
|
|
35524
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35525
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
35526
|
+
symbol,
|
|
35527
|
+
currentPrice,
|
|
35528
|
+
context,
|
|
35529
|
+
ladder,
|
|
35530
|
+
});
|
|
35531
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35532
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35533
|
+
{
|
|
35534
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35535
|
+
riskName &&
|
|
35536
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35537
|
+
riskList &&
|
|
35538
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35539
|
+
actions &&
|
|
35540
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35541
|
+
}
|
|
35542
|
+
const levels = await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
35543
|
+
if (!levels) {
|
|
35544
|
+
return false;
|
|
35545
|
+
}
|
|
35546
|
+
return levels.some((level) => {
|
|
35547
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
35548
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
35549
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
35550
|
+
});
|
|
35551
|
+
};
|
|
35552
|
+
/**
|
|
35553
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
35554
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
35555
|
+
*
|
|
35556
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
35557
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
35558
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
35559
|
+
*
|
|
35560
|
+
* @param symbol - Trading pair symbol
|
|
35561
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
35562
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35563
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35564
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
35565
|
+
*/
|
|
35566
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35567
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
35568
|
+
symbol,
|
|
35569
|
+
currentPrice,
|
|
35570
|
+
context,
|
|
35571
|
+
ladder,
|
|
35572
|
+
});
|
|
35573
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35574
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35575
|
+
{
|
|
35576
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35577
|
+
riskName &&
|
|
35578
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35579
|
+
riskList &&
|
|
35580
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35581
|
+
actions &&
|
|
35582
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35583
|
+
}
|
|
35584
|
+
const partials = await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
35585
|
+
if (!partials) {
|
|
35586
|
+
return false;
|
|
35587
|
+
}
|
|
35588
|
+
return partials.some((partial) => {
|
|
35589
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
35590
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
35591
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
35592
|
+
});
|
|
35593
|
+
};
|
|
34863
35594
|
/**
|
|
34864
35595
|
* Stops the strategy from generating new signals.
|
|
34865
35596
|
*
|
|
@@ -35349,6 +36080,7 @@ class BacktestUtils {
|
|
|
35349
36080
|
percentShift,
|
|
35350
36081
|
currentPrice,
|
|
35351
36082
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
36083
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35352
36084
|
position: signal.position,
|
|
35353
36085
|
context,
|
|
35354
36086
|
backtest: true,
|
|
@@ -35433,6 +36165,7 @@ class BacktestUtils {
|
|
|
35433
36165
|
percentShift,
|
|
35434
36166
|
currentPrice,
|
|
35435
36167
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
36168
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35436
36169
|
position: signal.position,
|
|
35437
36170
|
context,
|
|
35438
36171
|
backtest: true,
|
|
@@ -35487,6 +36220,7 @@ class BacktestUtils {
|
|
|
35487
36220
|
currentPrice,
|
|
35488
36221
|
newStopLossPrice,
|
|
35489
36222
|
position: signal.position,
|
|
36223
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35490
36224
|
context,
|
|
35491
36225
|
backtest: true,
|
|
35492
36226
|
});
|
|
@@ -35540,6 +36274,7 @@ class BacktestUtils {
|
|
|
35540
36274
|
currentPrice,
|
|
35541
36275
|
newTakeProfitPrice,
|
|
35542
36276
|
position: signal.position,
|
|
36277
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35543
36278
|
context,
|
|
35544
36279
|
backtest: true,
|
|
35545
36280
|
});
|
|
@@ -35883,6 +36618,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
|
35883
36618
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
35884
36619
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
35885
36620
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
36621
|
+
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36622
|
+
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
35886
36623
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
35887
36624
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
35888
36625
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
@@ -36071,6 +36808,20 @@ class LiveInstance {
|
|
|
36071
36808
|
frameName: "",
|
|
36072
36809
|
backtest: false,
|
|
36073
36810
|
});
|
|
36811
|
+
bt.timeMetaService.clear({
|
|
36812
|
+
symbol,
|
|
36813
|
+
strategyName: context.strategyName,
|
|
36814
|
+
exchangeName: context.exchangeName,
|
|
36815
|
+
frameName: "",
|
|
36816
|
+
backtest: false,
|
|
36817
|
+
});
|
|
36818
|
+
bt.priceMetaService.clear({
|
|
36819
|
+
symbol,
|
|
36820
|
+
strategyName: context.strategyName,
|
|
36821
|
+
exchangeName: context.exchangeName,
|
|
36822
|
+
frameName: "",
|
|
36823
|
+
backtest: false,
|
|
36824
|
+
});
|
|
36074
36825
|
}
|
|
36075
36826
|
{
|
|
36076
36827
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
@@ -36736,6 +37487,98 @@ class LiveUtils {
|
|
|
36736
37487
|
frameName: "",
|
|
36737
37488
|
});
|
|
36738
37489
|
};
|
|
37490
|
+
/**
|
|
37491
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37492
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
37493
|
+
*
|
|
37494
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
37495
|
+
* where step = level * percent / 100.
|
|
37496
|
+
* Returns false if no pending signal exists.
|
|
37497
|
+
*
|
|
37498
|
+
* @param symbol - Trading pair symbol
|
|
37499
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
37500
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37501
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37502
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
37503
|
+
*/
|
|
37504
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37505
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
37506
|
+
symbol,
|
|
37507
|
+
currentPrice,
|
|
37508
|
+
context,
|
|
37509
|
+
ladder,
|
|
37510
|
+
});
|
|
37511
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37512
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37513
|
+
{
|
|
37514
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37515
|
+
riskName &&
|
|
37516
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37517
|
+
riskList &&
|
|
37518
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37519
|
+
actions &&
|
|
37520
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37521
|
+
}
|
|
37522
|
+
const levels = await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
37523
|
+
strategyName: context.strategyName,
|
|
37524
|
+
exchangeName: context.exchangeName,
|
|
37525
|
+
frameName: "",
|
|
37526
|
+
});
|
|
37527
|
+
if (!levels) {
|
|
37528
|
+
return false;
|
|
37529
|
+
}
|
|
37530
|
+
return levels.some((level) => {
|
|
37531
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
37532
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
37533
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
37534
|
+
});
|
|
37535
|
+
};
|
|
37536
|
+
/**
|
|
37537
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
37538
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
37539
|
+
*
|
|
37540
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
37541
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
37542
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
37543
|
+
*
|
|
37544
|
+
* @param symbol - Trading pair symbol
|
|
37545
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
37546
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37547
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37548
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
37549
|
+
*/
|
|
37550
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37551
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
37552
|
+
symbol,
|
|
37553
|
+
currentPrice,
|
|
37554
|
+
context,
|
|
37555
|
+
ladder,
|
|
37556
|
+
});
|
|
37557
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37558
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37559
|
+
{
|
|
37560
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37561
|
+
riskName &&
|
|
37562
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37563
|
+
riskList &&
|
|
37564
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37565
|
+
actions &&
|
|
37566
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37567
|
+
}
|
|
37568
|
+
const partials = await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
37569
|
+
strategyName: context.strategyName,
|
|
37570
|
+
exchangeName: context.exchangeName,
|
|
37571
|
+
frameName: "",
|
|
37572
|
+
});
|
|
37573
|
+
if (!partials) {
|
|
37574
|
+
return false;
|
|
37575
|
+
}
|
|
37576
|
+
return partials.some((partial) => {
|
|
37577
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
37578
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
37579
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
37580
|
+
});
|
|
37581
|
+
};
|
|
36739
37582
|
/**
|
|
36740
37583
|
* Stops the strategy from generating new signals.
|
|
36741
37584
|
*
|
|
@@ -37318,6 +38161,7 @@ class LiveUtils {
|
|
|
37318
38161
|
percentShift,
|
|
37319
38162
|
currentPrice,
|
|
37320
38163
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
38164
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37321
38165
|
position: signal.position,
|
|
37322
38166
|
context,
|
|
37323
38167
|
backtest: false,
|
|
@@ -37417,6 +38261,7 @@ class LiveUtils {
|
|
|
37417
38261
|
percentShift,
|
|
37418
38262
|
currentPrice,
|
|
37419
38263
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
38264
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37420
38265
|
position: signal.position,
|
|
37421
38266
|
context,
|
|
37422
38267
|
backtest: false,
|
|
@@ -37486,6 +38331,7 @@ class LiveUtils {
|
|
|
37486
38331
|
percentShift,
|
|
37487
38332
|
currentPrice,
|
|
37488
38333
|
newStopLossPrice,
|
|
38334
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37489
38335
|
position: signal.position,
|
|
37490
38336
|
context,
|
|
37491
38337
|
backtest: false,
|
|
@@ -37555,6 +38401,7 @@ class LiveUtils {
|
|
|
37555
38401
|
percentShift,
|
|
37556
38402
|
currentPrice,
|
|
37557
38403
|
newTakeProfitPrice,
|
|
38404
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37558
38405
|
position: signal.position,
|
|
37559
38406
|
context,
|
|
37560
38407
|
backtest: false,
|
|
@@ -40123,6 +40970,20 @@ class WalkerInstance {
|
|
|
40123
40970
|
frameName: walkerSchema.frameName,
|
|
40124
40971
|
backtest: true,
|
|
40125
40972
|
});
|
|
40973
|
+
bt.timeMetaService.clear({
|
|
40974
|
+
symbol,
|
|
40975
|
+
strategyName,
|
|
40976
|
+
exchangeName: walkerSchema.exchangeName,
|
|
40977
|
+
frameName: walkerSchema.frameName,
|
|
40978
|
+
backtest: true,
|
|
40979
|
+
});
|
|
40980
|
+
bt.priceMetaService.clear({
|
|
40981
|
+
symbol,
|
|
40982
|
+
strategyName,
|
|
40983
|
+
exchangeName: walkerSchema.exchangeName,
|
|
40984
|
+
frameName: walkerSchema.frameName,
|
|
40985
|
+
backtest: true,
|
|
40986
|
+
});
|
|
40126
40987
|
}
|
|
40127
40988
|
{
|
|
40128
40989
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(strategyName);
|
|
@@ -43090,13 +43951,15 @@ class NotificationMemoryBacktestUtils {
|
|
|
43090
43951
|
* Handles signal sync events (signal-open, signal-close).
|
|
43091
43952
|
* @param data - The signal sync contract data
|
|
43092
43953
|
*/
|
|
43093
|
-
this.handleSync = async (data) => {
|
|
43954
|
+
this.handleSync = trycatch(async (data) => {
|
|
43094
43955
|
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
|
|
43095
43956
|
signalId: data.signalId,
|
|
43096
43957
|
action: data.action,
|
|
43097
43958
|
});
|
|
43098
43959
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43099
|
-
}
|
|
43960
|
+
}, {
|
|
43961
|
+
defaultValue: null,
|
|
43962
|
+
});
|
|
43100
43963
|
/**
|
|
43101
43964
|
* Handles risk rejection event.
|
|
43102
43965
|
* @param data - The risk contract data
|
|
@@ -43205,8 +44068,10 @@ class NotificationDummyBacktestUtils {
|
|
|
43205
44068
|
/**
|
|
43206
44069
|
* No-op handler for signal sync event.
|
|
43207
44070
|
*/
|
|
43208
|
-
this.handleSync = async () => {
|
|
43209
|
-
}
|
|
44071
|
+
this.handleSync = trycatch(async () => {
|
|
44072
|
+
}, {
|
|
44073
|
+
defaultValue: null,
|
|
44074
|
+
});
|
|
43210
44075
|
/**
|
|
43211
44076
|
* No-op handler for risk rejection event.
|
|
43212
44077
|
*/
|
|
@@ -43345,7 +44210,7 @@ class NotificationPersistBacktestUtils {
|
|
|
43345
44210
|
* Handles signal sync events (signal-open, signal-close).
|
|
43346
44211
|
* @param data - The signal sync contract data
|
|
43347
44212
|
*/
|
|
43348
|
-
this.handleSync = async (data) => {
|
|
44213
|
+
this.handleSync = trycatch(async (data) => {
|
|
43349
44214
|
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
|
|
43350
44215
|
signalId: data.signalId,
|
|
43351
44216
|
action: data.action,
|
|
@@ -43353,7 +44218,9 @@ class NotificationPersistBacktestUtils {
|
|
|
43353
44218
|
await this.waitForInit();
|
|
43354
44219
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43355
44220
|
await this._updateNotifications();
|
|
43356
|
-
}
|
|
44221
|
+
}, {
|
|
44222
|
+
defaultValue: null,
|
|
44223
|
+
});
|
|
43357
44224
|
/**
|
|
43358
44225
|
* Handles risk rejection event.
|
|
43359
44226
|
* @param data - The risk contract data
|
|
@@ -43536,13 +44403,15 @@ class NotificationMemoryLiveUtils {
|
|
|
43536
44403
|
* Handles signal sync events (signal-open, signal-close).
|
|
43537
44404
|
* @param data - The signal sync contract data
|
|
43538
44405
|
*/
|
|
43539
|
-
this.handleSync = async (data) => {
|
|
44406
|
+
this.handleSync = trycatch(async (data) => {
|
|
43540
44407
|
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SYNC, {
|
|
43541
44408
|
signalId: data.signalId,
|
|
43542
44409
|
action: data.action,
|
|
43543
44410
|
});
|
|
43544
44411
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43545
|
-
}
|
|
44412
|
+
}, {
|
|
44413
|
+
defaultValue: null,
|
|
44414
|
+
});
|
|
43546
44415
|
/**
|
|
43547
44416
|
* Handles risk rejection event.
|
|
43548
44417
|
* @param data - The risk contract data
|
|
@@ -43651,8 +44520,10 @@ class NotificationDummyLiveUtils {
|
|
|
43651
44520
|
/**
|
|
43652
44521
|
* No-op handler for signal sync event.
|
|
43653
44522
|
*/
|
|
43654
|
-
this.handleSync = async () => {
|
|
43655
|
-
}
|
|
44523
|
+
this.handleSync = trycatch(async () => {
|
|
44524
|
+
}, {
|
|
44525
|
+
defaultValue: null,
|
|
44526
|
+
});
|
|
43656
44527
|
/**
|
|
43657
44528
|
* No-op handler for risk rejection event.
|
|
43658
44529
|
*/
|
|
@@ -43792,7 +44663,7 @@ class NotificationPersistLiveUtils {
|
|
|
43792
44663
|
* Handles signal sync events (signal-open, signal-close).
|
|
43793
44664
|
* @param data - The signal sync contract data
|
|
43794
44665
|
*/
|
|
43795
|
-
this.handleSync = async (data) => {
|
|
44666
|
+
this.handleSync = trycatch(async (data) => {
|
|
43796
44667
|
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SYNC, {
|
|
43797
44668
|
signalId: data.signalId,
|
|
43798
44669
|
action: data.action,
|
|
@@ -43800,7 +44671,9 @@ class NotificationPersistLiveUtils {
|
|
|
43800
44671
|
await this.waitForInit();
|
|
43801
44672
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43802
44673
|
await this._updateNotifications();
|
|
43803
|
-
}
|
|
44674
|
+
}, {
|
|
44675
|
+
defaultValue: null,
|
|
44676
|
+
});
|
|
43804
44677
|
/**
|
|
43805
44678
|
* Handles risk rejection event.
|
|
43806
44679
|
* @param data - The risk contract data
|
|
@@ -43962,9 +44835,11 @@ class NotificationBacktestAdapter {
|
|
|
43962
44835
|
* Proxies call to the underlying notification adapter.
|
|
43963
44836
|
* @param data - The signal sync contract data
|
|
43964
44837
|
*/
|
|
43965
|
-
this.handleSync = async (data) => {
|
|
44838
|
+
this.handleSync = trycatch(async (data) => {
|
|
43966
44839
|
return await this._notificationBacktestUtils.handleSync(data);
|
|
43967
|
-
}
|
|
44840
|
+
}, {
|
|
44841
|
+
defaultValue: null,
|
|
44842
|
+
});
|
|
43968
44843
|
/**
|
|
43969
44844
|
* Handles risk rejection event.
|
|
43970
44845
|
* Proxies call to the underlying notification adapter.
|
|
@@ -44106,9 +44981,11 @@ class NotificationLiveAdapter {
|
|
|
44106
44981
|
* Proxies call to the underlying notification adapter.
|
|
44107
44982
|
* @param data - The signal sync contract data
|
|
44108
44983
|
*/
|
|
44109
|
-
this.handleSync = async (data) => {
|
|
44984
|
+
this.handleSync = trycatch(async (data) => {
|
|
44110
44985
|
return await this._notificationLiveUtils.handleSync(data);
|
|
44111
|
-
}
|
|
44986
|
+
}, {
|
|
44987
|
+
defaultValue: null,
|
|
44988
|
+
});
|
|
44112
44989
|
/**
|
|
44113
44990
|
* Handles risk rejection event.
|
|
44114
44991
|
* Proxies call to the underlying notification adapter.
|
|
@@ -45465,4 +46342,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
45465
46342
|
return yesterdayValue / todayValue - 1;
|
|
45466
46343
|
};
|
|
45467
46344
|
|
|
45468
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|
|
46345
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionEntryOverlap, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|