backtest-kit 9.8.4 → 10.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.mjs CHANGED
@@ -942,7 +942,7 @@ async function writeFileAtomic(file, data, options = {}) {
942
942
 
943
943
  var _a$3;
944
944
  /** Logger service injected as DI singleton */
945
- const LOGGER_SERVICE$8 = new LoggerService();
945
+ const LOGGER_SERVICE$9 = new LoggerService();
946
946
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
947
947
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
948
948
  // Calculate step in milliseconds for candle close time validation
@@ -1065,7 +1065,7 @@ const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
1065
1065
  const BASE_UNLINK_RETRY_COUNT = 5;
1066
1066
  const BASE_UNLINK_RETRY_DELAY = 1000;
1067
1067
  const BASE_WAIT_FOR_INIT_FN = async (self) => {
1068
- LOGGER_SERVICE$8.debug(BASE_WAIT_FOR_INIT_FN_METHOD_NAME, {
1068
+ LOGGER_SERVICE$9.debug(BASE_WAIT_FOR_INIT_FN_METHOD_NAME, {
1069
1069
  entityName: self.entityName,
1070
1070
  directory: self._directory,
1071
1071
  });
@@ -1123,7 +1123,7 @@ class PersistBase {
1123
1123
  this.entityName = entityName;
1124
1124
  this.baseDir = baseDir;
1125
1125
  this[_a$3] = singleshot(async () => await BASE_WAIT_FOR_INIT_FN(this));
1126
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_CTOR, {
1126
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_CTOR, {
1127
1127
  entityName: this.entityName,
1128
1128
  baseDir,
1129
1129
  });
@@ -1139,14 +1139,14 @@ class PersistBase {
1139
1139
  return join(this.baseDir, this.entityName, `${entityId}.json`);
1140
1140
  }
1141
1141
  async waitForInit(initial) {
1142
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
1142
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
1143
1143
  entityName: this.entityName,
1144
1144
  initial,
1145
1145
  });
1146
1146
  await this[BASE_WAIT_FOR_INIT_SYMBOL]();
1147
1147
  }
1148
1148
  async readValue(entityId) {
1149
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
1149
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
1150
1150
  entityName: this.entityName,
1151
1151
  entityId,
1152
1152
  });
@@ -1163,7 +1163,7 @@ class PersistBase {
1163
1163
  }
1164
1164
  }
1165
1165
  async hasValue(entityId) {
1166
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_HAS_VALUE, {
1166
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_HAS_VALUE, {
1167
1167
  entityName: this.entityName,
1168
1168
  entityId,
1169
1169
  });
@@ -1180,7 +1180,7 @@ class PersistBase {
1180
1180
  }
1181
1181
  }
1182
1182
  async writeValue(entityId, entity) {
1183
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_WRITE_VALUE, {
1183
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_WRITE_VALUE, {
1184
1184
  entityName: this.entityName,
1185
1185
  entityId,
1186
1186
  });
@@ -1202,7 +1202,7 @@ class PersistBase {
1202
1202
  * @throws Error if reading fails
1203
1203
  */
1204
1204
  async *keys() {
1205
- LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
1205
+ LOGGER_SERVICE$9.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
1206
1206
  entityName: this.entityName,
1207
1207
  });
1208
1208
  try {
@@ -1347,7 +1347,7 @@ class PersistSignalUtils {
1347
1347
  * @returns Promise resolving to signal or null if none persisted
1348
1348
  */
1349
1349
  this.readSignalData = async (symbol, strategyName, exchangeName) => {
1350
- LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
1350
+ LOGGER_SERVICE$9.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
1351
1351
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1352
1352
  const isInitial = !this.getStorage.has(key);
1353
1353
  const instance = this.getStorage(symbol, strategyName, exchangeName);
@@ -1365,7 +1365,7 @@ class PersistSignalUtils {
1365
1365
  * @returns Promise that resolves when write is complete
1366
1366
  */
1367
1367
  this.writeSignalData = async (signalRow, symbol, strategyName, exchangeName) => {
1368
- LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
1368
+ LOGGER_SERVICE$9.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
1369
1369
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1370
1370
  const isInitial = !this.getStorage.has(key);
1371
1371
  const instance = this.getStorage(symbol, strategyName, exchangeName);
@@ -1380,7 +1380,7 @@ class PersistSignalUtils {
1380
1380
  * @param Ctor - Custom IPersistSignalInstance constructor
1381
1381
  */
1382
1382
  usePersistSignalAdapter(Ctor) {
1383
- LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER);
1383
+ LOGGER_SERVICE$9.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER);
1384
1384
  this.PersistSignalInstanceCtor = Ctor;
1385
1385
  this.getStorage.clear();
1386
1386
  }
@@ -1389,21 +1389,21 @@ class PersistSignalUtils {
1389
1389
  * Call when process.cwd() changes between strategy iterations.
1390
1390
  */
1391
1391
  clear() {
1392
- LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_CLEAR);
1392
+ LOGGER_SERVICE$9.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_CLEAR);
1393
1393
  this.getStorage.clear();
1394
1394
  }
1395
1395
  /**
1396
1396
  * Switches to the default file-based PersistSignalInstance.
1397
1397
  */
1398
1398
  useJson() {
1399
- LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
1399
+ LOGGER_SERVICE$9.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
1400
1400
  this.usePersistSignalAdapter(PersistSignalInstance);
1401
1401
  }
1402
1402
  /**
1403
1403
  * Switches to PersistSignalDummyInstance (all operations are no-ops).
1404
1404
  */
1405
1405
  useDummy() {
1406
- LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
1406
+ LOGGER_SERVICE$9.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
1407
1407
  this.usePersistSignalAdapter(PersistSignalDummyInstance);
1408
1408
  }
1409
1409
  }
@@ -1543,7 +1543,7 @@ class PersistRiskUtils {
1543
1543
  * @returns Promise resolving to position entries (empty array if none)
1544
1544
  */
1545
1545
  this.readPositionData = async (riskName, exchangeName, when) => {
1546
- LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
1546
+ LOGGER_SERVICE$9.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
1547
1547
  const key = `${riskName}:${exchangeName}`;
1548
1548
  const isInitial = !this.getRiskStorage.has(key);
1549
1549
  const instance = this.getRiskStorage(riskName, exchangeName);
@@ -1561,7 +1561,7 @@ class PersistRiskUtils {
1561
1561
  * @returns Promise that resolves when write is complete
1562
1562
  */
1563
1563
  this.writePositionData = async (riskRow, riskName, exchangeName, when) => {
1564
- LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
1564
+ LOGGER_SERVICE$9.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
1565
1565
  const key = `${riskName}:${exchangeName}`;
1566
1566
  const isInitial = !this.getRiskStorage.has(key);
1567
1567
  const instance = this.getRiskStorage(riskName, exchangeName);
@@ -1576,7 +1576,7 @@ class PersistRiskUtils {
1576
1576
  * @param Ctor - Custom IPersistRiskInstance constructor
1577
1577
  */
1578
1578
  usePersistRiskAdapter(Ctor) {
1579
- LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_USE_PERSIST_RISK_ADAPTER);
1579
+ LOGGER_SERVICE$9.info(PERSIST_RISK_UTILS_METHOD_NAME_USE_PERSIST_RISK_ADAPTER);
1580
1580
  this.PersistRiskInstanceCtor = Ctor;
1581
1581
  this.getRiskStorage.clear();
1582
1582
  }
@@ -1585,21 +1585,21 @@ class PersistRiskUtils {
1585
1585
  * Call when process.cwd() changes between strategy iterations.
1586
1586
  */
1587
1587
  clear() {
1588
- LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_CLEAR);
1588
+ LOGGER_SERVICE$9.log(PERSIST_RISK_UTILS_METHOD_NAME_CLEAR);
1589
1589
  this.getRiskStorage.clear();
1590
1590
  }
1591
1591
  /**
1592
1592
  * Switches to the default file-based PersistRiskInstance.
1593
1593
  */
1594
1594
  useJson() {
1595
- LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_JSON);
1595
+ LOGGER_SERVICE$9.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_JSON);
1596
1596
  this.usePersistRiskAdapter(PersistRiskInstance);
1597
1597
  }
1598
1598
  /**
1599
1599
  * Switches to PersistRiskDummyInstance (all operations are no-ops).
1600
1600
  */
1601
1601
  useDummy() {
1602
- LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_DUMMY);
1602
+ LOGGER_SERVICE$9.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_DUMMY);
1603
1603
  this.usePersistRiskAdapter(PersistRiskDummyInstance);
1604
1604
  }
1605
1605
  }
@@ -1738,7 +1738,7 @@ class PersistScheduleUtils {
1738
1738
  * @returns Promise resolving to scheduled signal or null if none persisted
1739
1739
  */
1740
1740
  this.readScheduleData = async (symbol, strategyName, exchangeName) => {
1741
- LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
1741
+ LOGGER_SERVICE$9.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
1742
1742
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1743
1743
  const isInitial = !this.getScheduleStorage.has(key);
1744
1744
  const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
@@ -1756,7 +1756,7 @@ class PersistScheduleUtils {
1756
1756
  * @returns Promise that resolves when write is complete
1757
1757
  */
1758
1758
  this.writeScheduleData = async (scheduledSignalRow, symbol, strategyName, exchangeName) => {
1759
- LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
1759
+ LOGGER_SERVICE$9.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
1760
1760
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1761
1761
  const isInitial = !this.getScheduleStorage.has(key);
1762
1762
  const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
@@ -1771,7 +1771,7 @@ class PersistScheduleUtils {
1771
1771
  * @param Ctor - Custom IPersistScheduleInstance constructor
1772
1772
  */
1773
1773
  usePersistScheduleAdapter(Ctor) {
1774
- LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_PERSIST_SCHEDULE_ADAPTER);
1774
+ LOGGER_SERVICE$9.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_PERSIST_SCHEDULE_ADAPTER);
1775
1775
  this.PersistScheduleInstanceCtor = Ctor;
1776
1776
  this.getScheduleStorage.clear();
1777
1777
  }
@@ -1780,21 +1780,21 @@ class PersistScheduleUtils {
1780
1780
  * Call when process.cwd() changes between strategy iterations.
1781
1781
  */
1782
1782
  clear() {
1783
- LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR);
1783
+ LOGGER_SERVICE$9.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR);
1784
1784
  this.getScheduleStorage.clear();
1785
1785
  }
1786
1786
  /**
1787
1787
  * Switches to the default file-based PersistScheduleInstance.
1788
1788
  */
1789
1789
  useJson() {
1790
- LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON);
1790
+ LOGGER_SERVICE$9.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON);
1791
1791
  this.usePersistScheduleAdapter(PersistScheduleInstance);
1792
1792
  }
1793
1793
  /**
1794
1794
  * Switches to PersistScheduleDummyInstance (all operations are no-ops).
1795
1795
  */
1796
1796
  useDummy() {
1797
- LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY);
1797
+ LOGGER_SERVICE$9.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY);
1798
1798
  this.usePersistScheduleAdapter(PersistScheduleDummyInstance);
1799
1799
  }
1800
1800
  }
@@ -1939,7 +1939,7 @@ class PersistPartialUtils {
1939
1939
  * @returns Promise resolving to partial data record (empty object if none)
1940
1940
  */
1941
1941
  this.readPartialData = async (symbol, strategyName, signalId, exchangeName, when) => {
1942
- LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
1942
+ LOGGER_SERVICE$9.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
1943
1943
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1944
1944
  const isInitial = !this.getPartialStorage.has(key);
1945
1945
  const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
@@ -1959,7 +1959,7 @@ class PersistPartialUtils {
1959
1959
  * @returns Promise that resolves when write is complete
1960
1960
  */
1961
1961
  this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName, when) => {
1962
- LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
1962
+ LOGGER_SERVICE$9.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
1963
1963
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1964
1964
  const isInitial = !this.getPartialStorage.has(key);
1965
1965
  const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
@@ -1974,7 +1974,7 @@ class PersistPartialUtils {
1974
1974
  * @param Ctor - Custom IPersistPartialInstance constructor
1975
1975
  */
1976
1976
  usePersistPartialAdapter(Ctor) {
1977
- LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER);
1977
+ LOGGER_SERVICE$9.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER);
1978
1978
  this.PersistPartialInstanceCtor = Ctor;
1979
1979
  this.getPartialStorage.clear();
1980
1980
  }
@@ -1983,21 +1983,21 @@ class PersistPartialUtils {
1983
1983
  * Call when process.cwd() changes between strategy iterations.
1984
1984
  */
1985
1985
  clear() {
1986
- LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_CLEAR);
1986
+ LOGGER_SERVICE$9.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_CLEAR);
1987
1987
  this.getPartialStorage.clear();
1988
1988
  }
1989
1989
  /**
1990
1990
  * Switches to the default file-based PersistPartialInstance.
1991
1991
  */
1992
1992
  useJson() {
1993
- LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_JSON);
1993
+ LOGGER_SERVICE$9.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_JSON);
1994
1994
  this.usePersistPartialAdapter(PersistPartialInstance);
1995
1995
  }
1996
1996
  /**
1997
1997
  * Switches to PersistPartialDummyInstance (all operations are no-ops).
1998
1998
  */
1999
1999
  useDummy() {
2000
- LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_DUMMY);
2000
+ LOGGER_SERVICE$9.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_DUMMY);
2001
2001
  this.usePersistPartialAdapter(PersistPartialDummyInstance);
