backtest-kit 11.7.0 → 11.9.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/README.md +3 -3
- package/build/index.cjs +624 -173
- package/build/index.mjs +624 -174
- package/package.json +1 -1
- package/types.d.ts +304 -51
package/build/index.cjs
CHANGED
|
@@ -94,6 +94,7 @@ const metaServices$1 = {
|
|
|
94
94
|
contextMetaService: Symbol('contextMetaService'),
|
|
95
95
|
priceMetaService: Symbol('priceMetaService'),
|
|
96
96
|
timeMetaService: Symbol('timeMetaService'),
|
|
97
|
+
runtimeMetaService: Symbol('runtimeMetaService'),
|
|
97
98
|
};
|
|
98
99
|
const globalServices$1 = {
|
|
99
100
|
sizingGlobalService: Symbol('sizingGlobalService'),
|
|
@@ -4680,7 +4681,7 @@ const LOGGER_SERVICE$8 = new LoggerService();
|
|
|
4680
4681
|
* @param backtest - `true` for backtest, `false` for live.
|
|
4681
4682
|
* @returns Colon-joined composite key.
|
|
4682
4683
|
*/
|
|
4683
|
-
const CREATE_KEY_FN$
|
|
4684
|
+
const CREATE_KEY_FN$B = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
4684
4685
|
const parts = [symbol, strategyName, exchangeName];
|
|
4685
4686
|
if (frameName)
|
|
4686
4687
|
parts.push(frameName);
|
|
@@ -4723,7 +4724,7 @@ class LookupUtils {
|
|
|
4723
4724
|
LOGGER_SERVICE$8.info(METHOD_NAME_ADD_ACTIVITY, {
|
|
4724
4725
|
activity,
|
|
4725
4726
|
});
|
|
4726
|
-
const key = CREATE_KEY_FN$
|
|
4727
|
+
const key = CREATE_KEY_FN$B(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
|
|
4727
4728
|
this._lookupMap.set(key, activity);
|
|
4728
4729
|
};
|
|
4729
4730
|
/**
|
|
@@ -4737,7 +4738,7 @@ class LookupUtils {
|
|
|
4737
4738
|
LOGGER_SERVICE$8.info(METHOD_NAME_REMOVE_ACTIVITY, {
|
|
4738
4739
|
activity,
|
|
4739
4740
|
});
|
|
4740
|
-
const key = CREATE_KEY_FN$
|
|
4741
|
+
const key = CREATE_KEY_FN$B(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
|
|
4741
4742
|
this._lookupMap.delete(key);
|
|
4742
4743
|
};
|
|
4743
4744
|
/**
|
|
@@ -12355,7 +12356,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
12355
12356
|
* @param backtest - Whether running in backtest mode
|
|
12356
12357
|
* @returns Unique string key for memoization
|
|
12357
12358
|
*/
|
|
12358
|
-
const CREATE_KEY_FN$
|
|
12359
|
+
const CREATE_KEY_FN$A = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
12359
12360
|
const parts = [symbol, strategyName, exchangeName];
|
|
12360
12361
|
if (frameName)
|
|
12361
12362
|
parts.push(frameName);
|
|
@@ -12376,6 +12377,7 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => functoolsKit.trycatch(async (sy
|
|
|
12376
12377
|
symbol,
|
|
12377
12378
|
strategyName,
|
|
12378
12379
|
exchangeName,
|
|
12380
|
+
frameName: data.frameName,
|
|
12379
12381
|
currentPrice,
|
|
12380
12382
|
data,
|
|
12381
12383
|
backtest,
|
|
@@ -12443,6 +12445,7 @@ const CREATE_COMMIT_ACTIVE_PING_FN = (self) => functoolsKit.trycatch(async (symb
|
|
|
12443
12445
|
symbol,
|
|
12444
12446
|
strategyName,
|
|
12445
12447
|
exchangeName,
|
|
12448
|
+
frameName: data.frameName,
|
|
12446
12449
|
currentPrice,
|
|
12447
12450
|
data,
|
|
12448
12451
|
backtest,
|
|
@@ -12655,7 +12658,7 @@ class StrategyConnectionService {
|
|
|
12655
12658
|
* @param backtest - Whether running in backtest mode
|
|
12656
12659
|
* @returns Configured ClientStrategy instance
|
|
12657
12660
|
*/
|
|
12658
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12661
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$A(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
12659
12662
|
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
12660
12663
|
return new ClientStrategy({
|
|
12661
12664
|
symbol,
|
|
@@ -13617,7 +13620,7 @@ class StrategyConnectionService {
|
|
|
13617
13620
|
}
|
|
13618
13621
|
return;
|
|
13619
13622
|
}
|
|
13620
|
-
const key = CREATE_KEY_FN$
|
|
13623
|
+
const key = CREATE_KEY_FN$A(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13621
13624
|
if (!this.getStrategy.has(key)) {
|
|
13622
13625
|
return;
|
|
13623
13626
|
}
|
|
@@ -14015,11 +14018,51 @@ class StrategyConnectionService {
|
|
|
14015
14018
|
}
|
|
14016
14019
|
}
|
|
14017
14020
|
|
|
14021
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
14022
|
+
const INTERVAL_MINUTES$7 = {
|
|
14023
|
+
"1m": 1,
|
|
14024
|
+
"3m": 3,
|
|
14025
|
+
"5m": 5,
|
|
14026
|
+
"15m": 15,
|
|
14027
|
+
"30m": 30,
|
|
14028
|
+
"1h": 60,
|
|
14029
|
+
"2h": 120,
|
|
14030
|
+
"4h": 240,
|
|
14031
|
+
"6h": 360,
|
|
14032
|
+
"8h": 480,
|
|
14033
|
+
"1d": 1440,
|
|
14034
|
+
};
|
|
14035
|
+
/**
|
|
14036
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
14037
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
14038
|
+
*
|
|
14039
|
+
* Candle timestamp convention:
|
|
14040
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
14041
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
14042
|
+
*
|
|
14043
|
+
* Adapter contract:
|
|
14044
|
+
* - Adapter must return candles with timestamp = openTime
|
|
14045
|
+
* - First returned candle.timestamp must equal aligned since
|
|
14046
|
+
* - Adapter must return exactly `limit` candles
|
|
14047
|
+
*
|
|
14048
|
+
* @param date - Date to align
|
|
14049
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
14050
|
+
* @returns New Date aligned down to interval boundary
|
|
14051
|
+
*/
|
|
14052
|
+
const alignToInterval = (date, interval) => {
|
|
14053
|
+
const minutes = INTERVAL_MINUTES$7[interval];
|
|
14054
|
+
if (minutes === undefined) {
|
|
14055
|
+
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
14056
|
+
}
|
|
14057
|
+
const intervalMs = minutes * MS_PER_MINUTE$6;
|
|
14058
|
+
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
14059
|
+
};
|
|
14060
|
+
|
|
14018
14061
|
/**
|
|
14019
14062
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
14020
14063
|
* Used to generate timeframe arrays with proper spacing.
|
|
14021
14064
|
*/
|
|
14022
|
-
const INTERVAL_MINUTES$
|
|
14065
|
+
const INTERVAL_MINUTES$6 = {
|
|
14023
14066
|
"1m": 1,
|
|
14024
14067
|
"3m": 3,
|
|
14025
14068
|
"5m": 5,
|
|
@@ -14073,7 +14116,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
14073
14116
|
symbol,
|
|
14074
14117
|
});
|
|
14075
14118
|
const { interval, startDate, endDate } = self.params;
|
|
14076
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
14119
|
+
const intervalMinutes = INTERVAL_MINUTES$6[interval];
|
|
14077
14120
|
if (!intervalMinutes) {
|
|
14078
14121
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
14079
14122
|
}
|
|
@@ -14082,8 +14125,14 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
14082
14125
|
today.setUTCHours(0, 0, 0, 0);
|
|
14083
14126
|
// Ensure endDate doesn't go beyond today
|
|
14084
14127
|
const effectiveEndDate = endDate > today ? today : endDate;
|
|
14128
|
+
// Align the iteration start down to the 1-minute boundary so every generated
|
|
14129
|
+
// timestamp lands on a clean minute, matching live mode
|
|
14130
|
+
// (LiveLogicPrivateService aligns `when` via alignToInterval(new Date(), "1m")).
|
|
14131
|
+
// Without this, a startDate carrying sub-minute (or any non-aligned) offset
|
|
14132
|
+
// would propagate that offset to every tick `when` — and therefore to
|
|
14133
|
+
// IRuntimeInfo.when handed to Cron handlers — diverging from live behaviour.
|
|
14085
14134
|
const timeframes = [];
|
|
14086
|
-
let currentDate =
|
|
14135
|
+
let currentDate = alignToInterval(startDate, "1m");
|
|
14087
14136
|
while (currentDate <= effectiveEndDate) {
|
|
14088
14137
|
timeframes.push(new Date(currentDate));
|
|
14089
14138
|
currentDate = new Date(currentDate.getTime() + intervalMinutes * 60 * 1000);
|
|
@@ -14117,6 +14166,7 @@ class ClientFrame {
|
|
|
14117
14166
|
}
|
|
14118
14167
|
}
|
|
14119
14168
|
|
|
14169
|
+
const DEFAULT_INTERVAL = "1m";
|
|
14120
14170
|
/**
|
|
14121
14171
|
* Connection service routing frame operations to correct ClientFrame instance.
|
|
14122
14172
|
*
|
|
@@ -14160,7 +14210,7 @@ class FrameConnectionService {
|
|
|
14160
14210
|
logger: this.loggerService,
|
|
14161
14211
|
startDate,
|
|
14162
14212
|
endDate,
|
|
14163
|
-
interval,
|
|
14213
|
+
interval: interval || DEFAULT_INTERVAL,
|
|
14164
14214
|
callbacks,
|
|
14165
14215
|
});
|
|
14166
14216
|
});
|
|
@@ -14819,7 +14869,7 @@ class ClientRisk {
|
|
|
14819
14869
|
* @param backtest - Whether running in backtest mode
|
|
14820
14870
|
* @returns Unique string key for memoization
|
|
14821
14871
|
*/
|
|
14822
|
-
const CREATE_KEY_FN$
|
|
14872
|
+
const CREATE_KEY_FN$z = (riskName, exchangeName, frameName, backtest) => {
|
|
14823
14873
|
const parts = [riskName, exchangeName];
|
|
14824
14874
|
if (frameName)
|
|
14825
14875
|
parts.push(frameName);
|
|
@@ -14919,7 +14969,7 @@ class RiskConnectionService {
|
|
|
14919
14969
|
* @param backtest - True if backtest mode, false if live mode
|
|
14920
14970
|
* @returns Configured ClientRisk instance
|
|
14921
14971
|
*/
|
|
14922
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
14972
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$z(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
14923
14973
|
const schema = this.riskSchemaService.get(riskName);
|
|
14924
14974
|
return new ClientRisk({
|
|
14925
14975
|
...schema,
|
|
@@ -15009,7 +15059,7 @@ class RiskConnectionService {
|
|
|
15009
15059
|
payload,
|
|
15010
15060
|
});
|
|
15011
15061
|
if (payload) {
|
|
15012
|
-
const key = CREATE_KEY_FN$
|
|
15062
|
+
const key = CREATE_KEY_FN$z(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15013
15063
|
this.getRisk.clear(key);
|
|
15014
15064
|
}
|
|
15015
15065
|
else {
|
|
@@ -16128,7 +16178,7 @@ class ClientAction {
|
|
|
16128
16178
|
* @param backtest - Whether running in backtest mode
|
|
16129
16179
|
* @returns Unique string key for memoization
|
|
16130
16180
|
*/
|
|
16131
|
-
const CREATE_KEY_FN$
|
|
16181
|
+
const CREATE_KEY_FN$y = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
16132
16182
|
const parts = [actionName, strategyName, exchangeName];
|
|
16133
16183
|
if (frameName)
|
|
16134
16184
|
parts.push(frameName);
|
|
@@ -16180,7 +16230,7 @@ class ActionConnectionService {
|
|
|
16180
16230
|
* @param backtest - True if backtest mode, false if live mode
|
|
16181
16231
|
* @returns Configured ClientAction instance
|
|
16182
16232
|
*/
|
|
16183
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16233
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$y(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
16184
16234
|
const schema = this.actionSchemaService.get(actionName);
|
|
16185
16235
|
return new ClientAction({
|
|
16186
16236
|
...schema,
|
|
@@ -16406,7 +16456,7 @@ class ActionConnectionService {
|
|
|
16406
16456
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
16407
16457
|
return;
|
|
16408
16458
|
}
|
|
16409
|
-
const key = CREATE_KEY_FN$
|
|
16459
|
+
const key = CREATE_KEY_FN$y(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16410
16460
|
if (!this.getAction.has(key)) {
|
|
16411
16461
|
return;
|
|
16412
16462
|
}
|
|
@@ -16417,14 +16467,14 @@ class ActionConnectionService {
|
|
|
16417
16467
|
}
|
|
16418
16468
|
}
|
|
16419
16469
|
|
|
16420
|
-
const METHOD_NAME_VALIDATE$
|
|
16470
|
+
const METHOD_NAME_VALIDATE$6 = "exchangeCoreService validate";
|
|
16421
16471
|
/**
|
|
16422
16472
|
* Creates a unique key for memoizing validate calls.
|
|
16423
16473
|
* Key format: "exchangeName"
|
|
16424
16474
|
* @param exchangeName - Exchange name
|
|
16425
16475
|
* @returns Unique string key for memoization
|
|
16426
16476
|
*/
|
|
16427
|
-
const CREATE_KEY_FN$
|
|
16477
|
+
const CREATE_KEY_FN$x = (exchangeName) => {
|
|
16428
16478
|
return exchangeName;
|
|
16429
16479
|
};
|
|
16430
16480
|
/**
|
|
@@ -16448,11 +16498,11 @@ class ExchangeCoreService {
|
|
|
16448
16498
|
* @param exchangeName - Name of the exchange to validate
|
|
16449
16499
|
* @returns Promise that resolves when validation is complete
|
|
16450
16500
|
*/
|
|
16451
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
16452
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
16501
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$x(exchangeName), async (exchangeName) => {
|
|
16502
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$6, {
|
|
16453
16503
|
exchangeName,
|
|
16454
16504
|
});
|
|
16455
|
-
this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$
|
|
16505
|
+
this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$6);
|
|
16456
16506
|
});
|
|
16457
16507
|
/**
|
|
16458
16508
|
* Fetches historical candles with execution context.
|
|
@@ -16721,14 +16771,14 @@ class ExchangeCoreService {
|
|
|
16721
16771
|
}
|
|
16722
16772
|
}
|
|
16723
16773
|
|
|
16724
|
-
const METHOD_NAME_VALIDATE$
|
|
16774
|
+
const METHOD_NAME_VALIDATE$5 = "strategyCoreService validate";
|
|
16725
16775
|
/**
|
|
16726
16776
|
* Creates a unique key for memoizing validate calls.
|
|
16727
16777
|
* Key format: "strategyName:exchangeName:frameName"
|
|
16728
16778
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
16729
16779
|
* @returns Unique string key for memoization
|
|
16730
16780
|
*/
|
|
16731
|
-
const CREATE_KEY_FN$
|
|
16781
|
+
const CREATE_KEY_FN$w = (context) => {
|
|
16732
16782
|
const parts = [context.strategyName, context.exchangeName];
|
|
16733
16783
|
if (context.frameName)
|
|
16734
16784
|
parts.push(context.frameName);
|
|
@@ -16751,6 +16801,7 @@ class StrategyCoreService {
|
|
|
16751
16801
|
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
16752
16802
|
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
16753
16803
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
16804
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
16754
16805
|
/**
|
|
16755
16806
|
* Validates strategy and associated risk configuration.
|
|
16756
16807
|
*
|
|
@@ -16760,16 +16811,17 @@ class StrategyCoreService {
|
|
|
16760
16811
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
16761
16812
|
* @returns Promise that resolves when validation is complete
|
|
16762
16813
|
*/
|
|
16763
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
16764
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
16814
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$w(context), async (context) => {
|
|
16815
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$5, {
|
|
16765
16816
|
context,
|
|
16766
16817
|
});
|
|
16767
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
16768
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$
|
|
16769
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$
|
|
16770
|
-
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$
|
|
16771
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
16772
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
16818
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
16819
|
+
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$5);
|
|
16820
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$5);
|
|
16821
|
+
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$5);
|
|
16822
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$5);
|
|
16823
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$5));
|
|
16824
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$5));
|
|
16773
16825
|
});
|
|
16774
16826
|
/**
|
|
16775
16827
|
* Retrieves the currently active pending signal for the symbol.
|
|
@@ -18130,7 +18182,7 @@ class SizingGlobalService {
|
|
|
18130
18182
|
* @param context - Context with riskName, exchangeName, frameName
|
|
18131
18183
|
* @returns Unique string key for memoization
|
|
18132
18184
|
*/
|
|
18133
|
-
const CREATE_KEY_FN$
|
|
18185
|
+
const CREATE_KEY_FN$v = (context) => {
|
|
18134
18186
|
const parts = [context.riskName, context.exchangeName];
|
|
18135
18187
|
if (context.frameName)
|
|
18136
18188
|
parts.push(context.frameName);
|
|
@@ -18156,7 +18208,7 @@ class RiskGlobalService {
|
|
|
18156
18208
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
18157
18209
|
* @returns Promise that resolves when validation is complete
|
|
18158
18210
|
*/
|
|
18159
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
18211
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$v(context), async (context) => {
|
|
18160
18212
|
this.loggerService.log("riskGlobalService validate", {
|
|
18161
18213
|
context,
|
|
18162
18214
|
});
|
|
@@ -18248,14 +18300,14 @@ class RiskGlobalService {
|
|
|
18248
18300
|
}
|
|
18249
18301
|
}
|
|
18250
18302
|
|
|
18251
|
-
const METHOD_NAME_VALIDATE$
|
|
18303
|
+
const METHOD_NAME_VALIDATE$4 = "actionCoreService validate";
|
|
18252
18304
|
/**
|
|
18253
18305
|
* Creates a unique key for memoizing validate calls.
|
|
18254
18306
|
* Key format: "strategyName:exchangeName:frameName"
|
|
18255
18307
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
18256
18308
|
* @returns Unique string key for memoization
|
|
18257
18309
|
*/
|
|
18258
|
-
const CREATE_KEY_FN$
|
|
18310
|
+
const CREATE_KEY_FN$u = (context) => {
|
|
18259
18311
|
const parts = [context.strategyName, context.exchangeName];
|
|
18260
18312
|
if (context.frameName)
|
|
18261
18313
|
parts.push(context.frameName);
|
|
@@ -18299,17 +18351,17 @@ class ActionCoreService {
|
|
|
18299
18351
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
18300
18352
|
* @returns Promise that resolves when all validations complete
|
|
18301
18353
|
*/
|
|
18302
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
18303
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
18354
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$u(context), async (context) => {
|
|
18355
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$4, {
|
|
18304
18356
|
context,
|
|
18305
18357
|
});
|
|
18306
18358
|
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
18307
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$
|
|
18308
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$
|
|
18309
|
-
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$
|
|
18310
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
18311
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
18312
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$
|
|
18359
|
+
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$4);
|
|
18360
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$4);
|
|
18361
|
+
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$4);
|
|
18362
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$4);
|
|
18363
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$4));
|
|
18364
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$4));
|
|
18313
18365
|
});
|
|
18314
18366
|
/**
|
|
18315
18367
|
* Initializes all ClientAction instances for the strategy.
|
|
@@ -18805,8 +18857,8 @@ class FrameSchemaService {
|
|
|
18805
18857
|
if (typeof frameSchema.frameName !== "string") {
|
|
18806
18858
|
throw new Error(`frame schema validation failed: missing frameName`);
|
|
18807
18859
|
}
|
|
18808
|
-
if (typeof frameSchema.interval !== "string") {
|
|
18809
|
-
throw new Error(`frame schema validation failed:
|
|
18860
|
+
if (frameSchema.interval && typeof frameSchema.interval !== "string") {
|
|
18861
|
+
throw new Error(`frame schema validation failed: invalid interval for frameName=${frameSchema.frameName}`);
|
|
18810
18862
|
}
|
|
18811
18863
|
if (!(frameSchema.startDate instanceof Date)) {
|
|
18812
18864
|
throw new Error(`frame schema validation failed: missing startDate for frameName=${frameSchema.frameName}`);
|
|
@@ -19934,8 +19986,8 @@ class BacktestLogicPrivateService {
|
|
|
19934
19986
|
}
|
|
19935
19987
|
|
|
19936
19988
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
19937
|
-
const MS_PER_MINUTE$
|
|
19938
|
-
const INTERVAL_MINUTES$
|
|
19989
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
19990
|
+
const INTERVAL_MINUTES$5 = {
|
|
19939
19991
|
"1m": 1,
|
|
19940
19992
|
"3m": 3,
|
|
19941
19993
|
"5m": 5,
|
|
@@ -19950,7 +20002,7 @@ const INTERVAL_MINUTES$6 = {
|
|
|
19950
20002
|
};
|
|
19951
20003
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
19952
20004
|
const tickSubject = new functoolsKit.Subject();
|
|
19953
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
20005
|
+
const intervalMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$5;
|
|
19954
20006
|
{
|
|
19955
20007
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
19956
20008
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -19977,46 +20029,6 @@ const waitForCandle = async (interval) => {
|
|
|
19977
20029
|
return emitter.toPromise();
|
|
19978
20030
|
};
|
|
19979
20031
|
|
|
19980
|
-
const MS_PER_MINUTE$5 = 60000;
|
|
19981
|
-
const INTERVAL_MINUTES$5 = {
|
|
19982
|
-
"1m": 1,
|
|
19983
|
-
"3m": 3,
|
|
19984
|
-
"5m": 5,
|
|
19985
|
-
"15m": 15,
|
|
19986
|
-
"30m": 30,
|
|
19987
|
-
"1h": 60,
|
|
19988
|
-
"2h": 120,
|
|
19989
|
-
"4h": 240,
|
|
19990
|
-
"6h": 360,
|
|
19991
|
-
"8h": 480,
|
|
19992
|
-
"1d": 1440,
|
|
19993
|
-
};
|
|
19994
|
-
/**
|
|
19995
|
-
* Aligns timestamp down to the nearest interval boundary.
|
|
19996
|
-
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
19997
|
-
*
|
|
19998
|
-
* Candle timestamp convention:
|
|
19999
|
-
* - Candle timestamp = openTime (when candle opens)
|
|
20000
|
-
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
20001
|
-
*
|
|
20002
|
-
* Adapter contract:
|
|
20003
|
-
* - Adapter must return candles with timestamp = openTime
|
|
20004
|
-
* - First returned candle.timestamp must equal aligned since
|
|
20005
|
-
* - Adapter must return exactly `limit` candles
|
|
20006
|
-
*
|
|
20007
|
-
* @param date - Date to align
|
|
20008
|
-
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
20009
|
-
* @returns New Date aligned down to interval boundary
|
|
20010
|
-
*/
|
|
20011
|
-
const alignToInterval = (date, interval) => {
|
|
20012
|
-
const minutes = INTERVAL_MINUTES$5[interval];
|
|
20013
|
-
if (minutes === undefined) {
|
|
20014
|
-
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
20015
|
-
}
|
|
20016
|
-
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
20017
|
-
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
20018
|
-
};
|
|
20019
|
-
|
|
20020
20032
|
/**
|
|
20021
20033
|
* Private service for live trading orchestration using async generators.
|
|
20022
20034
|
*
|
|
@@ -20839,6 +20851,17 @@ class WalkerLogicPublicService {
|
|
|
20839
20851
|
}
|
|
20840
20852
|
|
|
20841
20853
|
const METHOD_NAME_RUN$2 = "liveCommandService run";
|
|
20854
|
+
const METHOD_NAME_VALIDATE$3 = "liveCommandService validate";
|
|
20855
|
+
/**
|
|
20856
|
+
* Creates a unique key for memoizing validate calls.
|
|
20857
|
+
* Key format: "strategyName:exchangeName:frameName"
|
|
20858
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
20859
|
+
* @returns Unique string key for memoization
|
|
20860
|
+
*/
|
|
20861
|
+
const CREATE_KEY_FN$t = (context) => {
|
|
20862
|
+
const parts = [context.strategyName, context.exchangeName];
|
|
20863
|
+
return parts.join(":");
|
|
20864
|
+
};
|
|
20842
20865
|
/**
|
|
20843
20866
|
* Global service providing access to live trading functionality.
|
|
20844
20867
|
*
|
|
@@ -20854,6 +20877,25 @@ class LiveCommandService {
|
|
|
20854
20877
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
20855
20878
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
20856
20879
|
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
20880
|
+
/**
|
|
20881
|
+
* Validates strategy and associated risk configuration.
|
|
20882
|
+
* Memoized to avoid redundant validations for the same strategy-exchange combination.
|
|
20883
|
+
*
|
|
20884
|
+
* @param context - Context with strategyName, exchangeName
|
|
20885
|
+
* @param methodName - Name of the calling method for error tracking
|
|
20886
|
+
*/
|
|
20887
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$t(context), (context, methodName) => {
|
|
20888
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$3, {
|
|
20889
|
+
context,
|
|
20890
|
+
methodName,
|
|
20891
|
+
});
|
|
20892
|
+
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
20893
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
20894
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20895
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
20896
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
20897
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
20898
|
+
});
|
|
20857
20899
|
/**
|
|
20858
20900
|
* Runs live trading for a symbol with context propagation.
|
|
20859
20901
|
*
|
|
@@ -20868,22 +20910,26 @@ class LiveCommandService {
|
|
|
20868
20910
|
symbol,
|
|
20869
20911
|
context,
|
|
20870
20912
|
});
|
|
20871
|
-
|
|
20872
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$2);
|
|
20873
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$2);
|
|
20874
|
-
}
|
|
20875
|
-
{
|
|
20876
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20877
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$2);
|
|
20878
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$2));
|
|
20879
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN$2));
|
|
20880
|
-
}
|
|
20913
|
+
this.validate(context, METHOD_NAME_RUN$2);
|
|
20881
20914
|
return this.liveLogicPublicService.run(symbol, context);
|
|
20882
20915
|
};
|
|
20883
20916
|
}
|
|
20884
20917
|
}
|
|
20885
20918
|
|
|
20886
20919
|
const METHOD_NAME_RUN$1 = "backtestCommandService run";
|
|
20920
|
+
const METHOD_NAME_VALIDATE$2 = "backtestCommandService validate";
|
|
20921
|
+
/**
|
|
20922
|
+
* Creates a unique key for memoizing validate calls.
|
|
20923
|
+
* Key format: "strategyName:exchangeName:frameName"
|
|
20924
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
20925
|
+
* @returns Unique string key for memoization
|
|
20926
|
+
*/
|
|
20927
|
+
const CREATE_KEY_FN$s = (context) => {
|
|
20928
|
+
const parts = [context.strategyName, context.exchangeName];
|
|
20929
|
+
if (context.frameName)
|
|
20930
|
+
parts.push(context.frameName);
|
|
20931
|
+
return parts.join(":");
|
|
20932
|
+
};
|
|
20887
20933
|
/**
|
|
20888
20934
|
* Global service providing access to backtest functionality.
|
|
20889
20935
|
*
|
|
@@ -20900,6 +20946,26 @@ class BacktestCommandService {
|
|
|
20900
20946
|
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
20901
20947
|
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
20902
20948
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
20949
|
+
/**
|
|
20950
|
+
* Validates strategy and associated risk configuration.
|
|
20951
|
+
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
20952
|
+
*
|
|
20953
|
+
* @param context - Context with strategyName, exchangeName and frameName
|
|
20954
|
+
* @param methodName - Name of the calling method for error tracking
|
|
20955
|
+
*/
|
|
20956
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$s(context), (context, methodName) => {
|
|
20957
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
20958
|
+
context,
|
|
20959
|
+
methodName,
|
|
20960
|
+
});
|
|
20961
|
+
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
20962
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
20963
|
+
this.frameValidationService.validate(context.frameName, methodName);
|
|
20964
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20965
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
20966
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
20967
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
20968
|
+
});
|
|
20903
20969
|
/**
|
|
20904
20970
|
* Runs backtest for a symbol with context propagation.
|
|
20905
20971
|
*
|
|
@@ -20912,23 +20978,26 @@ class BacktestCommandService {
|
|
|
20912
20978
|
symbol,
|
|
20913
20979
|
context,
|
|
20914
20980
|
});
|
|
20915
|
-
|
|
20916
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
|
|
20917
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
|
|
20918
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
|
|
20919
|
-
}
|
|
20920
|
-
{
|
|
20921
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20922
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
20923
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
20924
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN$1));
|
|
20925
|
-
}
|
|
20981
|
+
this.validate(context, METHOD_NAME_RUN$1);
|
|
20926
20982
|
return this.backtestLogicPublicService.run(symbol, context);
|
|
20927
20983
|
};
|
|
20928
20984
|
}
|
|
20929
20985
|
}
|
|
20930
20986
|
|
|
20931
20987
|
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
20988
|
+
const METHOD_NAME_VALIDATE$1 = "walkerCommandService validate";
|
|
20989
|
+
/**
|
|
20990
|
+
* Creates a unique key for memoizing validate calls.
|
|
20991
|
+
* Key format: "walkerName:exchangeName:frameName"
|
|
20992
|
+
* @param context - Context with walkerName, exchangeName, frameName
|
|
20993
|
+
* @returns Unique string key for memoization
|
|
20994
|
+
*/
|
|
20995
|
+
const CREATE_KEY_FN$r = (context) => {
|
|
20996
|
+
const parts = [context.walkerName, context.exchangeName];
|
|
20997
|
+
if (context.frameName)
|
|
20998
|
+
parts.push(context.frameName);
|
|
20999
|
+
return parts.join(":");
|
|
21000
|
+
};
|
|
20932
21001
|
/**
|
|
20933
21002
|
* Global service providing access to walker functionality.
|
|
20934
21003
|
*
|
|
@@ -20947,6 +21016,34 @@ class WalkerCommandService {
|
|
|
20947
21016
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
20948
21017
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
20949
21018
|
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
21019
|
+
/**
|
|
21020
|
+
* Validates walker and associated strategy configurations.
|
|
21021
|
+
* Memoized to avoid redundant validations for the same walker-exchange-frame combination.
|
|
21022
|
+
*
|
|
21023
|
+
* Strategy/risk/action validation is performed explicitly here in addition to the
|
|
21024
|
+
* cascade inside WalkerValidationService — this is critical-path code and the
|
|
21025
|
+
* redundant check is intentional defense-in-depth.
|
|
21026
|
+
*
|
|
21027
|
+
* @param context - Context with walkerName, exchangeName and frameName
|
|
21028
|
+
* @param methodName - Name of the calling method for error tracking
|
|
21029
|
+
*/
|
|
21030
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), (context, methodName) => {
|
|
21031
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
21032
|
+
context,
|
|
21033
|
+
methodName,
|
|
21034
|
+
});
|
|
21035
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
21036
|
+
this.frameValidationService.validate(context.frameName, methodName);
|
|
21037
|
+
this.walkerValidationService.validate(context.walkerName, methodName);
|
|
21038
|
+
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
21039
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
21040
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
21041
|
+
this.strategyValidationService.validate(strategyName, methodName);
|
|
21042
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
21043
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
21044
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
21045
|
+
}
|
|
21046
|
+
});
|
|
20950
21047
|
/**
|
|
20951
21048
|
* Runs walker comparison for a symbol with context propagation.
|
|
20952
21049
|
*
|
|
@@ -20958,21 +21055,7 @@ class WalkerCommandService {
|
|
|
20958
21055
|
symbol,
|
|
20959
21056
|
context,
|
|
20960
21057
|
});
|
|
20961
|
-
|
|
20962
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
20963
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
20964
|
-
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
20965
|
-
}
|
|
20966
|
-
{
|
|
20967
|
-
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
20968
|
-
for (const strategyName of walkerSchema.strategies) {
|
|
20969
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
20970
|
-
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
20971
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
20972
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
20973
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN));
|
|
20974
|
-
}
|
|
20975
|
-
}
|
|
21058
|
+
this.validate(context, METHOD_NAME_RUN);
|
|
20976
21059
|
return this.walkerLogicPublicService.run(symbol, context);
|
|
20977
21060
|
};
|
|
20978
21061
|
}
|
|
@@ -28695,6 +28778,36 @@ class WalkerValidationService {
|
|
|
28695
28778
|
* Injected logger service instance
|
|
28696
28779
|
*/
|
|
28697
28780
|
this.loggerService = inject(TYPES.loggerService);
|
|
28781
|
+
/**
|
|
28782
|
+
* @private
|
|
28783
|
+
* @readonly
|
|
28784
|
+
* Injected walker schema service instance
|
|
28785
|
+
*/
|
|
28786
|
+
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
28787
|
+
/**
|
|
28788
|
+
* @private
|
|
28789
|
+
* @readonly
|
|
28790
|
+
* Injected strategy validation service instance
|
|
28791
|
+
*/
|
|
28792
|
+
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
28793
|
+
/**
|
|
28794
|
+
* @private
|
|
28795
|
+
* @readonly
|
|
28796
|
+
* Injected strategy schema service instance
|
|
28797
|
+
*/
|
|
28798
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
28799
|
+
/**
|
|
28800
|
+
* @private
|
|
28801
|
+
* @readonly
|
|
28802
|
+
* Injected risk validation service instance
|
|
28803
|
+
*/
|
|
28804
|
+
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
28805
|
+
/**
|
|
28806
|
+
* @private
|
|
28807
|
+
* @readonly
|
|
28808
|
+
* Injected action validation service instance
|
|
28809
|
+
*/
|
|
28810
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
28698
28811
|
/**
|
|
28699
28812
|
* @private
|
|
28700
28813
|
* Map storing walker schemas by walker name
|
|
@@ -28716,9 +28829,12 @@ class WalkerValidationService {
|
|
|
28716
28829
|
this._walkerMap.set(walkerName, walkerSchema);
|
|
28717
28830
|
};
|
|
28718
28831
|
/**
|
|
28719
|
-
* Validates the existence of a walker
|
|
28832
|
+
* Validates the existence of a walker and its associated strategy configurations.
|
|
28833
|
+
* Each strategy referenced by the walker is validated via StrategyValidationService,
|
|
28834
|
+
* which in turn validates the strategy's risk profiles and actions.
|
|
28720
28835
|
* @public
|
|
28721
28836
|
* @throws {Error} If walkerName is not found
|
|
28837
|
+
* @throws {Error} If any referenced strategy (or its risk/actions) is invalid
|
|
28722
28838
|
* Memoized function to cache validation results
|
|
28723
28839
|
*/
|
|
28724
28840
|
this.validate = functoolsKit.memoize(([walkerName]) => walkerName, (walkerName, source) => {
|
|
@@ -28730,6 +28846,14 @@ class WalkerValidationService {
|
|
|
28730
28846
|
if (!walker) {
|
|
28731
28847
|
throw new Error(`walker ${walkerName} not found source=${source}`);
|
|
28732
28848
|
}
|
|
28849
|
+
const walkerSchema = this.walkerSchemaService.get(walkerName);
|
|
28850
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
28851
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
28852
|
+
this.strategyValidationService.validate(strategyName, source);
|
|
28853
|
+
riskName && this.riskValidationService.validate(riskName, source);
|
|
28854
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, source));
|
|
28855
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, source));
|
|
28856
|
+
}
|
|
28733
28857
|
return true;
|
|
28734
28858
|
});
|
|
28735
28859
|
/**
|
|
@@ -30166,6 +30290,10 @@ class PartialGlobalService {
|
|
|
30166
30290
|
* Frame validation service for validating frame existence.
|
|
30167
30291
|
*/
|
|
30168
30292
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
30293
|
+
/**
|
|
30294
|
+
* Action validation service for validating action existence.
|
|
30295
|
+
*/
|
|
30296
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
30169
30297
|
/**
|
|
30170
30298
|
* Validates strategy and associated risk configuration.
|
|
30171
30299
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -30181,9 +30309,10 @@ class PartialGlobalService {
|
|
|
30181
30309
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
30182
30310
|
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
30183
30311
|
context.frameName && this.frameValidationService.validate(context.frameName, methodName);
|
|
30184
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
30312
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
30185
30313
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
30186
30314
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
30315
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
30187
30316
|
});
|
|
30188
30317
|
/**
|
|
30189
30318
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -31250,6 +31379,10 @@ class BreakevenGlobalService {
|
|
|
31250
31379
|
* Frame validation service for validating frame existence.
|
|
31251
31380
|
*/
|
|
31252
31381
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
31382
|
+
/**
|
|
31383
|
+
* Action validation service for validating frame existence.
|
|
31384
|
+
*/
|
|
31385
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
31253
31386
|
/**
|
|
31254
31387
|
* Validates strategy and associated risk configuration.
|
|
31255
31388
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -31265,9 +31398,10 @@ class BreakevenGlobalService {
|
|
|
31265
31398
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
31266
31399
|
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
31267
31400
|
context.frameName && this.frameValidationService.validate(context.frameName, methodName);
|
|
31268
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
31401
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
31269
31402
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
31270
31403
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
31404
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
31271
31405
|
});
|
|
31272
31406
|
/**
|
|
31273
31407
|
* Checks if breakeven should be triggered and emits event if conditions met.
|
|
@@ -36155,6 +36289,21 @@ class PriceMetaService {
|
|
|
36155
36289
|
* Instances are cached until clear() is called.
|
|
36156
36290
|
*/
|
|
36157
36291
|
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
36292
|
+
/**
|
|
36293
|
+
* Checks if a price exists for the given key and has emitted at least one value.
|
|
36294
|
+
*
|
|
36295
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
36296
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
36297
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
36298
|
+
* @returns True if a price exists and has emitted a value, false otherwise
|
|
36299
|
+
*/
|
|
36300
|
+
this.hasPrice = (symbol, context, backtest) => {
|
|
36301
|
+
const key = CREATE_KEY_FN$b(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
36302
|
+
if (!this.getSource.has(key)) {
|
|
36303
|
+
return false;
|
|
36304
|
+
}
|
|
36305
|
+
return !!this.getSource.get(key)?.data;
|
|
36306
|
+
};
|
|
36158
36307
|
/**
|
|
36159
36308
|
* Returns the current market price for the given symbol and context.
|
|
36160
36309
|
*
|
|
@@ -36817,6 +36966,129 @@ class NotificationHelperService {
|
|
|
36817
36966
|
}
|
|
36818
36967
|
}
|
|
36819
36968
|
|
|
36969
|
+
const GET_RANGE_FN = functoolsKit.trycatch((self, context, backtest) => {
|
|
36970
|
+
if (!backtest) {
|
|
36971
|
+
return null;
|
|
36972
|
+
}
|
|
36973
|
+
const { startDate, endDate } = self.frameSchemaService.get(context.frameName);
|
|
36974
|
+
return {
|
|
36975
|
+
from: startDate,
|
|
36976
|
+
to: endDate,
|
|
36977
|
+
};
|
|
36978
|
+
}, {
|
|
36979
|
+
fallback: (error, self) => {
|
|
36980
|
+
const message = "RuntimeMetaService GET_RANGE_FN thrown";
|
|
36981
|
+
const payload = {
|
|
36982
|
+
error: functoolsKit.errorData(error),
|
|
36983
|
+
message: functoolsKit.getErrorMessage(error),
|
|
36984
|
+
};
|
|
36985
|
+
self.loggerService.warn(message, payload);
|
|
36986
|
+
console.error(message, payload);
|
|
36987
|
+
errorEmitter.next(error);
|
|
36988
|
+
},
|
|
36989
|
+
defaultValue: null,
|
|
36990
|
+
});
|
|
36991
|
+
const GET_INFO_FN = functoolsKit.trycatch((self, context) => {
|
|
36992
|
+
const { info } = self.strategySchemaService.get(context.strategyName);
|
|
36993
|
+
return info || null;
|
|
36994
|
+
}, {
|
|
36995
|
+
fallback: (error, self) => {
|
|
36996
|
+
const message = "RuntimeMetaService GET_INFO_FN thrown";
|
|
36997
|
+
const payload = {
|
|
36998
|
+
error: functoolsKit.errorData(error),
|
|
36999
|
+
message: functoolsKit.getErrorMessage(error),
|
|
37000
|
+
};
|
|
37001
|
+
self.loggerService.warn(message, payload);
|
|
37002
|
+
console.error(message, payload);
|
|
37003
|
+
errorEmitter.next(error);
|
|
37004
|
+
},
|
|
37005
|
+
defaultValue: null,
|
|
37006
|
+
});
|
|
37007
|
+
const GET_PRICE_FN = functoolsKit.trycatch(async (self, symbol, context, backtest) => {
|
|
37008
|
+
return await self.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
37009
|
+
}, {
|
|
37010
|
+
fallback: (error, self) => {
|
|
37011
|
+
const message = "RuntimeMetaService GET_PRICE_FN thrown";
|
|
37012
|
+
const payload = {
|
|
37013
|
+
error: functoolsKit.errorData(error),
|
|
37014
|
+
message: functoolsKit.getErrorMessage(error),
|
|
37015
|
+
};
|
|
37016
|
+
self.loggerService.warn(message, payload);
|
|
37017
|
+
console.error(message, payload);
|
|
37018
|
+
errorEmitter.next(error);
|
|
37019
|
+
},
|
|
37020
|
+
defaultValue: null,
|
|
37021
|
+
});
|
|
37022
|
+
const RuntimeMetaService = diSingleton.singleton(class {
|
|
37023
|
+
constructor() {
|
|
37024
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
37025
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
37026
|
+
this.priceMetaService = inject(TYPES.priceMetaService);
|
|
37027
|
+
this.frameSchemaService = inject(TYPES.frameSchemaService);
|
|
37028
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
37029
|
+
/**
|
|
37030
|
+
* Fetches the time range for the current strategy execution context.
|
|
37031
|
+
*
|
|
37032
|
+
* For backtest mode, it retrieves the start and end dates from the frame schema.
|
|
37033
|
+
* For live mode, it returns null since there is no predefined time range.
|
|
37034
|
+
*
|
|
37035
|
+
* This method is memoized to optimize performance, as the time range for a given context will not change during execution.
|
|
37036
|
+
*
|
|
37037
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
37038
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
37039
|
+
* @returns An object containing 'from' and 'to' Date objects for backtest mode, or null for live mode
|
|
37040
|
+
*/
|
|
37041
|
+
this._getRange = functoolsKit.memoize(([context, backtest]) => `${context.frameName}:${backtest ? "backtest" : "live"}`, (context, backtest) => {
|
|
37042
|
+
return GET_RANGE_FN(this, context, backtest);
|
|
37043
|
+
});
|
|
37044
|
+
/**
|
|
37045
|
+
* Fetches strategy-defined runtime information for the current execution context.
|
|
37046
|
+
*
|
|
37047
|
+
* This method retrieves the 'info' object defined in the strategy schema, which can contain any custom data the strategy wants to track at runtime.
|
|
37048
|
+
* The content of this object is not defined by the system and can be used freely by strategy implementations for monitoring, reporting, or external logic.
|
|
37049
|
+
*
|
|
37050
|
+
* This method is memoized to optimize performance, as the strategy info for a given context will not change during execution.
|
|
37051
|
+
*
|
|
37052
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
37053
|
+
* @returns The 'info' object defined in the strategy schema for the given strategy, or null if not defined
|
|
37054
|
+
*/
|
|
37055
|
+
this._getInfo = functoolsKit.memoize(([context]) => context.strategyName, (context) => {
|
|
37056
|
+
return GET_INFO_FN(this, context);
|
|
37057
|
+
});
|
|
37058
|
+
/**
|
|
37059
|
+
* Fetches comprehensive runtime information for a given symbol and strategy context, including current price, timestamp, and strategy-specific info.
|
|
37060
|
+
*
|
|
37061
|
+
* This method aggregates data from multiple sources (time, price, frame schema, strategy schema) to provide a complete picture of the current runtime state for a strategy tick.
|
|
37062
|
+
*
|
|
37063
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
37064
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
37065
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
37066
|
+
* @returns An object containing symbol, time range, strategy-defined info, context, timestamp, current price, and backtest flag
|
|
37067
|
+
*/
|
|
37068
|
+
this.getRuntimeInfo = async (symbol, context, backtest) => {
|
|
37069
|
+
this.loggerService.log("runtimeMetaService getRuntimeInfo", {
|
|
37070
|
+
symbol,
|
|
37071
|
+
context,
|
|
37072
|
+
backtest,
|
|
37073
|
+
});
|
|
37074
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
37075
|
+
const when = new Date(timestamp);
|
|
37076
|
+
const currentPrice = await GET_PRICE_FN(this, symbol, context, backtest);
|
|
37077
|
+
const range = this._getRange(context, backtest);
|
|
37078
|
+
const info = this._getInfo(context);
|
|
37079
|
+
return {
|
|
37080
|
+
symbol,
|
|
37081
|
+
range,
|
|
37082
|
+
info,
|
|
37083
|
+
context,
|
|
37084
|
+
backtest,
|
|
37085
|
+
when,
|
|
37086
|
+
currentPrice,
|
|
37087
|
+
};
|
|
37088
|
+
};
|
|
37089
|
+
}
|
|
37090
|
+
});
|
|
37091
|
+
|
|
36820
37092
|
{
|
|
36821
37093
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
36822
37094
|
}
|
|
@@ -36853,6 +37125,7 @@ class NotificationHelperService {
|
|
|
36853
37125
|
provide(TYPES.contextMetaService, () => new ContextMetaService());
|
|
36854
37126
|
provide(TYPES.priceMetaService, () => new PriceMetaService());
|
|
36855
37127
|
provide(TYPES.timeMetaService, () => new TimeMetaService());
|
|
37128
|
+
provide(TYPES.runtimeMetaService, () => new RuntimeMetaService());
|
|
36856
37129
|
}
|
|
36857
37130
|
{
|
|
36858
37131
|
provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
|
|
@@ -36956,6 +37229,7 @@ const metaServices = {
|
|
|
36956
37229
|
timeMetaService: inject(TYPES.timeMetaService),
|
|
36957
37230
|
priceMetaService: inject(TYPES.priceMetaService),
|
|
36958
37231
|
contextMetaService: inject(TYPES.contextMetaService),
|
|
37232
|
+
runtimeMetaService: inject(TYPES.runtimeMetaService),
|
|
36959
37233
|
};
|
|
36960
37234
|
const globalServices = {
|
|
36961
37235
|
sizingGlobalService: inject(TYPES.sizingGlobalService),
|
|
@@ -50469,6 +50743,7 @@ const GET_TIMESTAMP_METHOD_NAME = "meta.getTimestamp";
|
|
|
50469
50743
|
const GET_MODE_METHOD_NAME = "meta.getMode";
|
|
50470
50744
|
const GET_SYMBOL_METHOD_NAME = "meta.getSymbol";
|
|
50471
50745
|
const GET_CONTEXT_METHOD_NAME = "meta.getContext";
|
|
50746
|
+
const GET_RUNTIME_INFO_METHOD_NAME = "meta.getRuntimeInfo";
|
|
50472
50747
|
/**
|
|
50473
50748
|
* Gets the current date from execution context.
|
|
50474
50749
|
*
|
|
@@ -50585,6 +50860,45 @@ async function getContext() {
|
|
|
50585
50860
|
}
|
|
50586
50861
|
return backtest.methodContextService.context;
|
|
50587
50862
|
}
|
|
50863
|
+
/**
|
|
50864
|
+
* Gets runtime information about the current execution environment.
|
|
50865
|
+
*
|
|
50866
|
+
* This includes details such as the current symbol, exchange, timeframe, strategy, and whether it's a backtest or live run.
|
|
50867
|
+
*
|
|
50868
|
+
* @returns Promise resolving to an object containing runtime information
|
|
50869
|
+
* @throws Error if method context or execution context is not active
|
|
50870
|
+
*
|
|
50871
|
+
* @example
|
|
50872
|
+
* ```typescript
|
|
50873
|
+
* const runtimeInfo = await getRuntimeInfo();
|
|
50874
|
+
* console.log(runtimeInfo);
|
|
50875
|
+
* // {
|
|
50876
|
+
* // symbol: "BTCUSDT",
|
|
50877
|
+
* // context: {,
|
|
50878
|
+
* // exchangeName: "Binance",
|
|
50879
|
+
* // frameName: "1m",
|
|
50880
|
+
* // strategyName: "MyStrategy",
|
|
50881
|
+
* // },
|
|
50882
|
+
* // backtest: false
|
|
50883
|
+
* // }
|
|
50884
|
+
* ```
|
|
50885
|
+
*/
|
|
50886
|
+
async function getRuntimeInfo() {
|
|
50887
|
+
backtest.loggerService.info(GET_RUNTIME_INFO_METHOD_NAME);
|
|
50888
|
+
if (!MethodContextService.hasContext()) {
|
|
50889
|
+
throw new Error("getRuntimeInfo requires a method context");
|
|
50890
|
+
}
|
|
50891
|
+
if (!ExecutionContextService.hasContext()) {
|
|
50892
|
+
throw new Error("getRuntimeInfo requires an execution context");
|
|
50893
|
+
}
|
|
50894
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50895
|
+
const { symbol, backtest: isBacktest } = backtest.executionContextService.context;
|
|
50896
|
+
return await backtest.runtimeMetaService.getRuntimeInfo(symbol, {
|
|
50897
|
+
exchangeName,
|
|
50898
|
+
frameName,
|
|
50899
|
+
strategyName,
|
|
50900
|
+
}, isBacktest);
|
|
50901
|
+
}
|
|
50588
50902
|
|
|
50589
50903
|
const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
|
|
50590
50904
|
const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
|
|
@@ -64611,6 +64925,20 @@ const CRON_METHOD_NAME_TICK = "CronUtils._tick";
|
|
|
64611
64925
|
const CRON_METHOD_NAME_ENABLE = "CronUtils.enable";
|
|
64612
64926
|
const CRON_METHOD_NAME_DISABLE = "CronUtils.disable";
|
|
64613
64927
|
const CRON_METHOD_NAME_DISPOSE = "CronUtils.dispose";
|
|
64928
|
+
/**
|
|
64929
|
+
* Watchdog timeout (ms) for a single cron handler invocation.
|
|
64930
|
+
*
|
|
64931
|
+
* A handler that does not settle within this window is treated as failed:
|
|
64932
|
+
* `_runEntry` races `entry.handler(info)` against this `sleep` and, when the
|
|
64933
|
+
* timeout wins, throws into the same `catch` as any other handler error —
|
|
64934
|
+
* surfacing `failed = true`, logging a warning, and (for periodic entries)
|
|
64935
|
+
* rolling back the watermark so the boundary is retried on the next tick.
|
|
64936
|
+
*
|
|
64937
|
+
* This guards the `singlerun`-serialised tick pipeline against a handler that
|
|
64938
|
+
* never resolves (a lost `resolve`, a hung promise with no timeout of its
|
|
64939
|
+
* own): without it such a handler would stall every subsequent tick forever.
|
|
64940
|
+
*/
|
|
64941
|
+
const CRON_HANDLER_TIMEOUT = 120000;
|
|
64614
64942
|
/**
|
|
64615
64943
|
* Local logger instance.
|
|
64616
64944
|
*
|
|
@@ -64619,6 +64947,20 @@ const CRON_METHOD_NAME_DISPOSE = "CronUtils.dispose";
|
|
|
64619
64947
|
* being bootstrapped — `Cron` can be imported and used in isolation.
|
|
64620
64948
|
*/
|
|
64621
64949
|
const LOGGER_SERVICE$1 = new LoggerService();
|
|
64950
|
+
/**
|
|
64951
|
+
* Local runtime-meta-service instance.
|
|
64952
|
+
*
|
|
64953
|
+
* Like {@link LOGGER_SERVICE}, instantiated directly via `new` rather than
|
|
64954
|
+
* resolved from the DI container so `CronUtils` carries no compile-time
|
|
64955
|
+
* dependency on a bootstrapped framework. `RuntimeMetaService` is built with
|
|
64956
|
+
* the `singleton` HOF from `di-singleton`, so `new RuntimeMetaService()`
|
|
64957
|
+
* returns the one shared singleton proxy — the same instance the rest of the
|
|
64958
|
+
* framework injects — and resolves its own dependencies lazily on first use.
|
|
64959
|
+
*
|
|
64960
|
+
* Used by {@link CronUtils._runEntry} to assemble the {@link IRuntimeInfo}
|
|
64961
|
+
* snapshot handed to each cron handler.
|
|
64962
|
+
*/
|
|
64963
|
+
const RUNTIME_META_SERVICE = new RuntimeMetaService();
|
|
64622
64964
|
/**
|
|
64623
64965
|
* Utility class for registering periodic tasks that fire on candle-interval
|
|
64624
64966
|
* boundaries of the virtual time produced by parallel backtests.
|
|
@@ -64641,8 +64983,8 @@ const LOGGER_SERVICE$1 = new LoggerService();
|
|
|
64641
64983
|
* Cron.register({
|
|
64642
64984
|
* name: "tg-signal-parser",
|
|
64643
64985
|
* interval: "1h",
|
|
64644
|
-
* handler: async (
|
|
64645
|
-
* await parseTelegramSignalsToMongo(when);
|
|
64986
|
+
* handler: async (info) => {
|
|
64987
|
+
* await parseTelegramSignalsToMongo(info.when);
|
|
64646
64988
|
* },
|
|
64647
64989
|
* });
|
|
64648
64990
|
*
|
|
@@ -64684,12 +65026,15 @@ class CronUtils {
|
|
|
64684
65026
|
* - Fire-once global: `${name}:once:g${generation}`.
|
|
64685
65027
|
* - Fire-once fan-out: `${name}:once:${symbol}:g${generation}`.
|
|
64686
65028
|
*
|
|
64687
|
-
* Value is the shared in-flight handler promise.
|
|
64688
|
-
*
|
|
64689
|
-
*
|
|
64690
|
-
*
|
|
64691
|
-
*
|
|
64692
|
-
*
|
|
65029
|
+
* Value is the shared in-flight handler promise. It resolves to a `boolean`
|
|
65030
|
+
* "failed" flag (`true` when the handler — or the runtime-info assembly —
|
|
65031
|
+
* threw), which `_tick` uses to roll back the periodic watermark of the slot
|
|
65032
|
+
* it opened so a failed boundary is retried. Every parallel `tick` for the
|
|
65033
|
+
* same slot key awaits this exact promise (mutex semantics) and is released
|
|
65034
|
+
* together when it settles. `_inFlight` is owned exclusively by `_runEntry` —
|
|
65035
|
+
* `clear()` does **not** touch it, so the singleshot promise survives
|
|
65036
|
+
* concurrent `clear` calls and continues to coordinate parallel ticks until
|
|
65037
|
+
* it settles.
|
|
64693
65038
|
*/
|
|
64694
65039
|
this._inFlight = new Map();
|
|
64695
65040
|
/**
|
|
@@ -64733,9 +65078,12 @@ class CronUtils {
|
|
|
64733
65078
|
*
|
|
64734
65079
|
* Written synchronously in `_tick` at slot-open time (before the `await`),
|
|
64735
65080
|
* so a still-in-flight handler does not let a later tick re-open the same
|
|
64736
|
-
* (or an already-passed) boundary.
|
|
64737
|
-
*
|
|
64738
|
-
*
|
|
65081
|
+
* (or an already-passed) boundary. If that handler then **fails**, the
|
|
65082
|
+
* advance is rolled back after the slot settles — the prior value is restored
|
|
65083
|
+
* (or the key deleted if there was none) — so the failed boundary is retried
|
|
65084
|
+
* on the next tick, mirroring catch-up of a skipped boundary. Fire-once
|
|
65085
|
+
* entries never touch this map — they use `_firedOnce`. Pruned by
|
|
65086
|
+
* `_clearBoundaryFor` on `register`/`unregister` and wiped by `dispose`.
|
|
64739
65087
|
*/
|
|
64740
65088
|
this._lastBoundary = new Map();
|
|
64741
65089
|
/**
|
|
@@ -64755,7 +65103,7 @@ class CronUtils {
|
|
|
64755
65103
|
* name: "fetch-funding",
|
|
64756
65104
|
* interval: "8h",
|
|
64757
65105
|
* symbols: ["BTCUSDT", "ETHUSDT"],
|
|
64758
|
-
* handler: async (
|
|
65106
|
+
* handler: async (info) => { ... },
|
|
64759
65107
|
* });
|
|
64760
65108
|
* // Later:
|
|
64761
65109
|
* dispose();
|
|
@@ -64875,7 +65223,7 @@ class CronUtils {
|
|
|
64875
65223
|
* 4. **Fire-once** (`entry.interval === undefined`):
|
|
64876
65224
|
* - If the entry's fired-once key is already in `_firedOnce`, skip.
|
|
64877
65225
|
* - Slot key: `${name}:once` (+ scope) (+ gen).
|
|
64878
|
-
* - `
|
|
65226
|
+
* - `alignedMs` = the 1-minute-aligned `when` from step 0 (`ts`).
|
|
64879
65227
|
* 5. **Periodic** (`entry.interval` set):
|
|
64880
65228
|
* - Align `when` to the entry's interval via {@link alignToInterval} to
|
|
64881
65229
|
* get `alignedMs`, the boundary this tick belongs to.
|
|
@@ -64897,32 +65245,44 @@ class CronUtils {
|
|
|
64897
65245
|
* handler is still in flight.
|
|
64898
65246
|
* - Slot key: `${name}:${alignedMs}` (+ scope) (+ gen).
|
|
64899
65247
|
* 6. Singleshot per slot key: look up the slot in `_inFlight`. If a promise
|
|
64900
|
-
* already exists, `await` the same promise. Otherwise
|
|
64901
|
-
*
|
|
64902
|
-
*
|
|
64903
|
-
*
|
|
64904
|
-
*
|
|
65248
|
+
* already exists, `await` the same promise. Otherwise open the slot via
|
|
65249
|
+
* {@link _runEntry} — which assembles the {@link IRuntimeInfo} snapshot
|
|
65250
|
+
* (from `symbol`, `context`, `backtest`) and invokes `entry.handler(info)`
|
|
65251
|
+
* — store the promise, and `await` it. The slot is removed in `.finally()`
|
|
65252
|
+
* so the next boundary creates a fresh promise; for fire-once entries the
|
|
65253
|
+
* fired-once key is also added to `_firedOnce` on success so subsequent
|
|
65254
|
+
* ticks skip it.
|
|
65255
|
+
* 7. After `await Promise.all`, roll back the watermark for every **periodic**
|
|
65256
|
+
* slot this tick *opened* (not the ones whose in-flight promise it reused)
|
|
65257
|
+
* whose handler reported failure, so the next tick re-opens and re-runs
|
|
65258
|
+
* that boundary.
|
|
64905
65259
|
*
|
|
64906
65260
|
* Errors thrown by `handler` are caught, logged via `console.error`, and
|
|
64907
65261
|
* **not** rethrown — a failing handler must not break the per-symbol
|
|
64908
65262
|
* tick loop or unblock other parallel backtests with an unhandled
|
|
64909
65263
|
* rejection. A failed fire-once handler is **not** marked as fired and
|
|
64910
|
-
* will retry on the next tick.
|
|
65264
|
+
* will retry on the next tick. A failed **periodic** handler likewise
|
|
65265
|
+
* retries: the boundary watermark advanced at slot-open time is rolled back
|
|
65266
|
+
* after the slot settles (step 7), so the next tick re-opens that boundary.
|
|
64911
65267
|
*
|
|
64912
65268
|
* Requires active method context and execution context.
|
|
64913
65269
|
*
|
|
64914
65270
|
* @param symbol - Trading symbol from the current tick.
|
|
64915
65271
|
* @param when - Virtual time of the current tick.
|
|
64916
65272
|
* @param backtest - `true` for backtest ticks, `false` for live ticks.
|
|
64917
|
-
* Forwarded
|
|
64918
|
-
* from the tick that **opens** a given slot is observed by all
|
|
64919
|
-
* awaiters of that slot.
|
|
65273
|
+
* Forwarded to {@link _runEntry} and surfaced as `info.backtest`. Only the
|
|
65274
|
+
* value from the tick that **opens** a given slot is observed by all
|
|
65275
|
+
* parallel awaiters of that slot.
|
|
65276
|
+
* @param context - Strategy/exchange/frame identifiers from the originating
|
|
65277
|
+
* lifecycle event, forwarded to `RuntimeMetaService.getRuntimeInfo` to
|
|
65278
|
+
* build the {@link IRuntimeInfo} snapshot passed to the handler.
|
|
64920
65279
|
* @throws Error if method or execution context is missing.
|
|
64921
65280
|
*/
|
|
64922
|
-
this._tick = async (symbol, when, backtest) => {
|
|
65281
|
+
this._tick = async (symbol, when, backtest, context) => {
|
|
64923
65282
|
LOGGER_SERVICE$1.debug(CRON_METHOD_NAME_TICK, {
|
|
64924
65283
|
symbol,
|
|
64925
65284
|
when,
|
|
65285
|
+
context,
|
|
64926
65286
|
});
|
|
64927
65287
|
if (!MethodContextService.hasContext()) {
|
|
64928
65288
|
throw new Error("CronUtils _tick requires method context");
|
|
@@ -64932,6 +65292,10 @@ class CronUtils {
|
|
|
64932
65292
|
}
|
|
64933
65293
|
const ts = alignToInterval(when, "1m").getTime();
|
|
64934
65294
|
const taskList = [];
|
|
65295
|
+
// Periodic slots THIS tick actually opened (the `!pending` branch), tracked
|
|
65296
|
+
// for watermark rollback on failure. See {@link IOpenedSlot} for what is and
|
|
65297
|
+
// is not recorded here and why.
|
|
65298
|
+
const openedList = [];
|
|
64935
65299
|
for (const { entry, generation } of this._entries.values()) {
|
|
64936
65300
|
if (entry.symbols?.length && !entry.symbols.includes(symbol)) {
|
|
64937
65301
|
continue;
|
|
@@ -64939,7 +65303,6 @@ class CronUtils {
|
|
|
64939
65303
|
const perSymbol = !!entry.symbols?.length;
|
|
64940
65304
|
const scope = perSymbol ? `:${symbol}` : "";
|
|
64941
65305
|
const genSuffix = `:g${generation}`;
|
|
64942
|
-
let aligned;
|
|
64943
65306
|
let alignedMs;
|
|
64944
65307
|
let slotKey;
|
|
64945
65308
|
let firedKey;
|
|
@@ -64951,15 +65314,13 @@ class CronUtils {
|
|
|
64951
65314
|
if (this._firedOnce.has(onceKey)) {
|
|
64952
65315
|
continue;
|
|
64953
65316
|
}
|
|
64954
|
-
aligned = alignToInterval(when, "1m");
|
|
64955
65317
|
alignedMs = ts;
|
|
64956
65318
|
slotKey = `${entry.name}:once${scope}${genSuffix}`;
|
|
64957
65319
|
firedKey = onceKey;
|
|
64958
65320
|
boundaryKey = null;
|
|
64959
65321
|
}
|
|
64960
65322
|
else {
|
|
64961
|
-
|
|
64962
|
-
alignedMs = aligned.getTime();
|
|
65323
|
+
alignedMs = alignToInterval(when, entry.interval).getTime();
|
|
64963
65324
|
boundaryKey = `${entry.name}${scope}${genSuffix}`;
|
|
64964
65325
|
const lastBoundary = this._lastBoundary.get(boundaryKey);
|
|
64965
65326
|
// Fire when the tick's aligned boundary has advanced past the last one
|
|
@@ -64977,16 +65338,70 @@ class CronUtils {
|
|
|
64977
65338
|
// Advance the watermark synchronously at slot-open time, before the
|
|
64978
65339
|
// await below. Otherwise a later tick on the same (or an already
|
|
64979
65340
|
// crossed) boundary, arriving while this handler is still in flight,
|
|
64980
|
-
// would see the stale watermark and open a duplicate slot.
|
|
65341
|
+
// would see the stale watermark and open a duplicate slot. The advance
|
|
65342
|
+
// is rolled back after the slot settles if the handler failed (see the
|
|
65343
|
+
// post-await loop below), so a failed boundary is retried next tick.
|
|
64981
65344
|
if (boundaryKey !== null) {
|
|
65345
|
+
// Capture the pre-advance value so it can be restored verbatim on
|
|
65346
|
+
// failure (undefined => the boundary had never opened => delete the
|
|
65347
|
+
// key on rollback). Read fresh here rather than reusing `lastBoundary`
|
|
65348
|
+
// above to keep the value↔slot binding local and obvious; there is no
|
|
65349
|
+
// `await` between the two reads, so they are identical.
|
|
65350
|
+
const prevBoundary = this._lastBoundary.get(boundaryKey);
|
|
64982
65351
|
this._lastBoundary.set(boundaryKey, alignedMs);
|
|
65352
|
+
pending = this._runEntry(entry, symbol, alignedMs, slotKey, firedKey, backtest, context);
|
|
65353
|
+
this._inFlight.set(slotKey, pending);
|
|
65354
|
+
openedList.push({ boundaryKey, prevBoundary, pending });
|
|
65355
|
+
}
|
|
65356
|
+
else {
|
|
65357
|
+
pending = this._runEntry(entry, symbol, alignedMs, slotKey, firedKey, backtest, context);
|
|
65358
|
+
this._inFlight.set(slotKey, pending);
|
|
64983
65359
|
}
|
|
64984
|
-
pending = this._runEntry(entry, symbol, aligned, alignedMs, slotKey, firedKey, backtest);
|
|
64985
|
-
this._inFlight.set(slotKey, pending);
|
|
64986
65360
|
}
|
|
64987
65361
|
taskList.push(pending);
|
|
64988
65362
|
}
|
|
64989
|
-
|
|
65363
|
+
{
|
|
65364
|
+
// Watchdog: warn (do not interrupt) if the slots this tick is awaiting
|
|
65365
|
+
// have not settled within CRON_HANDLER_TIMEOUT. We deliberately keep
|
|
65366
|
+
// awaiting Promise.all so the singlerun pipeline stays serialised and no
|
|
65367
|
+
// duplicate/zombie slots are spawned — the timer only surfaces the stall.
|
|
65368
|
+
// Use a real setTimeout/clearTimeout (not sleep) so the alarm is cancelled
|
|
65369
|
+
// the instant Promise.all resolves, rather than lingering for the full
|
|
65370
|
+
// timeout on every fast tick.
|
|
65371
|
+
const timer = setTimeout(() => {
|
|
65372
|
+
const message = `${CRON_METHOD_NAME_TICK} timed out after ${CRON_HANDLER_TIMEOUT}ms`;
|
|
65373
|
+
const payload = { symbol, when, context };
|
|
65374
|
+
LOGGER_SERVICE$1.warn(message, payload);
|
|
65375
|
+
console.error(message, payload);
|
|
65376
|
+
errorEmitter.next(new Error(message));
|
|
65377
|
+
}, CRON_HANDLER_TIMEOUT);
|
|
65378
|
+
try {
|
|
65379
|
+
await Promise.all(taskList);
|
|
65380
|
+
}
|
|
65381
|
+
finally {
|
|
65382
|
+
clearTimeout(timer);
|
|
65383
|
+
}
|
|
65384
|
+
}
|
|
65385
|
+
// Roll back the watermark for any periodic slot THIS tick opened whose
|
|
65386
|
+
// handler failed, so the next tick re-opens the same boundary and retries
|
|
65387
|
+
// it — mirroring how a skipped boundary is later caught up. Restoring
|
|
65388
|
+
// `prevBoundary` (or deleting the key when it was `undefined`) re-arms the
|
|
65389
|
+
// strict-`>` gate without disturbing any earlier already-fired boundary.
|
|
65390
|
+
// `await pending` is cheap — every promise already settled in `Promise.all`
|
|
65391
|
+
// above; we re-await via `openedList` because its entries (opened slots
|
|
65392
|
+
// only) do not line up with `taskList` indices.
|
|
65393
|
+
for (const { boundaryKey, prevBoundary, pending } of openedList) {
|
|
65394
|
+
const failed = await pending;
|
|
65395
|
+
if (!failed) {
|
|
65396
|
+
continue;
|
|
65397
|
+
}
|
|
65398
|
+
if (prevBoundary === undefined) {
|
|
65399
|
+
this._lastBoundary.delete(boundaryKey);
|
|
65400
|
+
}
|
|
65401
|
+
else {
|
|
65402
|
+
this._lastBoundary.set(boundaryKey, prevBoundary);
|
|
65403
|
+
}
|
|
65404
|
+
}
|
|
64990
65405
|
};
|
|
64991
65406
|
/**
|
|
64992
65407
|
* Subscribe `Cron` to the engine's strategy lifecycle subjects so registered
|
|
@@ -65001,7 +65416,11 @@ class CronUtils {
|
|
|
65001
65416
|
*
|
|
65002
65417
|
* All four subjects are subscribed to a single `singlerun`-wrapped
|
|
65003
65418
|
* handler that builds `_tick(event.symbol, new Date(event.timestamp),
|
|
65004
|
-
* event.backtest
|
|
65419
|
+
* event.backtest, { strategyName, exchangeName, frameName })`. The context
|
|
65420
|
+
* object is read uniformly from the event — every contract carries
|
|
65421
|
+
* `strategyName`, `exchangeName` and `frameName` at the top level (Active /
|
|
65422
|
+
* Schedule contracts gained `frameName` for exactly this reason), so no
|
|
65423
|
+
* per-event branching is needed. `singlerun` merges the four streams into one serial
|
|
65005
65424
|
* queue: at most one `_tick` runs at a time, the next waits. This matters
|
|
65006
65425
|
* because the engine can emit `beforeStart` and an immediate `idlePing`
|
|
65007
65426
|
* on the very same minute, and concurrent `_tick`s on the same
|
|
@@ -65037,7 +65456,11 @@ class CronUtils {
|
|
|
65037
65456
|
this.enable = functoolsKit.singleshot(() => {
|
|
65038
65457
|
LOGGER_SERVICE$1.info(CRON_METHOD_NAME_ENABLE);
|
|
65039
65458
|
const handleTick = functoolsKit.singlerun(async (event) => {
|
|
65040
|
-
return await this._tick(event.symbol, new Date(event.timestamp), event.backtest
|
|
65459
|
+
return await this._tick(event.symbol, new Date(event.timestamp), event.backtest, {
|
|
65460
|
+
strategyName: event.strategyName,
|
|
65461
|
+
exchangeName: event.exchangeName,
|
|
65462
|
+
frameName: event.frameName,
|
|
65463
|
+
});
|
|
65041
65464
|
});
|
|
65042
65465
|
const unBeforeStart = beforeStartSubject.subscribe(handleTick);
|
|
65043
65466
|
const unIdlePing = idlePingSubject.subscribe(handleTick);
|
|
@@ -65140,25 +65563,51 @@ class CronUtils {
|
|
|
65140
65563
|
/**
|
|
65141
65564
|
* Build the singleshot promise for a single in-flight slot.
|
|
65142
65565
|
*
|
|
65143
|
-
*
|
|
65144
|
-
*
|
|
65145
|
-
*
|
|
65146
|
-
*
|
|
65147
|
-
*
|
|
65148
|
-
*
|
|
65566
|
+
* Assembles the {@link IRuntimeInfo} snapshot via
|
|
65567
|
+
* `RuntimeMetaService.getRuntimeInfo(symbol, context, backtest)` and invokes
|
|
65568
|
+
* `entry.handler(info)`. Logs any error via `console.error` and **returns** a
|
|
65569
|
+
* `failed` boolean (`true` when the handler — or the runtime-info assembly —
|
|
65570
|
+
* threw) so the caller (`_tick`) can roll back the periodic watermark of the
|
|
65571
|
+
* slot it opened and retry that boundary. The error is **not** rethrown, so a
|
|
65572
|
+
* failing handler never produces an unhandled rejection. Clears the
|
|
65573
|
+
* `_inFlight` slot in `.finally()` so the next boundary produces a fresh
|
|
65574
|
+
* promise. For fire-once entries `firedKey` is added to `_firedOnce` on
|
|
65575
|
+
* success so subsequent ticks skip it.
|
|
65576
|
+
*
|
|
65577
|
+
* `getRuntimeInfo` is the user-facing aggregator: its sub-fetches (range,
|
|
65578
|
+
* info, price) are individually wrapped in `trycatch` with `null` fallbacks,
|
|
65579
|
+
* so it almost never throws for missing data. Whatever does throw — the
|
|
65580
|
+
* handler, or in rare cases `getRuntimeInfo` — is caught here and reported via
|
|
65581
|
+
* the returned `failed` flag; the watermark rollback treats both identically.
|
|
65582
|
+
*
|
|
65583
|
+
* @param context - Strategy/exchange/frame identifiers from the originating
|
|
65584
|
+
* lifecycle event, forwarded to `getRuntimeInfo` to resolve `range`/`info`.
|
|
65149
65585
|
* @param firedKey - Key to add to `_firedOnce` on success, or `null` for
|
|
65150
65586
|
* periodic entries (which never populate `_firedOnce`).
|
|
65151
|
-
* @param backtest -
|
|
65152
|
-
* "winner" tick's flag is what all parallel awaiters
|
|
65587
|
+
* @param backtest - Forwarded to `getRuntimeInfo` and surfaced as
|
|
65588
|
+
* `info.backtest`; the "winner" tick's flag is what all parallel awaiters
|
|
65589
|
+
* of this slot see.
|
|
65590
|
+
* @returns `true` if the handler (or `getRuntimeInfo`) threw, `false` on
|
|
65591
|
+
* success. `_tick` uses this to decide whether to roll back the watermark.
|
|
65153
65592
|
*/
|
|
65154
|
-
async _runEntry(entry, symbol,
|
|
65593
|
+
async _runEntry(entry, symbol, alignedMs, slotKey, firedKey, backtest, context) {
|
|
65155
65594
|
let failed = false;
|
|
65156
65595
|
try {
|
|
65157
|
-
await
|
|
65596
|
+
const info = await RUNTIME_META_SERVICE.getRuntimeInfo(symbol, context, backtest);
|
|
65597
|
+
await entry.handler(info);
|
|
65158
65598
|
}
|
|
65159
|
-
catch (
|
|
65599
|
+
catch (error) {
|
|
65160
65600
|
failed = true;
|
|
65161
|
-
|
|
65601
|
+
const message = `${CRON_METHOD_NAME_TICK} entry "${entry.name}" failed`;
|
|
65602
|
+
const payload = {
|
|
65603
|
+
symbol,
|
|
65604
|
+
alignedMs,
|
|
65605
|
+
error: functoolsKit.errorData(error),
|
|
65606
|
+
message: functoolsKit.getErrorMessage(error),
|
|
65607
|
+
};
|
|
65608
|
+
LOGGER_SERVICE$1.warn(message, payload);
|
|
65609
|
+
console.error(message, payload);
|
|
65610
|
+
errorEmitter.next(error);
|
|
65162
65611
|
}
|
|
65163
65612
|
finally {
|
|
65164
65613
|
this._inFlight.delete(slotKey);
|
|
@@ -65166,6 +65615,7 @@ class CronUtils {
|
|
|
65166
65615
|
this._firedOnce.add(firedKey);
|
|
65167
65616
|
}
|
|
65168
65617
|
}
|
|
65618
|
+
return failed;
|
|
65169
65619
|
}
|
|
65170
65620
|
}
|
|
65171
65621
|
/**
|
|
@@ -65179,7 +65629,7 @@ class CronUtils {
|
|
|
65179
65629
|
* Cron.register({
|
|
65180
65630
|
* name: "tg-parser",
|
|
65181
65631
|
* interval: "1h",
|
|
65182
|
-
* handler: async (
|
|
65632
|
+
* handler: async (info) => { ... },
|
|
65183
65633
|
* });
|
|
65184
65634
|
* ```
|
|
65185
65635
|
*/
|
|
@@ -66528,6 +66978,7 @@ exports.getPositionPnlPercent = getPositionPnlPercent;
|
|
|
66528
66978
|
exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
|
|
66529
66979
|
exports.getRawCandles = getRawCandles;
|
|
66530
66980
|
exports.getRiskSchema = getRiskSchema;
|
|
66981
|
+
exports.getRuntimeInfo = getRuntimeInfo;
|
|
66531
66982
|
exports.getScheduledSignal = getScheduledSignal;
|
|
66532
66983
|
exports.getSessionData = getSessionData;
|
|
66533
66984
|
exports.getSignalState = getSignalState;
|