2002
2002
  }
2003
2003
  }
@@ -2162,7 +2162,7 @@ class PersistBreakevenUtils {
2162
2162
  * @returns Promise resolving to breakeven data record (empty object if none)
2163
2163
  */
2164
2164
  this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName, when) => {
2165
- LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
2165
+ LOGGER_SERVICE$9.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
2166
2166
  const key = `${symbol}:${strategyName}:${exchangeName}`;
2167
2167
  const isInitial = !this.getBreakevenStorage.has(key);
2168
2168
  const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
@@ -2182,7 +2182,7 @@ class PersistBreakevenUtils {
2182
2182
  * @returns Promise that resolves when write is complete
2183
2183
  */
2184
2184
  this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName, when) => {
2185
- LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
2185
+ LOGGER_SERVICE$9.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
2186
2186
  const key = `${symbol}:${strategyName}:${exchangeName}`;
2187
2187
  const isInitial = !this.getBreakevenStorage.has(key);
2188
2188
  const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
@@ -2197,7 +2197,7 @@ class PersistBreakevenUtils {
2197
2197
  * @param Ctor - Custom IPersistBreakevenInstance constructor
2198
2198
  */
2199
2199
  usePersistBreakevenAdapter(Ctor) {
2200
- LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_PERSIST_BREAKEVEN_ADAPTER);
2200
+ LOGGER_SERVICE$9.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_PERSIST_BREAKEVEN_ADAPTER);
2201
2201
  this.PersistBreakevenInstanceCtor = Ctor;
2202
2202
  this.getBreakevenStorage.clear();
2203
2203
  }
@@ -2206,21 +2206,21 @@ class PersistBreakevenUtils {
2206
2206
  * Call when process.cwd() changes between strategy iterations.
2207
2207
  */
2208
2208
  clear() {
2209
- LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_CLEAR);
2209
+ LOGGER_SERVICE$9.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_CLEAR);
2210
2210
  this.getBreakevenStorage.clear();
2211
2211
  }
2212
2212
  /**
2213
2213
  * Switches to the default file-based PersistBreakevenInstance.
2214
2214
  */
2215
2215
  useJson() {
2216
- LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_JSON);
2216
+ LOGGER_SERVICE$9.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_JSON);
2217
2217
  this.usePersistBreakevenAdapter(PersistBreakevenInstance);
2218
2218
  }
2219
2219
  /**
2220
2220
  * Switches to PersistBreakevenDummyInstance (all operations are no-ops).
2221
2221
  */
2222
2222
  useDummy() {
2223
- LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_DUMMY);
2223
+ LOGGER_SERVICE$9.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_DUMMY);
2224
2224
  this.usePersistBreakevenAdapter(PersistBreakevenDummyInstance);
2225
2225
  }
2226
2226
  }
@@ -2311,7 +2311,7 @@ class PersistCandleInstance {
2311
2311
  error: errorData(error),
2312
2312
  message: getErrorMessage(error),
2313
2313
  };
2314
- LOGGER_SERVICE$8.warn(message, payload);
2314
+ LOGGER_SERVICE$9.warn(message, payload);
2315
2315
  console.warn(message, payload);
2316
2316
  errorEmitter.next(error);
2317
2317
  return null;
@@ -2333,7 +2333,7 @@ class PersistCandleInstance {
2333
2333
  for (const candle of candles) {
2334
2334
  const candleCloseTime = candle.timestamp + stepMs;
2335
2335
  if (candleCloseTime > now) {
2336
- LOGGER_SERVICE$8.debug("PersistCandleInstance.writeCandlesData: skipping incomplete candle", {
2336
+ LOGGER_SERVICE$9.debug("PersistCandleInstance.writeCandlesData: skipping incomplete candle", {
2337
2337
  symbol: this.symbol,
2338
2338
  interval: this.interval,
2339
2339
  exchangeName: this.exchangeName,
@@ -2410,7 +2410,7 @@ class PersistCandleUtils {
2410
2410
  * @returns Promise resolving to candles in order, or null on cache miss
2411
2411
  */
2412
2412
  this.readCandlesData = async (symbol, interval, exchangeName, limit, sinceTimestamp, untilTimestamp) => {
2413
- LOGGER_SERVICE$8.info("PersistCandleUtils.readCandlesData", {
2413
+ LOGGER_SERVICE$9.info("PersistCandleUtils.readCandlesData", {
2414
2414
  symbol,
2415
2415
  interval,
2416
2416
  exchangeName,
@@ -2434,7 +2434,7 @@ class PersistCandleUtils {
2434
2434
  * @returns Promise that resolves when all writes are complete
2435
2435
  */
2436
2436
  this.writeCandlesData = async (candles, symbol, interval, exchangeName) => {
2437
- LOGGER_SERVICE$8.info("PersistCandleUtils.writeCandlesData", {
2437
+ LOGGER_SERVICE$9.info("PersistCandleUtils.writeCandlesData", {
2438
2438
  symbol,
2439
2439
  interval,
2440
2440
  exchangeName,
@@ -2454,7 +2454,7 @@ class PersistCandleUtils {
2454
2454
  * @param Ctor - Custom IPersistCandleInstance constructor
2455
2455
  */
2456
2456
  usePersistCandleAdapter(Ctor) {
2457
- LOGGER_SERVICE$8.info("PersistCandleUtils.usePersistCandleAdapter");
2457
+ LOGGER_SERVICE$9.info("PersistCandleUtils.usePersistCandleAdapter");
2458
2458
  this.PersistCandleInstanceCtor = Ctor;
2459
2459
  this.getCandlesStorage.clear();
2460
2460
  }
@@ -2463,21 +2463,21 @@ class PersistCandleUtils {
2463
2463
  * Call when process.cwd() changes between strategy iterations.
2464
2464
  */
2465
2465
  clear() {
2466
- LOGGER_SERVICE$8.log(PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR);
2466
+ LOGGER_SERVICE$9.log(PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR);
2467
2467
  this.getCandlesStorage.clear();
2468
2468
  }
2469
2469
  /**
2470
2470
  * Switches to the default file-based PersistCandleInstance.
2471
2471
  */
2472
2472
  useJson() {
2473
- LOGGER_SERVICE$8.log("PersistCandleUtils.useJson");
2473
+ LOGGER_SERVICE$9.log("PersistCandleUtils.useJson");
2474
2474
  this.usePersistCandleAdapter(PersistCandleInstance);
2475
2475
  }
2476
2476
  /**
2477
2477
  * Switches to PersistCandleDummyInstance (always returns null on read, discards writes).
2478
2478
  */
2479
2479
  useDummy() {
2480
- LOGGER_SERVICE$8.log("PersistCandleUtils.useDummy");
2480
+ LOGGER_SERVICE$9.log("PersistCandleUtils.useDummy");
2481
2481
  this.usePersistCandleAdapter(PersistCandleDummyInstance);
2482
2482
  }
2483
2483
  }
@@ -2615,7 +2615,7 @@ class PersistStorageUtils {
2615
2615
  * @returns Promise resolving to array of signal entries
2616
2616
  */
2617
2617
  this.readStorageData = async (backtest) => {
2618
- LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
2618
+ LOGGER_SERVICE$9.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
2619
2619
  const key = backtest ? `backtest` : `live`;
2620
2620
  const isInitial = !this.getStorage.has(key);
2621
2621
  const instance = this.getStorage(backtest);
@@ -2631,7 +2631,7 @@ class PersistStorageUtils {
2631
2631
  * @returns Promise that resolves when write is complete
2632
2632
  */
2633
2633
  this.writeStorageData = async (signalData, backtest) => {
2634
- LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
2634
+ LOGGER_SERVICE$9.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
2635
2635
  const key = backtest ? `backtest` : `live`;
2636
2636
  const isInitial = !this.getStorage.has(key);
2637
2637
  const instance = this.getStorage(backtest);
@@ -2646,7 +2646,7 @@ class PersistStorageUtils {
2646
2646
  * @param Ctor - Custom IPersistStorageInstance constructor
2647
2647
  */
2648
2648
  usePersistStorageAdapter(Ctor) {
2649
- LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
2649
+ LOGGER_SERVICE$9.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
2650
2650
  this.PersistStorageInstanceCtor = Ctor;
2651
2651
  this.getStorage.clear();
2652
2652
  }
@@ -2655,21 +2655,21 @@ class PersistStorageUtils {
2655
2655
  * Call when process.cwd() changes between strategy iterations.
2656
2656
  */
2657
2657
  clear() {
2658
- LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_CLEAR);
2658
+ LOGGER_SERVICE$9.log(PERSIST_STORAGE_UTILS_METHOD_NAME_CLEAR);
2659
2659
  this.getStorage.clear();
2660
2660
  }
2661
2661
  /**
2662
2662
  * Switches to the default file-based PersistStorageInstance.
2663
2663
  */
2664
2664
  useJson() {
2665
- LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
2665
+ LOGGER_SERVICE$9.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
2666
2666
  this.usePersistStorageAdapter(PersistStorageInstance);
2667
2667
  }
2668
2668
  /**
2669
2669
  * Switches to PersistStorageDummyInstance (all operations are no-ops).
2670
2670
  */
2671
2671
  useDummy() {
2672
- LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
2672
+ LOGGER_SERVICE$9.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
2673
2673
  this.usePersistStorageAdapter(PersistStorageDummyInstance);
2674
2674
  }
2675
2675
  }
@@ -2796,7 +2796,7 @@ class PersistNotificationUtils {
2796
2796
  * @returns Promise resolving to array of notification entries
2797
2797
  */
2798
2798
  this.readNotificationData = async (backtest) => {
2799
- LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
2799
+ LOGGER_SERVICE$9.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
2800
2800
  const key = backtest ? `backtest` : `live`;
2801
2801
  const isInitial = !this.getNotificationStorage.has(key);
2802
2802
  const instance = this.getNotificationStorage(backtest);
@@ -2812,7 +2812,7 @@ class PersistNotificationUtils {
2812
2812
  * @returns Promise that resolves when write is complete
2813
2813
  */
2814
2814
  this.writeNotificationData = async (notificationData, backtest) => {
2815
- LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
2815
+ LOGGER_SERVICE$9.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
2816
2816
  const key = backtest ? `backtest` : `live`;
2817
2817
  const isInitial = !this.getNotificationStorage.has(key);
2818
2818
  const instance = this.getNotificationStorage(backtest);
@@ -2827,7 +2827,7 @@ class PersistNotificationUtils {
2827
2827
  * @param Ctor - Custom IPersistNotificationInstance constructor
2828
2828
  */
2829
2829
  usePersistNotificationAdapter(Ctor) {
2830
- LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
2830
+ LOGGER_SERVICE$9.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
2831
2831
  this.PersistNotificationInstanceCtor = Ctor;
2832
2832
  this.getNotificationStorage.clear();
2833
2833
  }
@@ -2837,21 +2837,21 @@ class PersistNotificationUtils {
2837
2837
  * instances are created with the updated base path.
2838
2838
  */
2839
2839
  clear() {
2840
- LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_CLEAR);
2840
+ LOGGER_SERVICE$9.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_CLEAR);
2841
2841
  this.getNotificationStorage.clear();
2842
2842
  }
2843
2843
  /**
2844
2844
  * Switches to the default file-based PersistNotificationInstance.
2845
2845
  */
2846
2846
  useJson() {
2847
- LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
2847
+ LOGGER_SERVICE$9.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
2848
2848
  this.usePersistNotificationAdapter(PersistNotificationInstance);
2849
2849
  }
2850
2850
  /**
2851
2851
  * Switches to PersistNotificationDummyInstance (all operations are no-ops).
2852
2852
  */
2853
2853
  useDummy() {
2854
- LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
2854
+ LOGGER_SERVICE$9.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
2855
2855
  this.usePersistNotificationAdapter(PersistNotificationDummyInstance);
2856
2856
  }
2857
2857
  }
@@ -2975,7 +2975,7 @@ class PersistLogUtils {
2975
2975
  * @returns Promise resolving to array of log entries
2976
2976
  */
2977
2977
  this.readLogData = async () => {
2978
- LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_READ_DATA);
2978
+ LOGGER_SERVICE$9.info(PERSIST_LOG_UTILS_METHOD_NAME_READ_DATA);
2979
2979
  const isInitial = !this._logInstance;
2980
2980
  const instance = this.getLogInstance();
2981
2981
  await instance.waitForInit(isInitial);
@@ -2989,7 +2989,7 @@ class PersistLogUtils {
2989
2989
  * @returns Promise that resolves when write is complete
2990
2990
  */
2991
2991
  this.writeLogData = async (logData) => {
2992
- LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA);
2992
+ LOGGER_SERVICE$9.info(PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA);
2993
2993
  const isInitial = !this._logInstance;
2994
2994
  const instance = this.getLogInstance();
2995
2995
  await instance.waitForInit(isInitial);
@@ -3014,7 +3014,7 @@ class PersistLogUtils {
3014
3014
  * @param Ctor - Custom IPersistLogInstance constructor
3015
3015
  */
3016
3016
  usePersistLogAdapter(Ctor) {
3017
- LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER);
3017
+ LOGGER_SERVICE$9.info(PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER);
3018
3018
  this.PersistLogInstanceCtor = Ctor;
3019
3019
  this._logInstance = null;
3020
3020
  }
@@ -3023,21 +3023,21 @@ class PersistLogUtils {
3023
3023
  * Call when process.cwd() changes between strategy iterations.
3024
3024
  */
3025
3025
  clear() {
3026
- LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_CLEAR);
3026
+ LOGGER_SERVICE$9.log(PERSIST_LOG_UTILS_METHOD_NAME_CLEAR);
3027
3027
  this._logInstance = null;
3028
3028
  }
3029
3029
  /**
3030
3030
  * Switches to the default file-based PersistLogInstance.
3031
3031
  */
3032
3032
  useJson() {
3033
- LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON);
3033
+ LOGGER_SERVICE$9.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON);
3034
3034
  this.usePersistLogAdapter(PersistLogInstance);
3035
3035
  }
3036
3036
  /**
3037
3037
  * Switches to PersistLogDummyInstance (all operations are no-ops).
3038
3038
  */
3039
3039
  useDummy() {
3040
- LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY);
3040
+ LOGGER_SERVICE$9.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY);
3041
3041
  this.usePersistLogAdapter(PersistLogDummyInstance);
3042
3042
  }
3043
3043
  }
@@ -3199,7 +3199,7 @@ class PersistMeasureUtils {
3199
3199
  * @returns Promise resolving to cached value, or null if not found / soft-deleted
3200
3200
  */
3201
3201
  this.readMeasureData = async (bucket, key) => {
3202
- LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3202
+ LOGGER_SERVICE$9.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3203
3203
  const isInitial = !this.getMeasureStorage.has(bucket);
3204
3204
  const instance = this.getMeasureStorage(bucket);
3205
3205
  await instance.waitForInit(isInitial);
@@ -3215,7 +3215,7 @@ class PersistMeasureUtils {
3215
3215
  * @returns Promise that resolves when write is complete
3216
3216
  */
3217
3217
  this.writeMeasureData = async (data, bucket, key, when) => {
3218
- LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3218
+ LOGGER_SERVICE$9.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3219
3219
  const isInitial = !this.getMeasureStorage.has(bucket);
3220
3220
  const instance = this.getMeasureStorage(bucket);
3221
3221
  await instance.waitForInit(isInitial);
@@ -3230,7 +3230,7 @@ class PersistMeasureUtils {
3230
3230
  * @returns Promise that resolves when removal is complete
3231
3231
  */
3232
3232
  this.removeMeasureData = async (bucket, key) => {
3233
- LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3233
+ LOGGER_SERVICE$9.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3234
3234
  const isInitial = !this.getMeasureStorage.has(bucket);
3235
3235
  const instance = this.getMeasureStorage(bucket);
3236
3236
  await instance.waitForInit(isInitial);
@@ -3244,7 +3244,7 @@ class PersistMeasureUtils {
3244
3244
  * @param Ctor - Custom IPersistMeasureInstance constructor
3245
3245
  */
3246
3246
  usePersistMeasureAdapter(Ctor) {
3247
- LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
3247
+ LOGGER_SERVICE$9.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
3248
3248
  this.PersistMeasureInstanceCtor = Ctor;
3249
3249
  this.getMeasureStorage.clear();
3250
3250
  }
@@ -3256,7 +3256,7 @@ class PersistMeasureUtils {
3256
3256
  * @returns AsyncGenerator yielding entry keys
3257
3257
  */
3258
3258
  async *listMeasureData(bucket) {
3259
- LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3259
+ LOGGER_SERVICE$9.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3260
3260
  const isInitial = !this.getMeasureStorage.has(bucket);
3261
3261
  const instance = this.getMeasureStorage(bucket);
3262
3262
  await instance.waitForInit(isInitial);
@@ -3267,21 +3267,21 @@ class PersistMeasureUtils {
3267
3267
  * Call when process.cwd() changes between strategy iterations.
3268
3268
  */
3269
3269
  clear() {
3270
- LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR);
3270
+ LOGGER_SERVICE$9.log(PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR);
3271
3271
  this.getMeasureStorage.clear();
3272
3272
  }
3273
3273
  /**
3274
3274
  * Switches to the default file-based PersistMeasureInstance.
3275
3275
  */
3276
3276
  useJson() {
3277
- LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
3277
+ LOGGER_SERVICE$9.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
3278
3278
  this.usePersistMeasureAdapter(PersistMeasureInstance);
3279
3279
  }
3280
3280
  /**
3281
3281
  * Switches to PersistMeasureDummyInstance (all operations are no-ops).
3282
3282
  */
3283
3283
  useDummy() {
3284
- LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
3284
+ LOGGER_SERVICE$9.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
3285
3285
  this.usePersistMeasureAdapter(PersistMeasureDummyInstance);
3286
3286
  }
3287
3287
  }
@@ -3440,7 +3440,7 @@ class PersistIntervalUtils {
3440
3440
  * @returns Promise resolving to marker data, or null if not found / soft-deleted
3441
3441
  */
3442
3442
  this.readIntervalData = async (bucket, key) => {
3443
- LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3443
+ LOGGER_SERVICE$9.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3444
3444
  const isInitial = !this.getIntervalStorage.has(bucket);
3445
3445
  const instance = this.getIntervalStorage(bucket);
3446
3446
  await instance.waitForInit(isInitial);
@@ -3456,7 +3456,7 @@ class PersistIntervalUtils {
3456
3456
  * @returns Promise that resolves when write is complete
3457
3457
  */
3458
3458
  this.writeIntervalData = async (data, bucket, key, when) => {
3459
- LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3459
+ LOGGER_SERVICE$9.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3460
3460
  const isInitial = !this.getIntervalStorage.has(bucket);
3461
3461
  const instance = this.getIntervalStorage(bucket);
3462
3462
  await instance.waitForInit(isInitial);
@@ -3471,7 +3471,7 @@ class PersistIntervalUtils {
3471
3471
  * @returns Promise that resolves when removal is complete
3472
3472
  */
3473
3473
  this.removeIntervalData = async (bucket, key) => {
3474
- LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3474
+ LOGGER_SERVICE$9.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3475
3475
  const isInitial = !this.getIntervalStorage.has(bucket);
3476
3476
  const instance = this.getIntervalStorage(bucket);
3477
3477
  await instance.waitForInit(isInitial);
@@ -3485,7 +3485,7 @@ class PersistIntervalUtils {
3485
3485
  * @param Ctor - Custom IPersistIntervalInstance constructor
3486
3486
  */
3487
3487
  usePersistIntervalAdapter(Ctor) {
3488
- LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
3488
+ LOGGER_SERVICE$9.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
3489
3489
  this.PersistIntervalInstanceCtor = Ctor;
3490
3490
  this.getIntervalStorage.clear();
3491
3491
  }
@@ -3497,7 +3497,7 @@ class PersistIntervalUtils {
3497
3497
  * @returns AsyncGenerator yielding marker keys
3498
3498
  */
3499
3499
  async *listIntervalData(bucket) {
3500
- LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3500
+ LOGGER_SERVICE$9.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3501
3501
  const isInitial = !this.getIntervalStorage.has(bucket);
3502
3502
  const instance = this.getIntervalStorage(bucket);
3503
3503
  await instance.waitForInit(isInitial);
@@ -3508,21 +3508,21 @@ class PersistIntervalUtils {
3508
3508
  * Call when process.cwd() changes between strategy iterations.
3509
3509
  */
3510
3510
  clear() {
3511
- LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
3511
+ LOGGER_SERVICE$9.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
3512
3512
  this.getIntervalStorage.clear();
3513
3513
  }
3514
3514
  /**
3515
3515
  * Switches to the default file-based PersistIntervalInstance.
3516
3516
  */
3517
3517
  useJson() {
3518
- LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
3518
+ LOGGER_SERVICE$9.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
3519
3519
  this.usePersistIntervalAdapter(PersistIntervalInstance);
3520
3520
  }
3521
3521
  /**
3522
3522
  * Switches to PersistIntervalDummyInstance (all operations are no-ops).
3523
3523
  */
3524
3524
  useDummy() {
3525
- LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
3525
+ LOGGER_SERVICE$9.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
3526
3526
  this.usePersistIntervalAdapter(PersistIntervalDummyInstance);
3527
3527
  }
3528
3528
  }
@@ -3728,7 +3728,7 @@ class PersistMemoryUtils {
3728
3728
  * @returns Promise resolving to entry data, or null if not found / soft-deleted
3729
3729
  */
3730
3730
  this.readMemoryData = async (signalId, bucketName, memoryId) => {
3731
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName, memoryId });
3731
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName, memoryId });
3732
3732
  const key = `${signalId}:${bucketName}`;
3733
3733
  const isInitial = !this.getMemoryStorage.has(key);
3734
3734
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3745,7 +3745,7 @@ class PersistMemoryUtils {
3745
3745
  * @returns Promise resolving to true if entry exists
3746
3746
  */
3747
3747
  this.hasMemoryData = async (signalId, bucketName, memoryId) => {
3748
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, { signalId, bucketName, memoryId });
3748
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, { signalId, bucketName, memoryId });
3749
3749
  const key = `${signalId}:${bucketName}`;
3750
3750
  const isInitial = !this.getMemoryStorage.has(key);
3751
3751
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3764,7 +3764,7 @@ class PersistMemoryUtils {
3764
3764
  * @returns Promise that resolves when write is complete
3765
3765
  */
3766
3766
  this.writeMemoryData = async (data, signalId, bucketName, memoryId, when) => {
3767
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
3767
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
3768
3768
  const key = `${signalId}:${bucketName}`;
3769
3769
  const isInitial = !this.getMemoryStorage.has(key);
3770
3770
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3781,7 +3781,7 @@ class PersistMemoryUtils {
3781
3781
  * @returns Promise that resolves when removal is complete
3782
3782
  */
3783
3783
  this.removeMemoryData = async (signalId, bucketName, memoryId) => {
3784
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, { signalId, bucketName, memoryId });
3784
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, { signalId, bucketName, memoryId });
3785
3785
  const key = `${signalId}:${bucketName}`;
3786
3786
  const isInitial = !this.getMemoryStorage.has(key);
3787
3787
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3793,7 +3793,7 @@ class PersistMemoryUtils {
3793
3793
  * Call when process.cwd() changes between strategy iterations.
3794
3794
  */
3795
3795
  this.clear = () => {
3796
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR);
3796
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR);
3797
3797
  this.getMemoryStorage.clear();
3798
3798
  };
3799
3799
  /**
@@ -3804,7 +3804,7 @@ class PersistMemoryUtils {
3804
3804
  * @param bucketName - Bucket name
3805
3805
  */
3806
3806
  this.dispose = (signalId, bucketName) => {
3807
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE);
3807
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE);
3808
3808
  const key = `${signalId}:${bucketName}`;
3809
3809
  this.getMemoryStorage.clear(key);
3810
3810
  };
@@ -3816,7 +3816,7 @@ class PersistMemoryUtils {
3816
3816
  * @param Ctor - Custom IPersistMemoryInstance constructor
3817
3817
  */
3818
3818
  usePersistMemoryAdapter(Ctor) {
3819
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER);
3819
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER);
3820
3820
  this.PersistMemoryInstanceCtor = Ctor;
3821
3821
  this.getMemoryStorage.clear();
3822
3822
  }
@@ -3830,7 +3830,7 @@ class PersistMemoryUtils {
3830
3830
  * @returns AsyncGenerator yielding `{ memoryId, data }` tuples
3831
3831
  */
3832
3832
  async *listMemoryData(signalId, bucketName) {
3833
- LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, { signalId, bucketName });
3833
+ LOGGER_SERVICE$9.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, { signalId, bucketName });
3834
3834
  const key = `${signalId}:${bucketName}`;
3835
3835
  const isInitial = !this.getMemoryStorage.has(key);
3836
3836
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3841,14 +3841,14 @@ class PersistMemoryUtils {
3841
3841
  * Switches to the default file-based PersistMemoryInstance.
3842
3842
  */
3843
3843
  useJson() {
3844
- LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
3844
+ LOGGER_SERVICE$9.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
3845
3845
  this.usePersistMemoryAdapter(PersistMemoryInstance);
3846
3846
  }
3847
3847
  /**
3848
3848
  * Switches to PersistMemoryDummyInstance (all operations are no-ops).
3849
3849
  */
3850
3850
  useDummy() {
3851
- LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
3851
+ LOGGER_SERVICE$9.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
3852
3852
  this.usePersistMemoryAdapter(PersistMemoryDummyInstance);
3853
3853
  }
3854
3854
  }
@@ -3997,7 +3997,7 @@ class PersistRecentUtils {
3997
3997
  * @returns Promise resolving to recent signal or null if none persisted
3998
3998
  */
3999
3999
  this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
4000
- LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
4000
+ LOGGER_SERVICE$9.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
4001
4001
  const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
4002
4002
  const isInitial = !this.getStorage.has(key);
4003
4003
  const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
@@ -4018,7 +4018,7 @@ class PersistRecentUtils {
4018
4018
  * @returns Promise that resolves when write is complete
4019
4019
  */
4020
4020
  this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest, when) => {
4021
- LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
4021
+ LOGGER_SERVICE$9.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
4022
4022
  const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
4023
4023
  const isInitial = !this.getStorage.has(key);
4024
4024
  const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
@@ -4051,7 +4051,7 @@ class PersistRecentUtils {
4051
4051
  * @param Ctor - Custom IPersistRecentInstance constructor
4052
4052
  */
4053
4053
  usePersistRecentAdapter(Ctor) {
4054
- LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
4054
+ LOGGER_SERVICE$9.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
4055
4055
  this.PersistRecentInstanceCtor = Ctor;
4056
4056
  this.getStorage.clear();
4057
4057
  }
@@ -4060,21 +4060,21 @@ class PersistRecentUtils {
4060
4060
  * Call when process.cwd() changes between strategy iterations.
4061
4061
  */
4062
4062
  clear() {
4063
- LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
4063
+ LOGGER_SERVICE$9.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
4064
4064
  this.getStorage.clear();
4065
4065
  }
4066
4066
  /**
4067
4067
  * Switches to the default file-based PersistRecentInstance.
4068
4068
  */
4069
4069
  useJson() {
4070
- LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
4070
+ LOGGER_SERVICE$9.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
4071
4071
  this.usePersistRecentAdapter(PersistRecentInstance);
4072
4072
  }
4073
4073
  /**
4074
4074
  * Switches to PersistRecentDummyInstance (all operations are no-ops).
4075
4075
  */
4076
4076
  useDummy() {
4077
- LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
4077
+ LOGGER_SERVICE$9.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
4078
4078
  this.usePersistRecentAdapter(PersistRecentDummyInstance);
4079
4079
  }
4080
4080
  }
@@ -4209,7 +4209,7 @@ class PersistStateUtils {
4209
4209
  * @returns Promise that resolves when initialization is complete
4210
4210
  */
4211
4211
  this.waitForInit = async (signalId, bucketName, initial) => {
4212
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, { signalId, bucketName, initial });
4212
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, { signalId, bucketName, initial });
4213
4213
  const key = `${signalId}:${bucketName}`;
4214
4214
  const isInitial = initial && !this.getStateStorage.has(key);
4215
4215
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4224,7 +4224,7 @@ class PersistStateUtils {
4224
4224
  * @returns Promise resolving to state data or null if none persisted
4225
4225
  */
4226
4226
  this.readStateData = async (signalId, bucketName) => {
4227
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName });
4227
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName });
4228
4228
  const key = `${signalId}:${bucketName}`;
4229
4229
  const isInitial = !this.getStateStorage.has(key);
4230
4230
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4242,7 +4242,7 @@ class PersistStateUtils {
4242
4242
  * @returns Promise that resolves when write is complete
4243
4243
  */
4244
4244
  this.writeStateData = async (data, signalId, bucketName, when) => {
4245
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
4245
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
4246
4246
  const key = `${signalId}:${bucketName}`;
4247
4247
  const isInitial = !this.getStateStorage.has(key);
4248
4248
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4253,14 +4253,14 @@ class PersistStateUtils {
4253
4253
  * Switches to PersistStateDummyInstance (all operations are no-ops).
4254
4254
  */
4255
4255
  this.useDummy = () => {
4256
- LOGGER_SERVICE$8.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
4256
+ LOGGER_SERVICE$9.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
4257
4257
  this.usePersistStateAdapter(PersistStateDummyInstance);
4258
4258
  };
4259
4259
  /**
4260
4260
  * Switches to the default file-based PersistStateInstance.
4261
4261
  */
4262
4262
  this.useJson = () => {
4263
- LOGGER_SERVICE$8.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
4263
+ LOGGER_SERVICE$9.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
4264
4264
  this.usePersistStateAdapter(PersistStateInstance);
4265
4265
  };
4266
4266
  /**
@@ -4268,7 +4268,7 @@ class PersistStateUtils {
4268
4268
  * Call when process.cwd() changes between strategy iterations.
4269
4269
  */
4270
4270
  this.clear = () => {
4271
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
4271
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
4272
4272
  this.getStateStorage.clear();
4273
4273
  };
4274
4274
  /**
@@ -4279,7 +4279,7 @@ class PersistStateUtils {
4279
4279
  * @param bucketName - Bucket name
4280
4280
  */
4281
4281
  this.dispose = (signalId, bucketName) => {
4282
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
4282
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
4283
4283
  const key = `${signalId}:${bucketName}`;
4284
4284
  this.getStateStorage.clear(key);
4285
4285
  };
@@ -4291,7 +4291,7 @@ class PersistStateUtils {
4291
4291
  * @param Ctor - Custom IPersistStateInstance constructor
4292
4292
  */
4293
4293
  usePersistStateAdapter(Ctor) {
4294
- LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
4294
+ LOGGER_SERVICE$9.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
4295
4295
  this.PersistStateInstanceCtor = Ctor;
4296
4296
  this.getStateStorage.clear();
4297
4297
  }
@@ -4431,7 +4431,7 @@ class PersistSessionUtils {
4431
4431
  * @returns Promise that resolves when initialization is complete
4432
4432
  */
4433
4433
  this.waitForInit = async (strategyName, exchangeName, frameName, initial) => {
4434
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, { strategyName, exchangeName, frameName, initial });
4434
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, { strategyName, exchangeName, frameName, initial });
4435
4435
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4436
4436
  const isInitial = initial && !this.getSessionStorage.has(key);
4437
4437
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4447,7 +4447,7 @@ class PersistSessionUtils {
4447
4447
  * @returns Promise resolving to session data or null if none persisted
4448
4448
  */
4449
4449
  this.readSessionData = async (strategyName, exchangeName, frameName) => {
4450
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, { strategyName, exchangeName, frameName });
4450
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, { strategyName, exchangeName, frameName });
4451
4451
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4452
4452
  const isInitial = !this.getSessionStorage.has(key);
4453
4453
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4466,7 +4466,7 @@ class PersistSessionUtils {
4466
4466
  * @returns Promise that resolves when write is complete
4467
4467
  */
4468
4468
  this.writeSessionData = async (data, strategyName, exchangeName, frameName, when) => {
4469
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
4469
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
4470
4470
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4471
4471
  const isInitial = !this.getSessionStorage.has(key);
4472
4472
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4477,14 +4477,14 @@ class PersistSessionUtils {
4477
4477
  * Switches to PersistSessionDummyInstance (all operations are no-ops).
4478
4478
  */
4479
4479
  this.useDummy = () => {
4480
- LOGGER_SERVICE$8.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
4480
+ LOGGER_SERVICE$9.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
4481
4481
  this.usePersistSessionAdapter(PersistSessionDummyInstance);
4482
4482
  };
4483
4483
  /**
4484
4484
  * Switches to the default file-based PersistSessionInstance.
4485
4485
  */
4486
4486
  this.useJson = () => {
4487
- LOGGER_SERVICE$8.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
4487
+ LOGGER_SERVICE$9.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
4488
4488
  this.usePersistSessionAdapter(PersistSessionInstance);
4489
4489
  };
4490
4490
  /**
@@ -4492,7 +4492,7 @@ class PersistSessionUtils {
4492
4492
  * Call when process.cwd() changes between strategy iterations.
4493
4493
  */
4494
4494
  this.clear = () => {
4495
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
4495
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
4496
4496
  this.getSessionStorage.clear();
4497
4497
  };
4498
4498
  /**
@@ -4504,7 +4504,7 @@ class PersistSessionUtils {
4504
4504
  * @param frameName - Frame identifier
4505
4505
  */
4506
4506
  this.dispose = (strategyName, exchangeName, frameName) => {
4507
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE);
4507
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE);
4508
4508
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4509
4509
  this.getSessionStorage.clear(key);
4510
4510
  };
@@ -4516,7 +4516,7 @@ class PersistSessionUtils {
4516
4516
  * @param Ctor - Custom IPersistSessionInstance constructor
4517
4517
  */
4518
4518
  usePersistSessionAdapter(Ctor) {
4519
- LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
4519
+ LOGGER_SERVICE$9.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
4520
4520
  this.PersistSessionInstanceCtor = Ctor;
4521
4521
  this.getSessionStorage.clear();
4522
4522
  }
@@ -4638,7 +4638,7 @@ const METHOD_NAME_ADD_ACTIVITY = "LookupUtils.addActivity";
4638
4638
  const METHOD_NAME_REMOVE_ACTIVITY = "LookupUtils.removeActivity";
4639
4639
  const METHOD_NAME_LIST_ACTIVITY = "LookupUtils.listActivity";
4640
4640
  /** Logger service injected as DI singleton */
4641
- const LOGGER_SERVICE$7 = new LoggerService();
4641
+ const LOGGER_SERVICE$8 = new LoggerService();
4642
4642
  /**
4643
4643
  * Builds the composite {@link Key} used to register an activity in `_lookupMap`.
4644
4644
  *
@@ -4692,7 +4692,7 @@ class LookupUtils {
4692
4692
  * @param activity - Activity descriptor identifying the running workload.
4693
4693
  */
4694
4694
  this.addActivity = (activity) => {
4695
- LOGGER_SERVICE$7.info(METHOD_NAME_ADD_ACTIVITY, {
4695
+ LOGGER_SERVICE$8.info(METHOD_NAME_ADD_ACTIVITY, {
4696
4696
  activity,
4697
4697
  });
4698
4698
  const key = CREATE_KEY_FN$y(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
@@ -4706,7 +4706,7 @@ class LookupUtils {
4706
4706
  * @param activity - Activity descriptor matching the one passed to {@link addActivity}.
4707
4707
  */
4708
4708
  this.removeActivity = (activity) => {
4709
- LOGGER_SERVICE$7.info(METHOD_NAME_REMOVE_ACTIVITY, {
4709
+ LOGGER_SERVICE$8.info(METHOD_NAME_REMOVE_ACTIVITY, {
4710
4710
  activity,
4711
4711
  });
4712
4712
  const key = CREATE_KEY_FN$y(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
@@ -4718,7 +4718,7 @@ class LookupUtils {
4718
4718
  * @returns Array of all activities present in the lookup map at call time.
4719
4719
  */
4720
4720
  this.listActivity = () => {
4721
- LOGGER_SERVICE$7.info(METHOD_NAME_LIST_ACTIVITY);
4721
+ LOGGER_SERVICE$8.info(METHOD_NAME_LIST_ACTIVITY);
4722
4722
  return Array.from(this._lookupMap.values());
4723
4723
  };
4724
4724
  }
@@ -4747,7 +4747,7 @@ const METHOD_NAME_SPIN_LOCK = "CandleUtils.spinLock";
4747
4747
  */
4748
4748
  const ROTATE_DELAY = 50;
4749
4749
  /** Logger service injected as DI singleton */
4750
- const LOGGER_SERVICE$6 = new LoggerService();
4750
+ const LOGGER_SERVICE$7 = new LoggerService();
4751
4751
  /**
4752
4752
  * Process-wide coordinator for candle-fetch serialization and cooperative
4753
4753
  * yielding between parallel backtests.
@@ -4796,7 +4796,7 @@ class CandleUtils {
4796
4796
  * @param source - Caller identifier for logging.
4797
4797
  */
4798
4798
  this.acquireLock = async (source) => {
4799
- LOGGER_SERVICE$6.info(METHOD_NAME_ACQUIRE_LOCK, {
4799
+ LOGGER_SERVICE$7.info(METHOD_NAME_ACQUIRE_LOCK, {
4800
4800
  source,
4801
4801
  });
4802
4802
  if (!GLOBAL_CONFIG.CC_ENABLE_CANDLE_FETCH_MUTEX) {
@@ -4812,7 +4812,7 @@ class CandleUtils {
4812
4812
  * @param source - Caller identifier for logging.
4813
4813
  */
4814
4814
  this.releaseLock = async (source) => {
4815
- LOGGER_SERVICE$6.info(METHOD_NAME_RELEASE_LOCK, {
4815
+ LOGGER_SERVICE$7.info(METHOD_NAME_RELEASE_LOCK, {
4816
4816
  source,
4817
4817
  });
4818
4818
  if (!GLOBAL_CONFIG.CC_ENABLE_CANDLE_FETCH_MUTEX) {
@@ -4837,7 +4837,7 @@ class CandleUtils {
4837
4837
  * @param source - Caller identifier for logging.
4838
4838
  */
4839
4839
  this.spinLock = async (source) => {
4840
- LOGGER_SERVICE$6.info(METHOD_NAME_SPIN_LOCK, {
4840
+ LOGGER_SERVICE$7.info(METHOD_NAME_SPIN_LOCK, {
4841
4841
  source,
4842
4842
  });
4843
4843
  if (!GLOBAL_CONFIG.CC_ENABLE_CANDLE_FETCH_MUTEX) {
@@ -6347,7 +6347,7 @@ const validateCommonSignal = (signal) => {
6347
6347
  }
6348
6348
  // Кидаем ошибку если есть проблемы
6349
6349
  if (errors.length > 0) {
6350
- throw new Error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
6350
+ throw new Error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
6351
6351
  }
6352
6352
  };
6353
6353
 
@@ -6400,7 +6400,7 @@ const validatePendingSignal = (signal, currentPrice) => {
6400
6400
  }
6401
6401
  }
6402
6402
  if (errors.length > 0) {
6403
- throw new Error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
6403
+ throw new Error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
6404
6404
  }
6405
6405
  validateCommonSignal(signal);
6406
6406
  // ЗАЩИТА ОТ МОМЕНТАЛЬНОГО ЗАКРЫТИЯ: проверяем что позиция не закроется сразу после открытия
@@ -6448,7 +6448,7 @@ const validatePendingSignal = (signal, currentPrice) => {
6448
6448
  }
6449
6449
  }
6450
6450
  if (errors.length > 0) {
6451
- throw new Error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
6451
+ throw new Error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
6452
6452
  }
6453
6453
  };
6454
6454
 
@@ -6501,7 +6501,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
6501
6501
  }
6502
6502
  }
6503
6503
  if (errors.length > 0) {
6504
- throw new Error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
6504
+ throw new Error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
6505
6505
  }
6506
6506
  validateCommonSignal(signal);
6507
6507
  // ЗАЩИТА ОТ МОМЕНТАЛЬНОГО ЗАКРЫТИЯ scheduled сигналов
@@ -6547,7 +6547,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
6547
6547
  // pendingAt === 0 is allowed for scheduled signals (set to SCHEDULED_SIGNAL_PENDING_MOCK until activation)
6548
6548
  }
6549
6549
  if (errors.length > 0) {
6550
- throw new Error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
6550
+ throw new Error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
6551
6551
  }
6552
6552
  };
6553
6553
 
@@ -6994,6 +6994,13 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
6994
6994
  if (!signal) {
6995
6995
  return null;
6996
6996
  }
6997
+ if (signal?.symbol && signal?.symbol !== self.params.execution.context.symbol) {
6998
+ throw new Error(`Symbol mismatch: expected ${self.params.execution.context.symbol}, got ${signal.symbol}`);
6999
+ }
7000
+ // Whipsaw protection: skip signal if its id matches the last accepted pending id
7001
+ if (signal.id && signal.id === self._lastPendingId) {
7002
+ return null;
7003
+ }
6997
7004
  if (self._isStopped) {
6998
7005
  return null;
6999
7006
  }
@@ -7037,6 +7044,9 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
7037
7044
  }
7038
7045
  // Валидируем сигнал перед возвратом
7039
7046
  validatePendingSignal(signalRow, currentPrice);
7047
+ if (signal.id) {
7048
+ self._lastPendingId = signal.id;
7049
+ }
7040
7050
  return signalRow;
7041
7051
  }
7042
7052
  // ОЖИДАНИЕ АКТИВАЦИИ: создаем scheduled signal (risk check при активации)
@@ -7063,6 +7073,9 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
7063
7073
  };
7064
7074
  // Валидируем сигнал перед возвратом
7065
7075
  validateScheduledSignal(scheduledSignalRow, currentPrice);
7076
+ if (signal.id) {
7077
+ self._lastPendingId = signal.id;
7078
+ }
7066
7079
  return scheduledSignalRow;
7067
7080
  }
7068
7081
  const signalRow = {
@@ -7090,6 +7103,9 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
7090
7103
  }
7091
7104
  // Валидируем сигнал перед возвратом
7092
7105
  validatePendingSignal(signalRow, currentPrice);
7106
+ if (signal.id) {
7107
+ self._lastPendingId = signal.id;
7108
+ }
7093
7109
  return signalRow;
7094
7110
  }, {
7095
7111
  defaultValue: null,
@@ -7119,6 +7135,13 @@ const WAIT_FOR_INIT_FN$4 = async (self) => {
7119
7135
  if (self.params.execution.context.backtest) {
7120
7136
  return;
7121
7137
  }
7138
+ // Restore last pending signal id for whipsaw protection in GET_SIGNAL_FN
7139
+ {
7140
+ const recentSignal = await PersistRecentAdapter.readRecentData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName, self.params.method.context.frameName, false);
7141
+ if (recentSignal?.id) {
7142
+ self._lastPendingId = recentSignal.id;
7143
+ }
7144
+ }
7122
7145
  // Restore pending signal
7123
7146
  const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
7124
7147
  if (pendingSignal) {
@@ -9259,6 +9282,7 @@ class ClientStrategy {
9259
9282
  this._isStopped = false;
9260
9283
  this._pendingSignal = null;
9261
9284
  this._lastSignalTimestamp = null;
9285
+ this._lastPendingId = null;
9262
9286
  this._scheduledSignal = null;
9263
9287
  this._cancelledSignal = null;
9264
9288
  this._closedSignal = null;
@@ -12048,7 +12072,7 @@ const RISK_METHOD_NAME_CHECK_SIGNAL_AND_RESERVE = "MergeRisk.checkSignalAndReser
12048
12072
  const RISK_METHOD_NAME_ADD_SIGNAL = "MergeRisk.addSignal";
12049
12073
  const RISK_METHOD_NAME_REMOVE_SIGNAL = "MergeRisk.removeSignal";
12050
12074
  /** Logger service injected as DI singleton */
12051
- const LOGGER_SERVICE$5 = new LoggerService();
12075
+ const LOGGER_SERVICE$6 = new LoggerService();
12052
12076
  /**
12053
12077
  * Composite risk management class that combines multiple risk profiles.
12054
12078
  *
@@ -12104,7 +12128,7 @@ class MergeRisk {
12104
12128
  * @returns Promise resolving to true if all risks approve, false if any risk rejects
12105
12129
  */
12106
12130
  async checkSignal(params, options = {}) {
12107
- LOGGER_SERVICE$5.info(RISK_METHOD_NAME_CHECK_SIGNAL, {
12131
+ LOGGER_SERVICE$6.info(RISK_METHOD_NAME_CHECK_SIGNAL, {
12108
12132
  params,
12109
12133
  });
12110
12134
  for (const [riskName, risk] of Object.entries(this._riskMap)) {
@@ -12134,7 +12158,7 @@ class MergeRisk {
12134
12158
  * @returns Promise resolving to true if all risks approve (and reserved), false if any risk rejects
12135
12159
  */
12136
12160
  async checkSignalAndReserve(params) {
12137
- LOGGER_SERVICE$5.info(RISK_METHOD_NAME_CHECK_SIGNAL_AND_RESERVE, {
12161
+ LOGGER_SERVICE$6.info(RISK_METHOD_NAME_CHECK_SIGNAL_AND_RESERVE, {
12138
12162
  params,
12139
12163
  });
12140
12164
  for (const [riskName, risk] of Object.entries(this._riskMap)) {
@@ -12158,7 +12182,7 @@ class MergeRisk {
12158
12182
  * @returns Promise that resolves when all risks have registered the signal
12159
12183
  */
12160
12184
  async addSignal(symbol, context, positionData) {
12161
- LOGGER_SERVICE$5.info(RISK_METHOD_NAME_ADD_SIGNAL, {
12185
+ LOGGER_SERVICE$6.info(RISK_METHOD_NAME_ADD_SIGNAL, {
12162
12186
  symbol,
12163
12187
  context,
12164
12188
  });
@@ -12175,7 +12199,7 @@ class MergeRisk {
12175
12199
  * @returns Promise that resolves when all risks have removed the signal
12176
12200
  */
12177
12201
  async removeSignal(symbol, context) {
12178
- LOGGER_SERVICE$5.info(RISK_METHOD_NAME_REMOVE_SIGNAL, {
12202
+ LOGGER_SERVICE$6.info(RISK_METHOD_NAME_REMOVE_SIGNAL, {
12179
12203
  symbol,
12180
12204
  context,
12181
12205
  });
@@ -14968,7 +14992,7 @@ class RiskConnectionService {
14968
14992
  }
14969
14993
 
14970
14994
  /** Logger service injected as DI singleton */
14971
- const LOGGER_SERVICE$4 = new LoggerService();
14995
+ const LOGGER_SERVICE$5 = new LoggerService();
14972
14996
  /**
14973
14997
  * Wrapper to call init method with error capture.
14974
14998
  */
@@ -14983,7 +15007,7 @@ const CALL_INIT_FN = trycatch(async (self) => {
14983
15007
  error: errorData(error),
14984
15008
  message: getErrorMessage(error),
14985
15009
  };
14986
- LOGGER_SERVICE$4.warn(message, payload);
15010
+ LOGGER_SERVICE$5.warn(message, payload);
14987
15011
  console.warn(message, payload);
14988
15012
  errorEmitter.next(error);
14989
15013
  },
@@ -15003,7 +15027,7 @@ const CALL_SIGNAL_FN = trycatch(async (event, self) => {
15003
15027
  error: errorData(error),
15004
15028
  message: getErrorMessage(error),
15005
15029
  };
15006
- LOGGER_SERVICE$4.warn(message, payload);
15030
+ LOGGER_SERVICE$5.warn(message, payload);
15007
15031
  console.warn(message, payload);
15008
15032
  errorEmitter.next(error);
15009
15033
  },
@@ -15023,7 +15047,7 @@ const CALL_SIGNAL_LIVE_FN = trycatch(async (event, self) => {
15023
15047
  error: errorData(error),
15024
15048
  message: getErrorMessage(error),
15025
15049
  };
15026
- LOGGER_SERVICE$4.warn(message, payload);
15050
+ LOGGER_SERVICE$5.warn(message, payload);
15027
15051
  console.warn(message, payload);
15028
15052
  errorEmitter.next(error);
15029
15053
  },
@@ -15043,7 +15067,7 @@ const CALL_SIGNAL_BACKTEST_FN = trycatch(async (event, self) => {
15043
15067
  error: errorData(error),
15044
15068
  message: getErrorMessage(error),
15045
15069
  };
15046
- LOGGER_SERVICE$4.warn(message, payload);
15070
+ LOGGER_SERVICE$5.warn(message, payload);
15047
15071
  console.warn(message, payload);
15048
15072
  errorEmitter.next(error);
15049
15073
  },
@@ -15071,7 +15095,7 @@ const CALL_BREAKEVEN_AVAILABLE_FN = trycatch(async (event, self) => {
15071
15095
  error: errorData(error),
15072
15096
  message: getErrorMessage(error),
15073
15097
  };
15074
- LOGGER_SERVICE$4.warn(message, payload);
15098
+ LOGGER_SERVICE$5.warn(message, payload);
15075
15099
  console.warn(message, payload);
15076
15100
  errorEmitter.next(error);
15077
15101
  },
@@ -15099,7 +15123,7 @@ const CALL_PARTIAL_PROFIT_AVAILABLE_FN = trycatch(async (event, self) => {
15099
15123
  error: errorData(error),
15100
15124
  message: getErrorMessage(error),
15101
15125
  };
15102
- LOGGER_SERVICE$4.warn(message, payload);
15126
+ LOGGER_SERVICE$5.warn(message, payload);
15103
15127
  console.warn(message, payload);
15104
15128
  errorEmitter.next(error);
15105
15129
  },
@@ -15127,7 +15151,7 @@ const CALL_PARTIAL_LOSS_AVAILABLE_FN = trycatch(async (event, self) => {
15127
15151
  error: errorData(error),
15128
15152
  message: getErrorMessage(error),
15129
15153
  };
15130
- LOGGER_SERVICE$4.warn(message, payload);
15154
+ LOGGER_SERVICE$5.warn(message, payload);
15131
15155
  console.warn(message, payload);
15132
15156
  errorEmitter.next(error);
15133
15157
  },
@@ -15155,7 +15179,7 @@ const CALL_PING_SCHEDULED_FN = trycatch(async (event, self) => {
15155
15179
  error: errorData(error),
15156
15180
  message: getErrorMessage(error),
15157
15181
  };
15158
- LOGGER_SERVICE$4.warn(message, payload);
15182
+ LOGGER_SERVICE$5.warn(message, payload);
15159
15183
  console.warn(message, payload);
15160
15184
  errorEmitter.next(error);
15161
15185
  },
@@ -15183,7 +15207,7 @@ const CALL_PING_IDLE_FN = trycatch(async (event, self) => {
15183
15207
  error: errorData(error),
15184
15208
  message: getErrorMessage(error),
15185
15209
  };
15186
- LOGGER_SERVICE$4.warn(message, payload);
15210
+ LOGGER_SERVICE$5.warn(message, payload);
15187
15211
  console.warn(message, payload);
15188
15212
  errorEmitter.next(error);
15189
15213
  },
@@ -15211,7 +15235,7 @@ const CALL_PING_ACTIVE_FN = trycatch(async (event, self) => {
15211
15235
  error: errorData(error),
15212
15236
  message: getErrorMessage(error),
15213
15237
  };
15214
- LOGGER_SERVICE$4.warn(message, payload);
15238
+ LOGGER_SERVICE$5.warn(message, payload);
15215
15239
  console.warn(message, payload);
15216
15240
  errorEmitter.next(error);
15217
15241
  },
@@ -15231,7 +15255,7 @@ const CALL_RISK_REJECTION_FN = trycatch(async (event, self) => {
15231
15255
  error: errorData(error),
15232
15256
  message: getErrorMessage(error),
15233
15257
  };
15234
- LOGGER_SERVICE$4.warn(message, payload);
15258
+ LOGGER_SERVICE$5.warn(message, payload);
15235
15259
  console.warn(message, payload);
15236
15260
  errorEmitter.next(error);
15237
15261
  },
@@ -15251,7 +15275,7 @@ const CALL_DISPOSE_FN = trycatch(async (self) => {
15251
15275
  error: errorData(error),
15252
15276
  message: getErrorMessage(error),
15253
15277
  };
15254
- LOGGER_SERVICE$4.warn(message, payload);
15278
+ LOGGER_SERVICE$5.warn(message, payload);
15255
15279
  console.warn(message, payload);
15256
15280
  errorEmitter.next(error);
15257
15281
  },
@@ -23115,7 +23139,7 @@ const REPORT_UTILS_METHOD_NAME_USE_DUMMY$1 = "ReportUtils.useDummy";
23115
23139
  const REPORT_UTILS_METHOD_NAME_USE_JSONL$1 = "ReportUtils.useJsonl";
23116
23140
  const REPORT_UTILS_METHOD_NAME_CLEAR$1 = "ReportUtils.clear";
23117
23141
  /** Logger service injected as DI singleton */
23118
- const LOGGER_SERVICE$3 = new LoggerService();
23142
+ const LOGGER_SERVICE$4 = new LoggerService();
23119
23143
  /** Symbol key for the singleshot waitForInit function on MarkdownFileBase instances. */
23120
23144
  const WAIT_FOR_INIT_SYMBOL$1 = Symbol("wait-for-init");
23121
23145
  /** Symbol key for the timeout-protected write function on MarkdownFileBase instances. */
@@ -23196,7 +23220,7 @@ class MarkdownFileBase {
23196
23220
  * @throws Error if stream not initialized or write timeout exceeded
23197
23221
  */
23198
23222
  async dump(data, options) {
23199
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_FILE_DUMP, {
23223
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_FILE_DUMP, {
23200
23224
  markdownName: this.markdownName,
23201
23225
  options,
23202
23226
  });
@@ -23276,7 +23300,7 @@ class MarkdownFolderBase {
23276
23300
  * @throws Error if directory creation or file write fails
23277
23301
  */
23278
23302
  async dump(content, options) {
23279
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_FOLDER_DUMP, {
23303
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_FOLDER_DUMP, {
23280
23304
  markdownName: this.markdownName,
23281
23305
  options,
23282
23306
  });
@@ -23341,7 +23365,7 @@ class MarkdownWriterAdapter {
23341
23365
  * @param Ctor - Constructor for markdown storage adapter
23342
23366
  */
23343
23367
  useMarkdownAdapter(Ctor) {
23344
- LOGGER_SERVICE$3.info(MARKDOWN_METHOD_NAME_USE_ADAPTER$1);
23368
+ LOGGER_SERVICE$4.info(MARKDOWN_METHOD_NAME_USE_ADAPTER$1);
23345
23369
  this.MarkdownFactory = Ctor;
23346
23370
  }
23347
23371
  /**
@@ -23355,7 +23379,7 @@ class MarkdownWriterAdapter {
23355
23379
  * @throws Error if write fails or storage initialization fails
23356
23380
  */
23357
23381
  async writeData(markdownName, content, options) {
23358
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_WRITE_DATA, {
23382
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_WRITE_DATA, {
23359
23383
  markdownName,
23360
23384
  options,
23361
23385
  });
@@ -23369,7 +23393,7 @@ class MarkdownWriterAdapter {
23369
23393
  * Each report is written as a separate .md file.
23370
23394
  */
23371
23395
  useMd() {
23372
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_USE_MD$1);
23396
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_USE_MD$1);
23373
23397
  this.useMarkdownAdapter(MarkdownFolderBase);
23374
23398
  }
23375
23399
  /**
@@ -23377,7 +23401,7 @@ class MarkdownWriterAdapter {
23377
23401
  * All reports are appended to a single .jsonl file per markdown type.
23378
23402
  */
23379
23403
  useJsonl() {
23380
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_USE_JSONL$1);
23404
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_USE_JSONL$1);
23381
23405
  this.useMarkdownAdapter(MarkdownFileBase);
23382
23406
  }
23383
23407
  /**
@@ -23386,7 +23410,7 @@ class MarkdownWriterAdapter {
23386
23410
  * so new storage instances are created with the updated base path.
23387
23411
  */
23388
23412
  clear() {
23389
- LOGGER_SERVICE$3.log(MARKDOWN_METHOD_NAME_CLEAR$1);
23413
+ LOGGER_SERVICE$4.log(MARKDOWN_METHOD_NAME_CLEAR$1);
23390
23414
  this.getMarkdownStorage.clear();
23391
23415
  }
23392
23416
  /**
@@ -23394,7 +23418,7 @@ class MarkdownWriterAdapter {
23394
23418
  * All future markdown writes will be no-ops.
23395
23419
  */
23396
23420
  useDummy() {
23397
- LOGGER_SERVICE$3.debug(MARKDOWN_METHOD_NAME_USE_DUMMY$1);
23421
+ LOGGER_SERVICE$4.debug(MARKDOWN_METHOD_NAME_USE_DUMMY$1);
23398
23422
  this.useMarkdownAdapter(MarkdownDummy);
23399
23423
  }
23400
23424
  }
@@ -23455,7 +23479,7 @@ class ReportBase {
23455
23479
  });
23456
23480
  }
23457
23481
  }, 15000));
23458
- LOGGER_SERVICE$3.debug(REPORT_BASE_METHOD_NAME_CTOR, {
23482
+ LOGGER_SERVICE$4.debug(REPORT_BASE_METHOD_NAME_CTOR, {
23459
23483
  reportName: this.reportName,
23460
23484
  baseDir,
23461
23485
  });
@@ -23469,7 +23493,7 @@ class ReportBase {
23469
23493
  * @returns Promise that resolves when initialization is complete
23470
23494
  */
23471
23495
  async waitForInit(initial) {
23472
- LOGGER_SERVICE$3.debug(REPORT_BASE_METHOD_NAME_WAIT_FOR_INIT, {
23496
+ LOGGER_SERVICE$4.debug(REPORT_BASE_METHOD_NAME_WAIT_FOR_INIT, {
23473
23497
  reportName: this.reportName,
23474
23498
  initial,
23475
23499
  });
@@ -23488,7 +23512,7 @@ class ReportBase {
23488
23512
  * @throws Error if stream not initialized or write timeout exceeded
23489
23513
  */
23490
23514
  async write(data, options) {
23491
- LOGGER_SERVICE$3.debug(REPORT_BASE_METHOD_NAME_WRITE, {
23515
+ LOGGER_SERVICE$4.debug(REPORT_BASE_METHOD_NAME_WRITE, {
23492
23516
  reportName: this.reportName,
23493
23517
  options,
23494
23518
  });
@@ -23585,7 +23609,7 @@ class ReportWriterAdapter {
23585
23609
  * @internal - Automatically called by report services, not for direct use
23586
23610
  */
23587
23611
  this.writeData = async (reportName, data, options) => {
23588
- LOGGER_SERVICE$3.info(REPORT_UTILS_METHOD_NAME_WRITE_DATA, {
23612
+ LOGGER_SERVICE$4.info(REPORT_UTILS_METHOD_NAME_WRITE_DATA, {
23589
23613
  reportName,
23590
23614
  options,
23591
23615
  });
@@ -23602,7 +23626,7 @@ class ReportWriterAdapter {
23602
23626
  * @param Ctor - Constructor for report storage adapter
23603
23627
  */
23604
23628
  useReportAdapter(Ctor) {
23605
- LOGGER_SERVICE$3.info(REPORT_UTILS_METHOD_NAME_USE_REPORT_ADAPTER$1);
23629
+ LOGGER_SERVICE$4.info(REPORT_UTILS_METHOD_NAME_USE_REPORT_ADAPTER$1);
23606
23630
  this.ReportFactory = Ctor;
23607
23631
  }
23608
23632
  /**
@@ -23611,7 +23635,7 @@ class ReportWriterAdapter {
23611
23635
  * so new storage instances are created with the updated base path.
23612
23636
  */
23613
23637
  clear() {
23614
- LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_CLEAR$1);
23638
+ LOGGER_SERVICE$4.log(REPORT_UTILS_METHOD_NAME_CLEAR$1);
23615
23639
  this.getReportStorage.clear();
23616
23640
  }
23617
23641
  /**
@@ -23619,7 +23643,7 @@ class ReportWriterAdapter {
23619
23643
  * All future report writes will be no-ops.
23620
23644
  */
23621
23645
  useDummy() {
23622
- LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_USE_DUMMY$1);
23646
+ LOGGER_SERVICE$4.log(REPORT_UTILS_METHOD_NAME_USE_DUMMY$1);
23623
23647
  this.useReportAdapter(ReportDummy);
23624
23648
  }
23625
23649
  /**
@@ -23627,7 +23651,7 @@ class ReportWriterAdapter {
23627
23651
  * All future report writes will use JSONL storage.
23628
23652
  */
23629
23653
  useJsonl() {
23630
- LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_USE_JSONL$1);
23654
+ LOGGER_SERVICE$4.log(REPORT_UTILS_METHOD_NAME_USE_JSONL$1);
23631
23655
  this.useReportAdapter(ReportBase);
23632
23656
  }
23633
23657
  }
@@ -53928,7 +53952,7 @@ const REPORT_UTILS_METHOD_NAME_USE_DUMMY = "ReportUtils.useDummy";
53928
53952
  const REPORT_UTILS_METHOD_NAME_USE_JSONL = "ReportUtils.useJsonl";
53929
53953
  const REPORT_UTILS_METHOD_NAME_CLEAR = "ReportUtils.clear";
53930
53954
  /** Logger service injected as DI singleton */
53931
- const LOGGER_SERVICE$2 = new LoggerService();
53955
+ const LOGGER_SERVICE$3 = new LoggerService();
53932
53956
  /**
53933
53957
  * Default configuration that enables all report services.
53934
53958
  * Used when no specific configuration is provided to enable().
@@ -53985,7 +54009,7 @@ class ReportUtils {
53985
54009
  * @returns Cleanup function that unsubscribes from all enabled services
53986
54010
  */
53987
54011
  this.enable = singleshot(({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
53988
- LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
54012
+ LOGGER_SERVICE$3.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
53989
54013
  backtest: bt,
53990
54014
  breakeven,
53991
54015
  heat,
@@ -54080,7 +54104,7 @@ class ReportUtils {
54080
54104
  * ```
54081
54105
  */
54082
54106
  this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
54083
- LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
54107
+ LOGGER_SERVICE$3.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
54084
54108
  backtest: bt,
54085
54109
  breakeven,
54086
54110
  heat,
@@ -54160,7 +54184,7 @@ class ReportAdapter extends ReportUtils {
54160
54184
  * @param Ctor - Constructor for report storage adapter
54161
54185
  */
54162
54186
  useReportAdapter(Ctor) {
54163
- LOGGER_SERVICE$2.info(REPORT_UTILS_METHOD_NAME_USE_REPORT_ADAPTER);
54187
+ LOGGER_SERVICE$3.info(REPORT_UTILS_METHOD_NAME_USE_REPORT_ADAPTER);
54164
54188
  ReportWriter.useReportAdapter(Ctor);
54165
54189
  }
54166
54190
  /**
@@ -54169,7 +54193,7 @@ class ReportAdapter extends ReportUtils {
54169
54193
  * so new storage instances are created with the updated base path.
54170
54194
  */
54171
54195
  clear() {
54172
- LOGGER_SERVICE$2.log(REPORT_UTILS_METHOD_NAME_CLEAR);
54196
+ LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_CLEAR);
54173
54197
  ReportWriter.clear();
54174
54198
  }
54175
54199
  /**
@@ -54177,7 +54201,7 @@ class ReportAdapter extends ReportUtils {
54177
54201
  * All future report writes will be no-ops.
54178
54202
  */
54179
54203
  useDummy() {
54180
- LOGGER_SERVICE$2.log(REPORT_UTILS_METHOD_NAME_USE_DUMMY);
54204
+ LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_USE_DUMMY);
54181
54205
  ReportWriter.useDummy();
54182
54206
  }
54183
54207
  /**
@@ -54185,7 +54209,7 @@ class ReportAdapter extends ReportUtils {
54185
54209
  * All future report writes will use JSONL storage.
54186
54210
  */
54187
54211
  useJsonl() {
54188
- LOGGER_SERVICE$2.log(REPORT_UTILS_METHOD_NAME_USE_JSONL);
54212
+ LOGGER_SERVICE$3.log(REPORT_UTILS_METHOD_NAME_USE_JSONL);
54189
54213
  ReportWriter.useJsonl();
54190
54214
  }
54191
54215
  }
@@ -54203,7 +54227,7 @@ const MARKDOWN_METHOD_NAME_USE_JSONL = "MarkdownAdapter.useJsonl";
54203
54227
  const MARKDOWN_METHOD_NAME_USE_DUMMY = "MarkdownAdapter.useDummy";
54204
54228
  const MARKDOWN_METHOD_NAME_CLEAR = "MarkdownAdapter.clear";
54205
54229
  /** Logger service injected as DI singleton */
54206
- const LOGGER_SERVICE$1 = new LoggerService();
54230
+ const LOGGER_SERVICE$2 = new LoggerService();
54207
54231
  /**
54208
54232
  * Default configuration that enables all markdown services.
54209
54233
  * Used when no specific configuration is provided to `enable()`.
@@ -54260,7 +54284,7 @@ class MarkdownUtils {
54260
54284
  * @returns Cleanup function that unsubscribes from all enabled services
54261
54285
  */
54262
54286
  this.enable = singleshot(({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
54263
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_ENABLE, {
54287
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_ENABLE, {
54264
54288
  backtest: bt,
54265
54289
  breakeven,
54266
54290
  heat,
@@ -54357,7 +54381,7 @@ class MarkdownUtils {
54357
54381
  * ```
54358
54382
  */
54359
54383
  this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
54360
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_DISABLE, {
54384
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_DISABLE, {
54361
54385
  backtest: bt,
54362
54386
  breakeven,
54363
54387
  heat,
@@ -54443,7 +54467,7 @@ class MarkdownUtils {
54443
54467
  * @param config.max_drawdown - Clear max drawdown report data
54444
54468
  */
54445
54469
  this.clear = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
54446
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
54470
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_CLEAR, {
54447
54471
  backtest: bt,
54448
54472
  breakeven,
54449
54473
  heat,
@@ -54518,7 +54542,7 @@ class MarkdownAdapter extends MarkdownUtils {
54518
54542
  * @param Ctor - Constructor for markdown storage adapter
54519
54543
  */
54520
54544
  useMarkdownAdapter(Ctor) {
54521
- LOGGER_SERVICE$1.info(MARKDOWN_METHOD_NAME_USE_ADAPTER);
54545
+ LOGGER_SERVICE$2.info(MARKDOWN_METHOD_NAME_USE_ADAPTER);
54522
54546
  return MarkdownWriter.useMarkdownAdapter(Ctor);
54523
54547
  }
54524
54548
  /**
@@ -54527,7 +54551,7 @@ class MarkdownAdapter extends MarkdownUtils {
54527
54551
  * Each dump creates a separate .md file.
54528
54552
  */
54529
54553
  useMd() {
54530
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_MD);
54554
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_USE_MD);
54531
54555
  MarkdownWriter.useMd();
54532
54556
  }
54533
54557
  /**
@@ -54536,7 +54560,7 @@ class MarkdownAdapter extends MarkdownUtils {
54536
54560
  * All dumps append to a single .jsonl file per markdown type.
54537
54561
  */
54538
54562
  useJsonl() {
54539
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
54563
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
54540
54564
  MarkdownWriter.useJsonl();
54541
54565
  }
54542
54566
  /**
@@ -54544,7 +54568,7 @@ class MarkdownAdapter extends MarkdownUtils {
54544
54568
  * All future markdown writes will be no-ops.
54545
54569
  */
54546
54570
  useDummy() {
54547
- LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_DUMMY);
54571
+ LOGGER_SERVICE$2.debug(MARKDOWN_METHOD_NAME_USE_DUMMY);
54548
54572
  MarkdownWriter.useDummy();
54549
54573
  }
54550
54574
  }
@@ -63233,6 +63257,470 @@ class IntervalUtils {
63233
63257
  */
63234
63258
  const Interval = new IntervalUtils();
63235
63259
 
63260
+ const CRON_METHOD_NAME_REGISTER = "CronUtils.register";
63261
+ const CRON_METHOD_NAME_UNREGISTER = "CronUtils.unregister";
63262
+ const CRON_METHOD_NAME_CLEAR = "CronUtils.clear";
63263
+ const CRON_METHOD_NAME_TICK = "CronUtils._tick";
63264
+ const CRON_METHOD_NAME_ENABLE = "CronUtils.enable";
63265
+ const CRON_METHOD_NAME_DISABLE = "CronUtils.disable";
63266
+ /**
63267
+ * Local logger instance.
63268
+ *
63269
+ * Created directly rather than resolved from the DI container so that
63270
+ * `CronUtils` has no compile-time dependency on the rest of the framework
63271
+ * being bootstrapped — `Cron` can be imported and used in isolation.
63272
+ */
63273
+ const LOGGER_SERVICE$1 = new LoggerService();
63274
+ /**
63275
+ * Utility class for registering periodic tasks that fire on candle-interval
63276
+ * boundaries of the virtual time produced by parallel backtests.
63277
+ *
63278
+ * Exported as singleton instance `Cron` for convenient usage.
63279
+ *
63280
+ * Key property — **singleshot coordination across parallel backtests**:
63281
+ * when several `Backtest.background(symbol, ...)` runs hit the same aligned
63282
+ * boundary concurrently, the handler is invoked exactly once. Every parallel
63283
+ * `tick` for that boundary awaits the same in-flight promise and is released
63284
+ * together when the promise settles. After settlement the slot is cleared and
63285
+ * the next boundary produces a fresh promise.
63286
+ *
63287
+ * Typical wiring:
63288
+ *
63289
+ * @example
63290
+ * ```typescript
63291
+ * import { Cron, Backtest } from "backtest-kit";
63292
+ *
63293
+ * Cron.register({
63294
+ * name: "tg-signal-parser",
63295
+ * interval: "1h",
63296
+ * handler: async (symbol, when, backtest) => {
63297
+ * await parseTelegramSignalsToMongo(when);
63298
+ * },
63299
+ * });
63300
+ *
63301
+ * // Subscribe Cron to the engine's lifecycle subjects (beforeStart,
63302
+ * // idlePing, activePing, schedulePing) once at startup. After this every
63303
+ * // strategy tick is forwarded into Cron automatically.
63304
+ * Cron.enable();
63305
+ *
63306
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "TRXUSDT"]) {
63307
+ * Backtest.background(symbol, { strategyName, exchangeName, frameName });
63308
+ * }
63309
+ *
63310
+ * // On shutdown:
63311
+ * // Cron.disable();
63312
+ * ```
63313
+ */
63314
+ class CronUtils {
63315
+ constructor() {
63316
+ /**
63317
+ * Registered entries by `name`.
63318
+ *
63319
+ * Each record carries a monotonically increasing `generation` counter that
63320
+ * is bumped on every `register(entry)` call for the same name. The
63321
+ * generation participates in `firedKey` so writes from a still-in-flight
63322
+ * handler of a previous incarnation cannot poison `_firedOnce` for the
63323
+ * current incarnation — their key has a different generation suffix and
63324
+ * is simply ignored on lookup.
63325
+ */
63326
+ this._entries = new Map();
63327
+ /** Monotonic counter used to mint new entry generations on `register`. */
63328
+ this._generationCounter = 0;
63329
+ /**
63330
+ * In-flight handler slots.
63331
+ *
63332
+ * Slot key shape (always includes the generation suffix `:g${generation}`;
63333
+ * the `:${symbol}` scope is present only in fan-out mode):
63334
+ * - Periodic global: `${name}:${alignedMs}:g${generation}`.
63335
+ * - Periodic fan-out: `${name}:${alignedMs}:${symbol}:g${generation}`.
63336
+ * - Fire-once global: `${name}:once:g${generation}`.
63337
+ * - Fire-once fan-out: `${name}:once:${symbol}:g${generation}`.
63338
+ *
63339
+ * Value is the shared in-flight handler promise. Every parallel `tick` for
63340
+ * the same slot key awaits this exact promise (mutex semantics) and is
63341
+ * released together when it settles. `_inFlight` is owned exclusively by
63342
+ * `_runEntry` — `clear()` does **not** touch it, so the singleshot promise
63343
+ * survives concurrent `clear` calls and continues to coordinate parallel
63344
+ * ticks until it settles.
63345
+ */
63346
+ this._inFlight = new Map();
63347
+ /**
63348
+ * Keys of fire-once entries whose handler has already settled successfully.
63349
+ *
63350
+ * Key shape (always includes the entry generation suffix `:g${generation}`):
63351
+ * - Global fire-once: `${name}:g${generation}`.
63352
+ * - Fan-out fire-once: `${name}:${symbol}:g${generation}` — one entry per
63353
+ * whitelisted symbol.
63354
+ *
63355
+ * The generation suffix isolates incarnations of the same `name`: writes
63356
+ * landing from a still-in-flight handler of a previous `register()` carry
63357
+ * the old generation and are never matched by the new entry's lookup.
63358
+ * Stale entries are pruned by `_clearFiredOnceFor` on `register`/`unregister`
63359
+ * and wiped by `clear()`.
63360
+ *
63361
+ * Looked up by `_tick` to decide whether to skip; written by `_runEntry`
63362
+ * on successful settle.
63363
+ */
63364
+ this._firedOnce = new Set();
63365
+ /**
63366
+ * Register a periodic cron entry.
63367
+ *
63368
+ * Idempotent on `name`: re-registering the same name replaces the previous
63369
+ * entry (interval/symbols/handler can all change). Re-registration does
63370
+ * **not** clear in-flight promises — entries still resolving complete with
63371
+ * the previous handler.
63372
+ *
63373
+ * @param entry - Entry configuration; see {@link CronEntry}.
63374
+ * @returns Disposer function — call it to unregister the entry.
63375
+ *
63376
+ * @example
63377
+ * ```typescript
63378
+ * const dispose = Cron.register({
63379
+ * name: "fetch-funding",
63380
+ * interval: "8h",
63381
+ * symbols: ["BTCUSDT", "ETHUSDT"],
63382
+ * handler: async (symbol, when, backtest) => { ... },
63383
+ * });
63384
+ * // Later:
63385
+ * dispose();
63386
+ * ```
63387
+ */
63388
+ this.register = (entry) => {
63389
+ LOGGER_SERVICE$1.info(CRON_METHOD_NAME_REGISTER, {
63390
+ name: entry.name,
63391
+ interval: entry.interval,
63392
+ symbols: entry.symbols,
63393
+ });
63394
+ if (!entry.name) {
63395
+ throw new Error("CronUtils.register requires a non-empty name");
63396
+ }
63397
+ if (entry.name.includes(":")) {
63398
+ throw new Error(`CronUtils.register: name must not contain ':' (got "${entry.name}"). ` +
63399
+ `':' is reserved as the segment separator in slot keys.`);
63400
+ }
63401
+ if (entry.symbols) {
63402
+ for (const symbol of entry.symbols) {
63403
+ if (symbol.includes(":")) {
63404
+ throw new Error(`CronUtils.register: symbols[] entry must not contain ':' (got "${symbol}"). ` +
63405
+ `':' is reserved as the segment separator in slot keys.`);
63406
+ }
63407
+ }
63408
+ }
63409
+ this._clearFiredOnceFor(entry.name);
63410
+ const generation = ++this._generationCounter;
63411
+ this._entries.set(entry.name, { entry, generation });
63412
+ return () => this.unregister(entry.name);
63413
+ };
63414
+ /**
63415
+ * Remove a registered entry by name.
63416
+ *
63417
+ * Does not cancel handlers already in flight — those resolve on their own
63418
+ * and clear their slot via `.finally()`.
63419
+ *
63420
+ * @param name - Name passed to `register`.
63421
+ */
63422
+ this.unregister = (name) => {
63423
+ LOGGER_SERVICE$1.info(CRON_METHOD_NAME_UNREGISTER, { name });
63424
+ this._entries.delete(name);
63425
+ this._clearFiredOnceFor(name);
63426
+ };
63427
+ /**
63428
+ * Clear fire-once marks so that fire-once entries can fire again.
63429
+ *
63430
+ * Does **not** touch `_inFlight` — that map holds shared in-flight handler
63431
+ * promises through which parallel `tick`s coordinate. Wiping it mid-flight
63432
+ * would let a new `tick` start a second handler for a boundary that's
63433
+ * already running, breaking the singleshot contract.
63434
+ *
63435
+ * Two modes:
63436
+ * - **Per-symbol** (`symbol` provided): clears only fan-out fire-once
63437
+ * marks for that symbol — keys of the shape `${name}:${symbol}:g${gen}`.
63438
+ * Global fire-once marks (`${name}:g${gen}`, no symbol component) are
63439
+ * left intact, since they are not attributable to a single symbol.
63440
+ * Useful for re-arming fan-out fire-once entries when a particular
63441
+ * symbol's run finishes and you want a future re-run to fire again.
63442
+ * - **All** (no argument): wipes every fire-once mark across all entries
63443
+ * and symbols. Registered entries are not removed — use `unregister`
63444
+ * (or the disposer returned by `register`) for that.
63445
+ *
63446
+ * **Race with in-flight handlers.** `_firedOnce` is written in
63447
+ * `_runEntry`'s `.finally()`, which can run *after* a concurrent
63448
+ * `clear()` call. In that case the fire-once mark reappears immediately
63449
+ * after being wiped, and the next tick will treat the entry as already
63450
+ * fired. This is consistent with the singleshot promise itself surviving
63451
+ * `clear()` — the handler is allowed to finish — and the entry's
63452
+ * generation suffix in `firedKey` guarantees the stale mark cannot
63453
+ * outlive a subsequent `register()` of the same name. If you need a hard
63454
+ * re-arm, `unregister` + `register` bumps the generation and makes any
63455
+ * late write a no-op.
63456
+ *
63457
+ * @param symbol - Optional symbol filter; if omitted, clears all fire-once
63458
+ * marks.
63459
+ */
63460
+ this.clear = (symbol) => {
63461
+ LOGGER_SERVICE$1.info(CRON_METHOD_NAME_CLEAR, { symbol });
63462
+ if (!symbol) {
63463
+ this._firedOnce.clear();
63464
+ return;
63465
+ }
63466
+ const symbolSegment = `:${symbol}:`;
63467
+ for (const key of this._firedOnce) {
63468
+ if (key.includes(symbolSegment)) {
63469
+ this._firedOnce.delete(key);
63470
+ }
63471
+ }
63472
+ };
63473
+ /**
63474
+ * Process a virtual-time tick for `symbol` and fire any due cron entries.
63475
+ *
63476
+ * **Private.** Invoked exclusively by the lifecycle bridge installed in
63477
+ * {@link enable} — `beforeStart` / `idlePing` / `activePing` / `schedulePing`
63478
+ * are funneled here through a shared `singlerun` queue, so calls to
63479
+ * `_tick` are serialised end-to-end. Do not call directly.
63480
+ *
63481
+ * Algorithm (per registered entry):
63482
+ * 0. Base-align the incoming `when` down to the 1-minute boundary (`ts`).
63483
+ * Lifecycle subjects may emit with sub-second jitter; rounding here
63484
+ * guarantees that `beforeStart` / `idlePing` / `activePing` /
63485
+ * `schedulePing` for the same virtual minute all hash to the same
63486
+ * slot key.
63487
+ * 1. If `entry.symbols` is non-empty and does not include `symbol`, skip.
63488
+ * 2. Decide scope from `entry.symbols`:
63489
+ * - Empty/undefined → **global** (slot key has no symbol component).
63490
+ * - Non-empty → **fan-out**, slot key carries `:${symbol}` so each
63491
+ * whitelisted symbol gets its own slot and handler invocation.
63492
+ * 3. Append the current entry generation suffix `:g${generation}` to both
63493
+ * slot key and fired-once key. This isolates incarnations of the same
63494
+ * `name`: a `register()` after an in-flight handler bumps the
63495
+ * generation, so the late `_firedOnce` write from the old handler can
63496
+ * never block the new entry.
63497
+ * 4. **Fire-once** (`entry.interval === undefined`):
63498
+ * - If the entry's fired-once key is already in `_firedOnce`, skip.
63499
+ * - Slot key: `${name}:once` (+ scope) (+ gen).
63500
+ * - `aligned` = the 1-minute-aligned `when` from step 0.
63501
+ * 5. **Periodic** (`entry.interval` set):
63502
+ * - Align `when` further to the entry's interval via {@link alignToInterval}.
63503
+ * - If `ts !== alignedMs`, the tick is mid-interval — skip.
63504
+ * (This is the "remainder === 0" boundary check from the spec;
63505
+ * since `ts` is already on the 1-minute boundary, the check is exact
63506
+ * for `1m` and consistent for higher intervals.)
63507
+ * - Slot key: `${name}:${alignedMs}` (+ scope) (+ gen).
63508
+ * 6. Singleshot per slot key: look up the slot in `_inFlight`. If a promise
63509
+ * already exists, `await` the same promise. Otherwise invoke
63510
+ * `entry.handler`, store the promise, and `await` it. The slot is
63511
+ * removed in `.finally()` so the next boundary creates a fresh promise;
63512
+ * for fire-once entries the fired-once key is also added to
63513
+ * `_firedOnce` on success so subsequent ticks skip it.
63514
+ *
63515
+ * Errors thrown by `handler` are caught, logged via `console.error`, and
63516
+ * **not** rethrown — a failing handler must not break the per-symbol
63517
+ * tick loop or unblock other parallel backtests with an unhandled
63518
+ * rejection. A failed fire-once handler is **not** marked as fired and
63519
+ * will retry on the next tick.
63520
+ *
63521
+ * Requires active method context and execution context.
63522
+ *
63523
+ * @param symbol - Trading symbol from the current tick.
63524
+ * @param when - Virtual time of the current tick.
63525
+ * @param backtest - `true` for backtest ticks, `false` for live ticks.
63526
+ * Forwarded as the third argument to `entry.handler`. Only the value
63527
+ * from the tick that **opens** a given slot is observed by all parallel
63528
+ * awaiters of that slot.
63529
+ * @throws Error if method or execution context is missing.
63530
+ */
63531
+ this._tick = async (symbol, when, backtest) => {
63532
+ LOGGER_SERVICE$1.debug(CRON_METHOD_NAME_TICK, {
63533
+ symbol,
63534
+ when,
63535
+ });
63536
+ if (!MethodContextService.hasContext()) {
63537
+ throw new Error("CronUtils _tick requires method context");
63538
+ }
63539
+ if (!ExecutionContextService.hasContext()) {
63540
+ throw new Error("CronUtils _tick requires execution context");
63541
+ }
63542
+ const ts = alignToInterval(when, "1m").getTime();
63543
+ const taskList = [];
63544
+ for (const { entry, generation } of this._entries.values()) {
63545
+ if (entry.symbols?.length && !entry.symbols.includes(symbol)) {
63546
+ continue;
63547
+ }
63548
+ const perSymbol = !!entry.symbols?.length;
63549
+ const scope = perSymbol ? `:${symbol}` : "";
63550
+ const genSuffix = `:g${generation}`;
63551
+ let aligned;
63552
+ let alignedMs;
63553
+ let slotKey;
63554
+ let firedKey;
63555
+ if (entry.interval === undefined) {
63556
+ const onceKey = `${entry.name}${scope}${genSuffix}`;
63557
+ if (this._firedOnce.has(onceKey)) {
63558
+ continue;
63559
+ }
63560
+ aligned = alignToInterval(when, "1m");
63561
+ alignedMs = ts;
63562
+ slotKey = `${entry.name}:once${scope}${genSuffix}`;
63563
+ firedKey = onceKey;
63564
+ }
63565
+ else {
63566
+ aligned = alignToInterval(when, entry.interval);
63567
+ alignedMs = aligned.getTime();
63568
+ if (ts !== alignedMs) {
63569
+ continue;
63570
+ }
63571
+ slotKey = `${entry.name}:${alignedMs}${scope}${genSuffix}`;
63572
+ firedKey = null;
63573
+ }
63574
+ let pending = this._inFlight.get(slotKey);
63575
+ if (!pending) {
63576
+ pending = this._runEntry(entry, symbol, aligned, alignedMs, slotKey, firedKey, backtest);
63577
+ this._inFlight.set(slotKey, pending);
63578
+ }
63579
+ taskList.push(pending);
63580
+ }
63581
+ await Promise.all(taskList);
63582
+ };
63583
+ /**
63584
+ * Subscribe `Cron` to the engine's strategy lifecycle subjects so registered
63585
+ * entries fire automatically — no manual wiring of `listenTickBacktest` /
63586
+ * `listenSchedulePing` etc. needed.
63587
+ *
63588
+ * Subjects funneled into {@link _tick}:
63589
+ * - `beforeStartSubject` — first event of every run.
63590
+ * - `idlePingSubject` — every tick when no signal is pending or scheduled.
63591
+ * - `activePingSubject` — every tick while a pending signal is being monitored.
63592
+ * - `schedulePingSubject` — every tick while a scheduled signal is being monitored.
63593
+ *
63594
+ * All four subjects are subscribed to a single `singlerun`-wrapped
63595
+ * handler that builds `_tick(event.symbol, new Date(event.timestamp),
63596
+ * event.backtest)`. `singlerun` merges the four streams into one serial
63597
+ * queue: at most one `_tick` runs at a time, the next waits. This matters
63598
+ * because the engine can emit `beforeStart` and an immediate `idlePing`
63599
+ * on the very same minute, and concurrent `_tick`s on the same
63600
+ * `(symbol, minute)` would otherwise race to open the same `_inFlight`
63601
+ * slot before either commit. Together these four sources cover every
63602
+ * tick the engine processes for every `(symbol, virtual-minute)` pair
63603
+ * regardless of whether the strategy is idle, active, or scheduled.
63604
+ *
63605
+ * `enable` itself is wrapped in `singleshot`, so calling it repeatedly is
63606
+ * a no-op — subsequent calls return the same disposer. The disposer
63607
+ * unsubscribes from every subject and resets the singleshot so a future
63608
+ * `enable()` can re-subscribe cleanly. Equivalent to the
63609
+ * `RecentAdapter.enable` pattern.
63610
+ *
63611
+ * The `.subscribe` callbacks are synchronous wrappers around the
63612
+ * `singlerun`-async handler; `_tick`'s returned promise is awaited inside
63613
+ * `singlerun` to enforce ordering but not bubbled back to the subject.
63614
+ * Errors are caught and logged inside `_runEntry`.
63615
+ *
63616
+ * @returns Cleanup function that unsubscribes from all four subjects and
63617
+ * resets the singleshot. Idempotent.
63618
+ *
63619
+ * @example
63620
+ * ```typescript
63621
+ * import { Cron } from "backtest-kit";
63622
+ *
63623
+ * Cron.register({ name: "tg-parser", interval: "1h", handler });
63624
+ * Cron.enable(); // wire once at startup
63625
+ * // ... run backtests / live as usual
63626
+ * Cron.disable(); // on shutdown
63627
+ * ```
63628
+ */
63629
+ this.enable = singleshot(() => {
63630
+ LOGGER_SERVICE$1.info(CRON_METHOD_NAME_ENABLE);
63631
+ const handleTick = singlerun(async (event) => {
63632
+ return await this._tick(event.symbol, new Date(event.timestamp), event.backtest);
63633
+ });
63634
+ const unBeforeStart = beforeStartSubject.subscribe(handleTick);
63635
+ const unIdlePing = idlePingSubject.subscribe(handleTick);
63636
+ const unActivePing = activePingSubject.subscribe(handleTick);
63637
+ const unSchedulePing = schedulePingSubject.subscribe(handleTick);
63638
+ return compose(() => unBeforeStart(), () => unIdlePing(), () => unActivePing(), () => unSchedulePing(), () => this.enable.clear());
63639
+ });
63640
+ /**
63641
+ * Tear down the lifecycle subscriptions installed by {@link enable}.
63642
+ *
63643
+ * Safe to call multiple times and safe to call before `enable()` — both
63644
+ * are no-ops. Does **not** unregister entries, does **not** touch
63645
+ * `_inFlight`, and does **not** wipe `_firedOnce` (use `unregister` or
63646
+ * `clear()` for those).
63647
+ */
63648
+ this.disable = () => {
63649
+ LOGGER_SERVICE$1.info(CRON_METHOD_NAME_DISABLE);
63650
+ if (this.enable.hasValue()) {
63651
+ const lastSubscription = this.enable();
63652
+ lastSubscription();
63653
+ }
63654
+ };
63655
+ }
63656
+ /**
63657
+ * Garbage-collect every `_firedOnce` key that belongs to the entry `name`
63658
+ * (any generation, global or fan-out).
63659
+ *
63660
+ * Called from `register`/`unregister` to free memory; **not** required
63661
+ * for correctness — the generation suffix already isolates re-registrations,
63662
+ * so leftover keys from old generations can never block a new entry.
63663
+ * They just sit unused until they are GC'd here or wiped by `clear()`.
63664
+ */
63665
+ _clearFiredOnceFor(name) {
63666
+ if (!name) {
63667
+ return;
63668
+ }
63669
+ const prefix = `${name}:`;
63670
+ for (const key of this._firedOnce) {
63671
+ if (key === name || key.startsWith(prefix)) {
63672
+ this._firedOnce.delete(key);
63673
+ }
63674
+ }
63675
+ }
63676
+ /**
63677
+ * Build the singleshot promise for a single in-flight slot.
63678
+ *
63679
+ * Invokes `entry.handler(symbol, aligned, backtest)`, swallows and logs
63680
+ * any error via `console.error`, and clears the `_inFlight` slot
63681
+ * in `.finally()` so the next boundary produces a fresh promise. For
63682
+ * fire-once entries `firedKey` is added to `_firedOnce` on success so
63683
+ * subsequent ticks skip it.
63684
+ *
63685
+ * @param firedKey - Key to add to `_firedOnce` on success, or `null` for
63686
+ * periodic entries (which never populate `_firedOnce`).
63687
+ * @param backtest - Value forwarded as the third handler argument; the
63688
+ * "winner" tick's flag is what all parallel awaiters of this slot see.
63689
+ */
63690
+ async _runEntry(entry, symbol, aligned, alignedMs, slotKey, firedKey, backtest) {
63691
+ let failed = false;
63692
+ try {
63693
+ await entry.handler(symbol, aligned, backtest);
63694
+ }
63695
+ catch (err) {
63696
+ failed = true;
63697
+ console.error(`${CRON_METHOD_NAME_TICK} entry "${entry.name}" failed`, { symbol, alignedMs, err });
63698
+ }
63699
+ finally {
63700
+ this._inFlight.delete(slotKey);
63701
+ if (!failed && firedKey !== null) {
63702
+ this._firedOnce.add(firedKey);
63703
+ }
63704
+ }
63705
+ }
63706
+ }
63707
+ /**
63708
+ * Singleton instance of {@link CronUtils} for registering periodic tasks
63709
+ * coordinated across parallel `Backtest.background` runs.
63710
+ *
63711
+ * @example
63712
+ * ```typescript
63713
+ * import { Cron } from "backtest-kit";
63714
+ *
63715
+ * Cron.register({
63716
+ * name: "tg-parser",
63717
+ * interval: "1h",
63718
+ * handler: async (symbol, when, backtest) => { ... },
63719
+ * });
63720
+ * ```
63721
+ */
63722
+ const Cron = new CronUtils();
63723
+
63236
63724
  const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
63237
63725
  const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
63238
63726
  const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
@@ -64315,7 +64803,7 @@ const validateSignal = (signal, currentPrice) => {
64315
64803
  }
64316
64804
  }
64317
64805
  if (errors.length > 0) {
64318
- console.error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
64806
+ console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
64319
64807
  return false;
64320
64808
  }
64321
64809
  try {
@@ -64388,9 +64876,9 @@ const validateSignal = (signal, currentPrice) => {
64388
64876
  }
64389
64877
  }
64390
64878
  if (errors.length > 0) {
64391
- console.error(`Invalid signal for ${signal.position} position:\n${errors.join("\n")}`);
64879
+ console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
64392
64880
  }
64393
64881
  return !errors.length;
64394
64882
  };
64395
64883
 
64396
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };
64884
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Cron, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, beginContext, beginTime, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toPlainString, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };