backtest-kit 9.8.1 → 9.8.3

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.
Files changed (6) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1898 -1871
  3. package/build/index.cjs +949 -282
  4. package/build/index.mjs +944 -283
  5. package/package.json +86 -86
  6. package/types.d.ts +551 -151
package/build/index.cjs CHANGED
@@ -574,6 +574,22 @@ const GLOBAL_CONFIG = {
574
574
  * Default: true (mutex locking enabled for candle fetching)
575
575
  */
576
576
  CC_ENABLE_CANDLE_FETCH_MUTEX: true,
577
+ /**
578
+ * Enables cooperative interleaving of concurrently running backtests after each candle fetch.
579
+ *
580
+ * Mechanism (implemented in `Candle.spinLock`):
581
+ * - After `getNextCandles` resolves, the current backtest awaits
582
+ * `Promise.race([_spin.toPromise(), sleep(50)])`, where `_spin` is emitted whenever
583
+ * another caller acquires the candle-fetch mutex.
584
+ * - This hands the event loop to a peer backtest waiting on the same mutex, so multiple
585
+ * parallel `Backtest.run` / `Walker` workloads progress in round-robin fashion instead
586
+ * of one monopolizing the event loop until completion.
587
+ * - The spin is skipped entirely when `Lookup.isParallel` is `false` (single active workload —
588
+ * no peer to yield to) or when `CC_ENABLE_CANDLE_FETCH_MUTEX` is disabled.
589
+ *
590
+ * Default: true (parallel backtests are interleaved on each candle fetch boundary)
591
+ */
592
+ CC_ENABLE_BACKTEST_PARALLEL_SPIN: true,
577
593
  /**
578
594
  * Enables DCA (Dollar-Cost Averaging) logic even if antirecord is not broken.
579
595
  * Allows to commitAverageBuy if currentPrice is not the lowest price since entry, but still lower than priceOpen.
@@ -792,11 +808,23 @@ const maxDrawdownSubject = new functoolsKit.Subject();
792
808
  * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
793
809
  */
794
810
  const signalNotifySubject = new functoolsKit.Subject();
811
+ /**
812
+ * Before start emitter for strategy initialization events.
813
+ * Emits when the engine is about to start a new strategy execution.
814
+ */
815
+ const beforeStartSubject = new functoolsKit.Subject();
816
+ /**
817
+ * After end emitter for strategy completion events.
818
+ * Emits when the engine has completed processing a signal.
819
+ */
820
+ const afterEndSubject = new functoolsKit.Subject();
795
821
 
796
822
  var emitters = /*#__PURE__*/Object.freeze({
797
823
  __proto__: null,
798
824
  activePingSubject: activePingSubject,
825
+ afterEndSubject: afterEndSubject,
799
826
  backtestScheduleOpenSubject: backtestScheduleOpenSubject,
827
+ beforeStartSubject: beforeStartSubject,
800
828
  breakevenSubject: breakevenSubject,
801
829
  doneBacktestSubject: doneBacktestSubject,
802
830
  doneLiveSubject: doneLiveSubject,
@@ -934,7 +962,7 @@ async function writeFileAtomic(file, data, options = {}) {
934
962
 
935
963
  var _a$3;
936
964
  /** Logger service injected as DI singleton */
937
- const LOGGER_SERVICE$7 = new LoggerService();
965
+ const LOGGER_SERVICE$8 = new LoggerService();
938
966
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
939
967
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
940
968
  // Calculate step in milliseconds for candle close time validation
@@ -1057,7 +1085,7 @@ const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
1057
1085
  const BASE_UNLINK_RETRY_COUNT = 5;
1058
1086
  const BASE_UNLINK_RETRY_DELAY = 1000;
1059
1087
  const BASE_WAIT_FOR_INIT_FN = async (self) => {
1060
- LOGGER_SERVICE$7.debug(BASE_WAIT_FOR_INIT_FN_METHOD_NAME, {
1088
+ LOGGER_SERVICE$8.debug(BASE_WAIT_FOR_INIT_FN_METHOD_NAME, {
1061
1089
  entityName: self.entityName,
1062
1090
  directory: self._directory,
1063
1091
  });
@@ -1115,7 +1143,7 @@ class PersistBase {
1115
1143
  this.entityName = entityName;
1116
1144
  this.baseDir = baseDir;
1117
1145
  this[_a$3] = functoolsKit.singleshot(async () => await BASE_WAIT_FOR_INIT_FN(this));
1118
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_CTOR, {
1146
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_CTOR, {
1119
1147
  entityName: this.entityName,
1120
1148
  baseDir,
1121
1149
  });
@@ -1131,14 +1159,14 @@ class PersistBase {
1131
1159
  return path.join(this.baseDir, this.entityName, `${entityId}.json`);
1132
1160
  }
1133
1161
  async waitForInit(initial) {
1134
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
1162
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
1135
1163
  entityName: this.entityName,
1136
1164
  initial,
1137
1165
  });
1138
1166
  await this[BASE_WAIT_FOR_INIT_SYMBOL]();
1139
1167
  }
1140
1168
  async readValue(entityId) {
1141
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
1169
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
1142
1170
  entityName: this.entityName,
1143
1171
  entityId,
1144
1172
  });
@@ -1155,7 +1183,7 @@ class PersistBase {
1155
1183
  }
1156
1184
  }
1157
1185
  async hasValue(entityId) {
1158
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_HAS_VALUE, {
1186
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_HAS_VALUE, {
1159
1187
  entityName: this.entityName,
1160
1188
  entityId,
1161
1189
  });
@@ -1172,7 +1200,7 @@ class PersistBase {
1172
1200
  }
1173
1201
  }
1174
1202
  async writeValue(entityId, entity) {
1175
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_WRITE_VALUE, {
1203
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_WRITE_VALUE, {
1176
1204
  entityName: this.entityName,
1177
1205
  entityId,
1178
1206
  });
@@ -1194,7 +1222,7 @@ class PersistBase {
1194
1222
  * @throws Error if reading fails
1195
1223
  */
1196
1224
  async *keys() {
1197
- LOGGER_SERVICE$7.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
1225
+ LOGGER_SERVICE$8.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
1198
1226
  entityName: this.entityName,
1199
1227
  });
1200
1228
  try {
@@ -1339,7 +1367,7 @@ class PersistSignalUtils {
1339
1367
  * @returns Promise resolving to signal or null if none persisted
1340
1368
  */
1341
1369
  this.readSignalData = async (symbol, strategyName, exchangeName) => {
1342
- LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
1370
+ LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
1343
1371
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1344
1372
  const isInitial = !this.getStorage.has(key);
1345
1373
  const instance = this.getStorage(symbol, strategyName, exchangeName);
@@ -1357,7 +1385,7 @@ class PersistSignalUtils {
1357
1385
  * @returns Promise that resolves when write is complete
1358
1386
  */
1359
1387
  this.writeSignalData = async (signalRow, symbol, strategyName, exchangeName) => {
1360
- LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
1388
+ LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
1361
1389
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1362
1390
  const isInitial = !this.getStorage.has(key);
1363
1391
  const instance = this.getStorage(symbol, strategyName, exchangeName);
@@ -1372,7 +1400,7 @@ class PersistSignalUtils {
1372
1400
  * @param Ctor - Custom IPersistSignalInstance constructor
1373
1401
  */
1374
1402
  usePersistSignalAdapter(Ctor) {
1375
- LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER);
1403
+ LOGGER_SERVICE$8.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER);
1376
1404
  this.PersistSignalInstanceCtor = Ctor;
1377
1405
  this.getStorage.clear();
1378
1406
  }
@@ -1381,21 +1409,21 @@ class PersistSignalUtils {
1381
1409
  * Call when process.cwd() changes between strategy iterations.
1382
1410
  */
1383
1411
  clear() {
1384
- LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_CLEAR);
1412
+ LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_CLEAR);
1385
1413
  this.getStorage.clear();
1386
1414
  }
1387
1415
  /**
1388
1416
  * Switches to the default file-based PersistSignalInstance.
1389
1417
  */
1390
1418
  useJson() {
1391
- LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
1419
+ LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
1392
1420
  this.usePersistSignalAdapter(PersistSignalInstance);
1393
1421
  }
1394
1422
  /**
1395
1423
  * Switches to PersistSignalDummyInstance (all operations are no-ops).
1396
1424
  */
1397
1425
  useDummy() {
1398
- LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
1426
+ LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
1399
1427
  this.usePersistSignalAdapter(PersistSignalDummyInstance);
1400
1428
  }
1401
1429
  }
@@ -1535,7 +1563,7 @@ class PersistRiskUtils {
1535
1563
  * @returns Promise resolving to position entries (empty array if none)
1536
1564
  */
1537
1565
  this.readPositionData = async (riskName, exchangeName, when) => {
1538
- LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
1566
+ LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
1539
1567
  const key = `${riskName}:${exchangeName}`;
1540
1568
  const isInitial = !this.getRiskStorage.has(key);
1541
1569
  const instance = this.getRiskStorage(riskName, exchangeName);
@@ -1553,7 +1581,7 @@ class PersistRiskUtils {
1553
1581
  * @returns Promise that resolves when write is complete
1554
1582
  */
1555
1583
  this.writePositionData = async (riskRow, riskName, exchangeName, when) => {
1556
- LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
1584
+ LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
1557
1585
  const key = `${riskName}:${exchangeName}`;
1558
1586
  const isInitial = !this.getRiskStorage.has(key);
1559
1587
  const instance = this.getRiskStorage(riskName, exchangeName);
@@ -1568,7 +1596,7 @@ class PersistRiskUtils {
1568
1596
  * @param Ctor - Custom IPersistRiskInstance constructor
1569
1597
  */
1570
1598
  usePersistRiskAdapter(Ctor) {
1571
- LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_USE_PERSIST_RISK_ADAPTER);
1599
+ LOGGER_SERVICE$8.info(PERSIST_RISK_UTILS_METHOD_NAME_USE_PERSIST_RISK_ADAPTER);
1572
1600
  this.PersistRiskInstanceCtor = Ctor;
1573
1601
  this.getRiskStorage.clear();
1574
1602
  }
@@ -1577,21 +1605,21 @@ class PersistRiskUtils {
1577
1605
  * Call when process.cwd() changes between strategy iterations.
1578
1606
  */
1579
1607
  clear() {
1580
- LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_CLEAR);
1608
+ LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_CLEAR);
1581
1609
  this.getRiskStorage.clear();
1582
1610
  }
1583
1611
  /**
1584
1612
  * Switches to the default file-based PersistRiskInstance.
1585
1613
  */
1586
1614
  useJson() {
1587
- LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_JSON);
1615
+ LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_JSON);
1588
1616
  this.usePersistRiskAdapter(PersistRiskInstance);
1589
1617
  }
1590
1618
  /**
1591
1619
  * Switches to PersistRiskDummyInstance (all operations are no-ops).
1592
1620
  */
1593
1621
  useDummy() {
1594
- LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_DUMMY);
1622
+ LOGGER_SERVICE$8.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_DUMMY);
1595
1623
  this.usePersistRiskAdapter(PersistRiskDummyInstance);
1596
1624
  }
1597
1625
  }
@@ -1730,7 +1758,7 @@ class PersistScheduleUtils {
1730
1758
  * @returns Promise resolving to scheduled signal or null if none persisted
1731
1759
  */
1732
1760
  this.readScheduleData = async (symbol, strategyName, exchangeName) => {
1733
- LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
1761
+ LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
1734
1762
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1735
1763
  const isInitial = !this.getScheduleStorage.has(key);
1736
1764
  const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
@@ -1748,7 +1776,7 @@ class PersistScheduleUtils {
1748
1776
  * @returns Promise that resolves when write is complete
1749
1777
  */
1750
1778
  this.writeScheduleData = async (scheduledSignalRow, symbol, strategyName, exchangeName) => {
1751
- LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
1779
+ LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
1752
1780
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1753
1781
  const isInitial = !this.getScheduleStorage.has(key);
1754
1782
  const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
@@ -1763,7 +1791,7 @@ class PersistScheduleUtils {
1763
1791
  * @param Ctor - Custom IPersistScheduleInstance constructor
1764
1792
  */
1765
1793
  usePersistScheduleAdapter(Ctor) {
1766
- LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_PERSIST_SCHEDULE_ADAPTER);
1794
+ LOGGER_SERVICE$8.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_PERSIST_SCHEDULE_ADAPTER);
1767
1795
  this.PersistScheduleInstanceCtor = Ctor;
1768
1796
  this.getScheduleStorage.clear();
1769
1797
  }
@@ -1772,21 +1800,21 @@ class PersistScheduleUtils {
1772
1800
  * Call when process.cwd() changes between strategy iterations.
1773
1801
  */
1774
1802
  clear() {
1775
- LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR);
1803
+ LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR);
1776
1804
  this.getScheduleStorage.clear();
1777
1805
  }
1778
1806
  /**
1779
1807
  * Switches to the default file-based PersistScheduleInstance.
1780
1808
  */
1781
1809
  useJson() {
1782
- LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON);
1810
+ LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON);
1783
1811
  this.usePersistScheduleAdapter(PersistScheduleInstance);
1784
1812
  }
1785
1813
  /**
1786
1814
  * Switches to PersistScheduleDummyInstance (all operations are no-ops).
1787
1815
  */
1788
1816
  useDummy() {
1789
- LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY);
1817
+ LOGGER_SERVICE$8.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY);
1790
1818
  this.usePersistScheduleAdapter(PersistScheduleDummyInstance);
1791
1819
  }
1792
1820
  }
@@ -1931,7 +1959,7 @@ class PersistPartialUtils {
1931
1959
  * @returns Promise resolving to partial data record (empty object if none)
1932
1960
  */
1933
1961
  this.readPartialData = async (symbol, strategyName, signalId, exchangeName, when) => {
1934
- LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
1962
+ LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
1935
1963
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1936
1964
  const isInitial = !this.getPartialStorage.has(key);
1937
1965
  const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
@@ -1951,7 +1979,7 @@ class PersistPartialUtils {
1951
1979
  * @returns Promise that resolves when write is complete
1952
1980
  */
1953
1981
  this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName, when) => {
1954
- LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
1982
+ LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
1955
1983
  const key = `${symbol}:${strategyName}:${exchangeName}`;
1956
1984
  const isInitial = !this.getPartialStorage.has(key);
1957
1985
  const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
@@ -1966,7 +1994,7 @@ class PersistPartialUtils {
1966
1994
  * @param Ctor - Custom IPersistPartialInstance constructor
1967
1995
  */
1968
1996
  usePersistPartialAdapter(Ctor) {
1969
- LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER);
1997
+ LOGGER_SERVICE$8.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER);
1970
1998
  this.PersistPartialInstanceCtor = Ctor;
1971
1999
  this.getPartialStorage.clear();
1972
2000
  }
@@ -1975,21 +2003,21 @@ class PersistPartialUtils {
1975
2003
  * Call when process.cwd() changes between strategy iterations.
1976
2004
  */
1977
2005
  clear() {
1978
- LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_CLEAR);
2006
+ LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_CLEAR);
1979
2007
  this.getPartialStorage.clear();
1980
2008
  }
1981
2009
  /**
1982
2010
  * Switches to the default file-based PersistPartialInstance.
1983
2011
  */
1984
2012
  useJson() {
1985
- LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_JSON);
2013
+ LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_JSON);
1986
2014
  this.usePersistPartialAdapter(PersistPartialInstance);
1987
2015
  }
1988
2016
  /**
1989
2017
  * Switches to PersistPartialDummyInstance (all operations are no-ops).
1990
2018
  */
1991
2019
  useDummy() {
1992
- LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_DUMMY);
2020
+ LOGGER_SERVICE$8.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_DUMMY);
1993
2021
  this.usePersistPartialAdapter(PersistPartialDummyInstance);
1994
2022
  }
1995
2023
  }
@@ -2154,7 +2182,7 @@ class PersistBreakevenUtils {
2154
2182
  * @returns Promise resolving to breakeven data record (empty object if none)
2155
2183
  */
2156
2184
  this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName, when) => {
2157
- LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
2185
+ LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
2158
2186
  const key = `${symbol}:${strategyName}:${exchangeName}`;
2159
2187
  const isInitial = !this.getBreakevenStorage.has(key);
2160
2188
  const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
@@ -2174,7 +2202,7 @@ class PersistBreakevenUtils {
2174
2202
  * @returns Promise that resolves when write is complete
2175
2203
  */
2176
2204
  this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName, when) => {
2177
- LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
2205
+ LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
2178
2206
  const key = `${symbol}:${strategyName}:${exchangeName}`;
2179
2207
  const isInitial = !this.getBreakevenStorage.has(key);
2180
2208
  const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
@@ -2189,7 +2217,7 @@ class PersistBreakevenUtils {
2189
2217
  * @param Ctor - Custom IPersistBreakevenInstance constructor
2190
2218
  */
2191
2219
  usePersistBreakevenAdapter(Ctor) {
2192
- LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_PERSIST_BREAKEVEN_ADAPTER);
2220
+ LOGGER_SERVICE$8.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_PERSIST_BREAKEVEN_ADAPTER);
2193
2221
  this.PersistBreakevenInstanceCtor = Ctor;
2194
2222
  this.getBreakevenStorage.clear();
2195
2223
  }
@@ -2198,21 +2226,21 @@ class PersistBreakevenUtils {
2198
2226
  * Call when process.cwd() changes between strategy iterations.
2199
2227
  */
2200
2228
  clear() {
2201
- LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_CLEAR);
2229
+ LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_CLEAR);
2202
2230
  this.getBreakevenStorage.clear();
2203
2231
  }
2204
2232
  /**
2205
2233
  * Switches to the default file-based PersistBreakevenInstance.
2206
2234
  */
2207
2235
  useJson() {
2208
- LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_JSON);
2236
+ LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_JSON);
2209
2237
  this.usePersistBreakevenAdapter(PersistBreakevenInstance);
2210
2238
  }
2211
2239
  /**
2212
2240
  * Switches to PersistBreakevenDummyInstance (all operations are no-ops).
2213
2241
  */
2214
2242
  useDummy() {
2215
- LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_DUMMY);
2243
+ LOGGER_SERVICE$8.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_DUMMY);
2216
2244
  this.usePersistBreakevenAdapter(PersistBreakevenDummyInstance);
2217
2245
  }
2218
2246
  }
@@ -2303,7 +2331,7 @@ class PersistCandleInstance {
2303
2331
  error: functoolsKit.errorData(error),
2304
2332
  message: functoolsKit.getErrorMessage(error),
2305
2333
  };
2306
- LOGGER_SERVICE$7.warn(message, payload);
2334
+ LOGGER_SERVICE$8.warn(message, payload);
2307
2335
  console.warn(message, payload);
2308
2336
  errorEmitter.next(error);
2309
2337
  return null;
@@ -2325,7 +2353,7 @@ class PersistCandleInstance {
2325
2353
  for (const candle of candles) {
2326
2354
  const candleCloseTime = candle.timestamp + stepMs;
2327
2355
  if (candleCloseTime > now) {
2328
- LOGGER_SERVICE$7.debug("PersistCandleInstance.writeCandlesData: skipping incomplete candle", {
2356
+ LOGGER_SERVICE$8.debug("PersistCandleInstance.writeCandlesData: skipping incomplete candle", {
2329
2357
  symbol: this.symbol,
2330
2358
  interval: this.interval,
2331
2359
  exchangeName: this.exchangeName,
@@ -2402,7 +2430,7 @@ class PersistCandleUtils {
2402
2430
  * @returns Promise resolving to candles in order, or null on cache miss
2403
2431
  */
2404
2432
  this.readCandlesData = async (symbol, interval, exchangeName, limit, sinceTimestamp, untilTimestamp) => {
2405
- LOGGER_SERVICE$7.info("PersistCandleUtils.readCandlesData", {
2433
+ LOGGER_SERVICE$8.info("PersistCandleUtils.readCandlesData", {
2406
2434
  symbol,
2407
2435
  interval,
2408
2436
  exchangeName,
@@ -2426,7 +2454,7 @@ class PersistCandleUtils {
2426
2454
  * @returns Promise that resolves when all writes are complete
2427
2455
  */
2428
2456
  this.writeCandlesData = async (candles, symbol, interval, exchangeName) => {
2429
- LOGGER_SERVICE$7.info("PersistCandleUtils.writeCandlesData", {
2457
+ LOGGER_SERVICE$8.info("PersistCandleUtils.writeCandlesData", {
2430
2458
  symbol,
2431
2459
  interval,
2432
2460
  exchangeName,
@@ -2446,7 +2474,7 @@ class PersistCandleUtils {
2446
2474
  * @param Ctor - Custom IPersistCandleInstance constructor
2447
2475
  */
2448
2476
  usePersistCandleAdapter(Ctor) {
2449
- LOGGER_SERVICE$7.info("PersistCandleUtils.usePersistCandleAdapter");
2477
+ LOGGER_SERVICE$8.info("PersistCandleUtils.usePersistCandleAdapter");
2450
2478
  this.PersistCandleInstanceCtor = Ctor;
2451
2479
  this.getCandlesStorage.clear();
2452
2480
  }
@@ -2455,21 +2483,21 @@ class PersistCandleUtils {
2455
2483
  * Call when process.cwd() changes between strategy iterations.
2456
2484
  */
2457
2485
  clear() {
2458
- LOGGER_SERVICE$7.log(PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR);
2486
+ LOGGER_SERVICE$8.log(PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR);
2459
2487
  this.getCandlesStorage.clear();
2460
2488
  }
2461
2489
  /**
2462
2490
  * Switches to the default file-based PersistCandleInstance.
2463
2491
  */
2464
2492
  useJson() {
2465
- LOGGER_SERVICE$7.log("PersistCandleUtils.useJson");
2493
+ LOGGER_SERVICE$8.log("PersistCandleUtils.useJson");
2466
2494
  this.usePersistCandleAdapter(PersistCandleInstance);
2467
2495
  }
2468
2496
  /**
2469
2497
  * Switches to PersistCandleDummyInstance (always returns null on read, discards writes).
2470
2498
  */
2471
2499
  useDummy() {
2472
- LOGGER_SERVICE$7.log("PersistCandleUtils.useDummy");
2500
+ LOGGER_SERVICE$8.log("PersistCandleUtils.useDummy");
2473
2501
  this.usePersistCandleAdapter(PersistCandleDummyInstance);
2474
2502
  }
2475
2503
  }
@@ -2607,7 +2635,7 @@ class PersistStorageUtils {
2607
2635
  * @returns Promise resolving to array of signal entries
2608
2636
  */
2609
2637
  this.readStorageData = async (backtest) => {
2610
- LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
2638
+ LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
2611
2639
  const key = backtest ? `backtest` : `live`;
2612
2640
  const isInitial = !this.getStorage.has(key);
2613
2641
  const instance = this.getStorage(backtest);
@@ -2623,7 +2651,7 @@ class PersistStorageUtils {
2623
2651
  * @returns Promise that resolves when write is complete
2624
2652
  */
2625
2653
  this.writeStorageData = async (signalData, backtest) => {
2626
- LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
2654
+ LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
2627
2655
  const key = backtest ? `backtest` : `live`;
2628
2656
  const isInitial = !this.getStorage.has(key);
2629
2657
  const instance = this.getStorage(backtest);
@@ -2638,7 +2666,7 @@ class PersistStorageUtils {
2638
2666
  * @param Ctor - Custom IPersistStorageInstance constructor
2639
2667
  */
2640
2668
  usePersistStorageAdapter(Ctor) {
2641
- LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
2669
+ LOGGER_SERVICE$8.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
2642
2670
  this.PersistStorageInstanceCtor = Ctor;
2643
2671
  this.getStorage.clear();
2644
2672
  }
@@ -2647,21 +2675,21 @@ class PersistStorageUtils {
2647
2675
  * Call when process.cwd() changes between strategy iterations.
2648
2676
  */
2649
2677
  clear() {
2650
- LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_CLEAR);
2678
+ LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_CLEAR);
2651
2679
  this.getStorage.clear();
2652
2680
  }
2653
2681
  /**
2654
2682
  * Switches to the default file-based PersistStorageInstance.
2655
2683
  */
2656
2684
  useJson() {
2657
- LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
2685
+ LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
2658
2686
  this.usePersistStorageAdapter(PersistStorageInstance);
2659
2687
  }
2660
2688
  /**
2661
2689
  * Switches to PersistStorageDummyInstance (all operations are no-ops).
2662
2690
  */
2663
2691
  useDummy() {
2664
- LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
2692
+ LOGGER_SERVICE$8.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
2665
2693
  this.usePersistStorageAdapter(PersistStorageDummyInstance);
2666
2694
  }
2667
2695
  }
@@ -2788,7 +2816,7 @@ class PersistNotificationUtils {
2788
2816
  * @returns Promise resolving to array of notification entries
2789
2817
  */
2790
2818
  this.readNotificationData = async (backtest) => {
2791
- LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
2819
+ LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
2792
2820
  const key = backtest ? `backtest` : `live`;
2793
2821
  const isInitial = !this.getNotificationStorage.has(key);
2794
2822
  const instance = this.getNotificationStorage(backtest);
@@ -2804,7 +2832,7 @@ class PersistNotificationUtils {
2804
2832
  * @returns Promise that resolves when write is complete
2805
2833
  */
2806
2834
  this.writeNotificationData = async (notificationData, backtest) => {
2807
- LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
2835
+ LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
2808
2836
  const key = backtest ? `backtest` : `live`;
2809
2837
  const isInitial = !this.getNotificationStorage.has(key);
2810
2838
  const instance = this.getNotificationStorage(backtest);
@@ -2819,7 +2847,7 @@ class PersistNotificationUtils {
2819
2847
  * @param Ctor - Custom IPersistNotificationInstance constructor
2820
2848
  */
2821
2849
  usePersistNotificationAdapter(Ctor) {
2822
- LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
2850
+ LOGGER_SERVICE$8.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
2823
2851
  this.PersistNotificationInstanceCtor = Ctor;
2824
2852
  this.getNotificationStorage.clear();
2825
2853
  }
@@ -2829,21 +2857,21 @@ class PersistNotificationUtils {
2829
2857
  * instances are created with the updated base path.
2830
2858
  */
2831
2859
  clear() {
2832
- LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_CLEAR);
2860
+ LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_CLEAR);
2833
2861
  this.getNotificationStorage.clear();
2834
2862
  }
2835
2863
  /**
2836
2864
  * Switches to the default file-based PersistNotificationInstance.
2837
2865
  */
2838
2866
  useJson() {
2839
- LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
2867
+ LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
2840
2868
  this.usePersistNotificationAdapter(PersistNotificationInstance);
2841
2869
  }
2842
2870
  /**
2843
2871
  * Switches to PersistNotificationDummyInstance (all operations are no-ops).
2844
2872
  */
2845
2873
  useDummy() {
2846
- LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
2874
+ LOGGER_SERVICE$8.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
2847
2875
  this.usePersistNotificationAdapter(PersistNotificationDummyInstance);
2848
2876
  }
2849
2877
  }
@@ -2967,7 +2995,7 @@ class PersistLogUtils {
2967
2995
  * @returns Promise resolving to array of log entries
2968
2996
  */
2969
2997
  this.readLogData = async () => {
2970
- LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_READ_DATA);
2998
+ LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_READ_DATA);
2971
2999
  const isInitial = !this._logInstance;
2972
3000
  const instance = this.getLogInstance();
2973
3001
  await instance.waitForInit(isInitial);
@@ -2981,7 +3009,7 @@ class PersistLogUtils {
2981
3009
  * @returns Promise that resolves when write is complete
2982
3010
  */
2983
3011
  this.writeLogData = async (logData) => {
2984
- LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA);
3012
+ LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA);
2985
3013
  const isInitial = !this._logInstance;
2986
3014
  const instance = this.getLogInstance();
2987
3015
  await instance.waitForInit(isInitial);
@@ -3006,7 +3034,7 @@ class PersistLogUtils {
3006
3034
  * @param Ctor - Custom IPersistLogInstance constructor
3007
3035
  */
3008
3036
  usePersistLogAdapter(Ctor) {
3009
- LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER);
3037
+ LOGGER_SERVICE$8.info(PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER);
3010
3038
  this.PersistLogInstanceCtor = Ctor;
3011
3039
  this._logInstance = null;
3012
3040
  }
@@ -3015,21 +3043,21 @@ class PersistLogUtils {
3015
3043
  * Call when process.cwd() changes between strategy iterations.
3016
3044
  */
3017
3045
  clear() {
3018
- LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_CLEAR);
3046
+ LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_CLEAR);
3019
3047
  this._logInstance = null;
3020
3048
  }
3021
3049
  /**
3022
3050
  * Switches to the default file-based PersistLogInstance.
3023
3051
  */
3024
3052
  useJson() {
3025
- LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON);
3053
+ LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON);
3026
3054
  this.usePersistLogAdapter(PersistLogInstance);
3027
3055
  }
3028
3056
  /**
3029
3057
  * Switches to PersistLogDummyInstance (all operations are no-ops).
3030
3058
  */
3031
3059
  useDummy() {
3032
- LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY);
3060
+ LOGGER_SERVICE$8.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY);
3033
3061
  this.usePersistLogAdapter(PersistLogDummyInstance);
3034
3062
  }
3035
3063
  }
@@ -3191,7 +3219,7 @@ class PersistMeasureUtils {
3191
3219
  * @returns Promise resolving to cached value, or null if not found / soft-deleted
3192
3220
  */
3193
3221
  this.readMeasureData = async (bucket, key) => {
3194
- LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3222
+ LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3195
3223
  const isInitial = !this.getMeasureStorage.has(bucket);
3196
3224
  const instance = this.getMeasureStorage(bucket);
3197
3225
  await instance.waitForInit(isInitial);
@@ -3207,7 +3235,7 @@ class PersistMeasureUtils {
3207
3235
  * @returns Promise that resolves when write is complete
3208
3236
  */
3209
3237
  this.writeMeasureData = async (data, bucket, key, when) => {
3210
- LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3238
+ LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3211
3239
  const isInitial = !this.getMeasureStorage.has(bucket);
3212
3240
  const instance = this.getMeasureStorage(bucket);
3213
3241
  await instance.waitForInit(isInitial);
@@ -3222,7 +3250,7 @@ class PersistMeasureUtils {
3222
3250
  * @returns Promise that resolves when removal is complete
3223
3251
  */
3224
3252
  this.removeMeasureData = async (bucket, key) => {
3225
- LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3253
+ LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3226
3254
  const isInitial = !this.getMeasureStorage.has(bucket);
3227
3255
  const instance = this.getMeasureStorage(bucket);
3228
3256
  await instance.waitForInit(isInitial);
@@ -3236,7 +3264,7 @@ class PersistMeasureUtils {
3236
3264
  * @param Ctor - Custom IPersistMeasureInstance constructor
3237
3265
  */
3238
3266
  usePersistMeasureAdapter(Ctor) {
3239
- LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
3267
+ LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
3240
3268
  this.PersistMeasureInstanceCtor = Ctor;
3241
3269
  this.getMeasureStorage.clear();
3242
3270
  }
@@ -3248,7 +3276,7 @@ class PersistMeasureUtils {
3248
3276
  * @returns AsyncGenerator yielding entry keys
3249
3277
  */
3250
3278
  async *listMeasureData(bucket) {
3251
- LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3279
+ LOGGER_SERVICE$8.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3252
3280
  const isInitial = !this.getMeasureStorage.has(bucket);
3253
3281
  const instance = this.getMeasureStorage(bucket);
3254
3282
  await instance.waitForInit(isInitial);
@@ -3259,21 +3287,21 @@ class PersistMeasureUtils {
3259
3287
  * Call when process.cwd() changes between strategy iterations.
3260
3288
  */
3261
3289
  clear() {
3262
- LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR);
3290
+ LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR);
3263
3291
  this.getMeasureStorage.clear();
3264
3292
  }
3265
3293
  /**
3266
3294
  * Switches to the default file-based PersistMeasureInstance.
3267
3295
  */
3268
3296
  useJson() {
3269
- LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
3297
+ LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
3270
3298
  this.usePersistMeasureAdapter(PersistMeasureInstance);
3271
3299
  }
3272
3300
  /**
3273
3301
  * Switches to PersistMeasureDummyInstance (all operations are no-ops).
3274
3302
  */
3275
3303
  useDummy() {
3276
- LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
3304
+ LOGGER_SERVICE$8.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
3277
3305
  this.usePersistMeasureAdapter(PersistMeasureDummyInstance);
3278
3306
  }
3279
3307
  }
@@ -3432,7 +3460,7 @@ class PersistIntervalUtils {
3432
3460
  * @returns Promise resolving to marker data, or null if not found / soft-deleted
3433
3461
  */
3434
3462
  this.readIntervalData = async (bucket, key) => {
3435
- LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3463
+ LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
3436
3464
  const isInitial = !this.getIntervalStorage.has(bucket);
3437
3465
  const instance = this.getIntervalStorage(bucket);
3438
3466
  await instance.waitForInit(isInitial);
@@ -3448,7 +3476,7 @@ class PersistIntervalUtils {
3448
3476
  * @returns Promise that resolves when write is complete
3449
3477
  */
3450
3478
  this.writeIntervalData = async (data, bucket, key, when) => {
3451
- LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3479
+ LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3452
3480
  const isInitial = !this.getIntervalStorage.has(bucket);
3453
3481
  const instance = this.getIntervalStorage(bucket);
3454
3482
  await instance.waitForInit(isInitial);
@@ -3463,7 +3491,7 @@ class PersistIntervalUtils {
3463
3491
  * @returns Promise that resolves when removal is complete
3464
3492
  */
3465
3493
  this.removeIntervalData = async (bucket, key) => {
3466
- LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3494
+ LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
3467
3495
  const isInitial = !this.getIntervalStorage.has(bucket);
3468
3496
  const instance = this.getIntervalStorage(bucket);
3469
3497
  await instance.waitForInit(isInitial);
@@ -3477,7 +3505,7 @@ class PersistIntervalUtils {
3477
3505
  * @param Ctor - Custom IPersistIntervalInstance constructor
3478
3506
  */
3479
3507
  usePersistIntervalAdapter(Ctor) {
3480
- LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
3508
+ LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
3481
3509
  this.PersistIntervalInstanceCtor = Ctor;
3482
3510
  this.getIntervalStorage.clear();
3483
3511
  }
@@ -3489,7 +3517,7 @@ class PersistIntervalUtils {
3489
3517
  * @returns AsyncGenerator yielding marker keys
3490
3518
  */
3491
3519
  async *listIntervalData(bucket) {
3492
- LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3520
+ LOGGER_SERVICE$8.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
3493
3521
  const isInitial = !this.getIntervalStorage.has(bucket);
3494
3522
  const instance = this.getIntervalStorage(bucket);
3495
3523
  await instance.waitForInit(isInitial);
@@ -3500,21 +3528,21 @@ class PersistIntervalUtils {
3500
3528
  * Call when process.cwd() changes between strategy iterations.
3501
3529
  */
3502
3530
  clear() {
3503
- LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
3531
+ LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
3504
3532
  this.getIntervalStorage.clear();
3505
3533
  }
3506
3534
  /**
3507
3535
  * Switches to the default file-based PersistIntervalInstance.
3508
3536
  */
3509
3537
  useJson() {
3510
- LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
3538
+ LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
3511
3539
  this.usePersistIntervalAdapter(PersistIntervalInstance);
3512
3540
  }
3513
3541
  /**
3514
3542
  * Switches to PersistIntervalDummyInstance (all operations are no-ops).
3515
3543
  */
3516
3544
  useDummy() {
3517
- LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
3545
+ LOGGER_SERVICE$8.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
3518
3546
  this.usePersistIntervalAdapter(PersistIntervalDummyInstance);
3519
3547
  }
3520
3548
  }
@@ -3720,7 +3748,7 @@ class PersistMemoryUtils {
3720
3748
  * @returns Promise resolving to entry data, or null if not found / soft-deleted
3721
3749
  */
3722
3750
  this.readMemoryData = async (signalId, bucketName, memoryId) => {
3723
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName, memoryId });
3751
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName, memoryId });
3724
3752
  const key = `${signalId}:${bucketName}`;
3725
3753
  const isInitial = !this.getMemoryStorage.has(key);
3726
3754
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3737,7 +3765,7 @@ class PersistMemoryUtils {
3737
3765
  * @returns Promise resolving to true if entry exists
3738
3766
  */
3739
3767
  this.hasMemoryData = async (signalId, bucketName, memoryId) => {
3740
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, { signalId, bucketName, memoryId });
3768
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, { signalId, bucketName, memoryId });
3741
3769
  const key = `${signalId}:${bucketName}`;
3742
3770
  const isInitial = !this.getMemoryStorage.has(key);
3743
3771
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3756,7 +3784,7 @@ class PersistMemoryUtils {
3756
3784
  * @returns Promise that resolves when write is complete
3757
3785
  */
3758
3786
  this.writeMemoryData = async (data, signalId, bucketName, memoryId, when) => {
3759
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
3787
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
3760
3788
  const key = `${signalId}:${bucketName}`;
3761
3789
  const isInitial = !this.getMemoryStorage.has(key);
3762
3790
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3773,7 +3801,7 @@ class PersistMemoryUtils {
3773
3801
  * @returns Promise that resolves when removal is complete
3774
3802
  */
3775
3803
  this.removeMemoryData = async (signalId, bucketName, memoryId) => {
3776
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, { signalId, bucketName, memoryId });
3804
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, { signalId, bucketName, memoryId });
3777
3805
  const key = `${signalId}:${bucketName}`;
3778
3806
  const isInitial = !this.getMemoryStorage.has(key);
3779
3807
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3785,7 +3813,7 @@ class PersistMemoryUtils {
3785
3813
  * Call when process.cwd() changes between strategy iterations.
3786
3814
  */
3787
3815
  this.clear = () => {
3788
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR);
3816
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR);
3789
3817
  this.getMemoryStorage.clear();
3790
3818
  };
3791
3819
  /**
@@ -3796,7 +3824,7 @@ class PersistMemoryUtils {
3796
3824
  * @param bucketName - Bucket name
3797
3825
  */
3798
3826
  this.dispose = (signalId, bucketName) => {
3799
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE);
3827
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE);
3800
3828
  const key = `${signalId}:${bucketName}`;
3801
3829
  this.getMemoryStorage.clear(key);
3802
3830
  };
@@ -3808,7 +3836,7 @@ class PersistMemoryUtils {
3808
3836
  * @param Ctor - Custom IPersistMemoryInstance constructor
3809
3837
  */
3810
3838
  usePersistMemoryAdapter(Ctor) {
3811
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER);
3839
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER);
3812
3840
  this.PersistMemoryInstanceCtor = Ctor;
3813
3841
  this.getMemoryStorage.clear();
3814
3842
  }
@@ -3822,7 +3850,7 @@ class PersistMemoryUtils {
3822
3850
  * @returns AsyncGenerator yielding `{ memoryId, data }` tuples
3823
3851
  */
3824
3852
  async *listMemoryData(signalId, bucketName) {
3825
- LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, { signalId, bucketName });
3853
+ LOGGER_SERVICE$8.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, { signalId, bucketName });
3826
3854
  const key = `${signalId}:${bucketName}`;
3827
3855
  const isInitial = !this.getMemoryStorage.has(key);
3828
3856
  const instance = this.getMemoryStorage(signalId, bucketName);
@@ -3833,14 +3861,14 @@ class PersistMemoryUtils {
3833
3861
  * Switches to the default file-based PersistMemoryInstance.
3834
3862
  */
3835
3863
  useJson() {
3836
- LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
3864
+ LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
3837
3865
  this.usePersistMemoryAdapter(PersistMemoryInstance);
3838
3866
  }
3839
3867
  /**
3840
3868
  * Switches to PersistMemoryDummyInstance (all operations are no-ops).
3841
3869
  */
3842
3870
  useDummy() {
3843
- LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
3871
+ LOGGER_SERVICE$8.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
3844
3872
  this.usePersistMemoryAdapter(PersistMemoryDummyInstance);
3845
3873
  }
3846
3874
  }
@@ -3989,7 +4017,7 @@ class PersistRecentUtils {
3989
4017
  * @returns Promise resolving to recent signal or null if none persisted
3990
4018
  */
3991
4019
  this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
3992
- LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
4020
+ LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
3993
4021
  const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
3994
4022
  const isInitial = !this.getStorage.has(key);
3995
4023
  const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
@@ -4010,7 +4038,7 @@ class PersistRecentUtils {
4010
4038
  * @returns Promise that resolves when write is complete
4011
4039
  */
4012
4040
  this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest, when) => {
4013
- LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
4041
+ LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
4014
4042
  const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
4015
4043
  const isInitial = !this.getStorage.has(key);
4016
4044
  const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
@@ -4043,7 +4071,7 @@ class PersistRecentUtils {
4043
4071
  * @param Ctor - Custom IPersistRecentInstance constructor
4044
4072
  */
4045
4073
  usePersistRecentAdapter(Ctor) {
4046
- LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
4074
+ LOGGER_SERVICE$8.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
4047
4075
  this.PersistRecentInstanceCtor = Ctor;
4048
4076
  this.getStorage.clear();
4049
4077
  }
@@ -4052,21 +4080,21 @@ class PersistRecentUtils {
4052
4080
  * Call when process.cwd() changes between strategy iterations.
4053
4081
  */
4054
4082
  clear() {
4055
- LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
4083
+ LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
4056
4084
  this.getStorage.clear();
4057
4085
  }
4058
4086
  /**
4059
4087
  * Switches to the default file-based PersistRecentInstance.
4060
4088
  */
4061
4089
  useJson() {
4062
- LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
4090
+ LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
4063
4091
  this.usePersistRecentAdapter(PersistRecentInstance);
4064
4092
  }
4065
4093
  /**
4066
4094
  * Switches to PersistRecentDummyInstance (all operations are no-ops).
4067
4095
  */
4068
4096
  useDummy() {
4069
- LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
4097
+ LOGGER_SERVICE$8.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
4070
4098
  this.usePersistRecentAdapter(PersistRecentDummyInstance);
4071
4099
  }
4072
4100
  }
@@ -4201,7 +4229,7 @@ class PersistStateUtils {
4201
4229
  * @returns Promise that resolves when initialization is complete
4202
4230
  */
4203
4231
  this.waitForInit = async (signalId, bucketName, initial) => {
4204
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, { signalId, bucketName, initial });
4232
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, { signalId, bucketName, initial });
4205
4233
  const key = `${signalId}:${bucketName}`;
4206
4234
  const isInitial = initial && !this.getStateStorage.has(key);
4207
4235
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4216,7 +4244,7 @@ class PersistStateUtils {
4216
4244
  * @returns Promise resolving to state data or null if none persisted
4217
4245
  */
4218
4246
  this.readStateData = async (signalId, bucketName) => {
4219
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName });
4247
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName });
4220
4248
  const key = `${signalId}:${bucketName}`;
4221
4249
  const isInitial = !this.getStateStorage.has(key);
4222
4250
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4234,7 +4262,7 @@ class PersistStateUtils {
4234
4262
  * @returns Promise that resolves when write is complete
4235
4263
  */
4236
4264
  this.writeStateData = async (data, signalId, bucketName, when) => {
4237
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
4265
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
4238
4266
  const key = `${signalId}:${bucketName}`;
4239
4267
  const isInitial = !this.getStateStorage.has(key);
4240
4268
  const instance = this.getStateStorage(signalId, bucketName);
@@ -4245,14 +4273,14 @@ class PersistStateUtils {
4245
4273
  * Switches to PersistStateDummyInstance (all operations are no-ops).
4246
4274
  */
4247
4275
  this.useDummy = () => {
4248
- LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
4276
+ LOGGER_SERVICE$8.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
4249
4277
  this.usePersistStateAdapter(PersistStateDummyInstance);
4250
4278
  };
4251
4279
  /**
4252
4280
  * Switches to the default file-based PersistStateInstance.
4253
4281
  */
4254
4282
  this.useJson = () => {
4255
- LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
4283
+ LOGGER_SERVICE$8.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
4256
4284
  this.usePersistStateAdapter(PersistStateInstance);
4257
4285
  };
4258
4286
  /**
@@ -4260,7 +4288,7 @@ class PersistStateUtils {
4260
4288
  * Call when process.cwd() changes between strategy iterations.
4261
4289
  */
4262
4290
  this.clear = () => {
4263
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
4291
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
4264
4292
  this.getStateStorage.clear();
4265
4293
  };
4266
4294
  /**
@@ -4271,7 +4299,7 @@ class PersistStateUtils {
4271
4299
  * @param bucketName - Bucket name
4272
4300
  */
4273
4301
  this.dispose = (signalId, bucketName) => {
4274
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
4302
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
4275
4303
  const key = `${signalId}:${bucketName}`;
4276
4304
  this.getStateStorage.clear(key);
4277
4305
  };
@@ -4283,7 +4311,7 @@ class PersistStateUtils {
4283
4311
  * @param Ctor - Custom IPersistStateInstance constructor
4284
4312
  */
4285
4313
  usePersistStateAdapter(Ctor) {
4286
- LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
4314
+ LOGGER_SERVICE$8.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
4287
4315
  this.PersistStateInstanceCtor = Ctor;
4288
4316
  this.getStateStorage.clear();
4289
4317
  }
@@ -4423,7 +4451,7 @@ class PersistSessionUtils {
4423
4451
  * @returns Promise that resolves when initialization is complete
4424
4452
  */
4425
4453
  this.waitForInit = async (strategyName, exchangeName, frameName, initial) => {
4426
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, { strategyName, exchangeName, frameName, initial });
4454
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, { strategyName, exchangeName, frameName, initial });
4427
4455
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4428
4456
  const isInitial = initial && !this.getSessionStorage.has(key);
4429
4457
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4439,7 +4467,7 @@ class PersistSessionUtils {
4439
4467
  * @returns Promise resolving to session data or null if none persisted
4440
4468
  */
4441
4469
  this.readSessionData = async (strategyName, exchangeName, frameName) => {
4442
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, { strategyName, exchangeName, frameName });
4470
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, { strategyName, exchangeName, frameName });
4443
4471
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4444
4472
  const isInitial = !this.getSessionStorage.has(key);
4445
4473
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4458,7 +4486,7 @@ class PersistSessionUtils {
4458
4486
  * @returns Promise that resolves when write is complete
4459
4487
  */
4460
4488
  this.writeSessionData = async (data, strategyName, exchangeName, frameName, when) => {
4461
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
4489
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
4462
4490
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4463
4491
  const isInitial = !this.getSessionStorage.has(key);
4464
4492
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
@@ -4469,14 +4497,14 @@ class PersistSessionUtils {
4469
4497
  * Switches to PersistSessionDummyInstance (all operations are no-ops).
4470
4498
  */
4471
4499
  this.useDummy = () => {
4472
- LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
4500
+ LOGGER_SERVICE$8.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
4473
4501
  this.usePersistSessionAdapter(PersistSessionDummyInstance);
4474
4502
  };
4475
4503
  /**
4476
4504
  * Switches to the default file-based PersistSessionInstance.
4477
4505
  */
4478
4506
  this.useJson = () => {
4479
- LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
4507
+ LOGGER_SERVICE$8.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
4480
4508
  this.usePersistSessionAdapter(PersistSessionInstance);
4481
4509
  };
4482
4510
  /**
@@ -4484,7 +4512,7 @@ class PersistSessionUtils {
4484
4512
  * Call when process.cwd() changes between strategy iterations.
4485
4513
  */
4486
4514
  this.clear = () => {
4487
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
4515
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
4488
4516
  this.getSessionStorage.clear();
4489
4517
  };
4490
4518
  /**
@@ -4496,7 +4524,7 @@ class PersistSessionUtils {
4496
4524
  * @param frameName - Frame identifier
4497
4525
  */
4498
4526
  this.dispose = (strategyName, exchangeName, frameName) => {
4499
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE);
4527
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE);
4500
4528
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4501
4529
  this.getSessionStorage.clear(key);
4502
4530
  };
@@ -4508,7 +4536,7 @@ class PersistSessionUtils {
4508
4536
  * @param Ctor - Custom IPersistSessionInstance constructor
4509
4537
  */
4510
4538
  usePersistSessionAdapter(Ctor) {
4511
- LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
4539
+ LOGGER_SERVICE$8.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
4512
4540
  this.PersistSessionInstanceCtor = Ctor;
4513
4541
  this.getSessionStorage.clear();
4514
4542
  }
@@ -4520,28 +4548,39 @@ class PersistSessionUtils {
4520
4548
  const PersistSessionAdapter = new PersistSessionUtils();
4521
4549
 
4522
4550
  var _a$2, _b$2;
4523
- const BUSY_DELAY = 100;
4524
4551
  const SET_BUSY_SYMBOL = Symbol("setBusy");
4525
4552
  const GET_BUSY_SYMBOL = Symbol("getBusy");
4526
4553
  const ACQUIRE_LOCK_SYMBOL = Symbol("acquireLock");
4527
4554
  const RELEASE_LOCK_SYMBOL = Symbol("releaseLock");
4555
+ /**
4556
+ * Body of the queued acquire operation.
4557
+ *
4558
+ * Parks the caller on `self._tick` whenever the lock is already busy: each
4559
+ * `releaseLock` emits on `_tick`, waking exactly the next queued acquirer
4560
+ * instead of polling on a fixed delay. The busy counter is bumped only after
4561
+ * the loop exits, so re-entry checks remain coherent under contention.
4562
+ *
4563
+ * @param self - The owning {@link Lock} instance.
4564
+ */
4528
4565
  const ACQUIRE_LOCK_FN = async (self) => {
4529
4566
  while (self[GET_BUSY_SYMBOL]()) {
4530
- await functoolsKit.sleep(BUSY_DELAY);
4567
+ // @ts-ignore
4568
+ await self._tick.toPromise();
4531
4569
  }
4532
4570
  self[SET_BUSY_SYMBOL](true);
4533
4571
  };
4534
4572
  /**
4535
4573
  * Mutual exclusion primitive for async TypeScript code.
4536
4574
  *
4537
- * Provides a reentrant-safe, queued lock that serializes access to a critical
4538
- * section across concurrent async callers. Internally tracks a busy counter so
4539
- * nested acquire/release pairs are detected and mis-matched releases throw
4540
- * immediately.
4575
+ * Provides a queued lock that serializes access to a critical section across
4576
+ * concurrent async callers. Wake-ups are event-driven (via an internal
4577
+ * `_tick` subject emitted on every `releaseLock`) rather than polling,
4578
+ * so contention does not incur a fixed delay.
4541
4579
  *
4542
- * Three usage styles are supported:
4580
+ * The busy counter detects mis-matched releases and throws immediately on
4581
+ * extra `releaseLock` calls.
4543
4582
  *
4544
- * **Manual acquire / release**
4583
+ * **Usage**
4545
4584
  * ```ts
4546
4585
  * await lock.acquireLock();
4547
4586
  * try {
@@ -4556,7 +4595,18 @@ const ACQUIRE_LOCK_FN = async (self) => {
4556
4595
  */
4557
4596
  class Lock {
4558
4597
  constructor() {
4598
+ /**
4599
+ * Outstanding acquires that have not yet been released.
4600
+ * Incremented in `[SET_BUSY_SYMBOL](true)`, decremented in `[SET_BUSY_SYMBOL](false)`.
4601
+ * A negative value indicates an extra `releaseLock` and throws on detection.
4602
+ */
4559
4603
  this._isBusy = 0;
4604
+ /**
4605
+ * Wake-up channel for {@link ACQUIRE_LOCK_FN}.
4606
+ * Every {@link releaseLock} emits a single tick that unblocks the next
4607
+ * queued acquirer parked on `toPromise()`.
4608
+ */
4609
+ this._tick = new functoolsKit.Subject();
4560
4610
  this[_a$2] = functoolsKit.queued(ACQUIRE_LOCK_FN);
4561
4611
  this[_b$2] = () => this[SET_BUSY_SYMBOL](false);
4562
4612
  /**
@@ -4577,16 +4627,19 @@ class Lock {
4577
4627
  await this[ACQUIRE_LOCK_SYMBOL](this);
4578
4628
  };
4579
4629
  /**
4580
- * Releases the lock previously acquired with {@link acquireLock}.
4630
+ * Releases the lock previously acquired with {@link acquireLock} and emits
4631
+ * on the internal `_tick` subject to wake the next queued acquirer.
4632
+ *
4581
4633
  * Must be called exactly once per successful {@link acquireLock} call,
4582
- * typically inside a `finally` block. Throws if called more times
4583
- * than the lock was acquired.
4634
+ * typically inside a `finally` block. Throws if called more times than
4635
+ * the lock was acquired.
4584
4636
  *
4585
4637
  * @returns {Promise<void>} Resolves once the lock has been released.
4586
4638
  * @throws {Error} If the lock is released more times than it was acquired.
4587
4639
  */
4588
4640
  this.releaseLock = async () => {
4589
4641
  await this[RELEASE_LOCK_SYMBOL]();
4642
+ await this._tick.next();
4590
4643
  };
4591
4644
  }
4592
4645
  [SET_BUSY_SYMBOL](isBusy) {
@@ -4601,18 +4654,166 @@ class Lock {
4601
4654
  }
4602
4655
  _a$2 = ACQUIRE_LOCK_SYMBOL, _b$2 = RELEASE_LOCK_SYMBOL;
4603
4656
 
4657
+ const METHOD_NAME_ADD_ACTIVITY = "LookupUtils.addActivity";
4658
+ const METHOD_NAME_REMOVE_ACTIVITY = "LookupUtils.removeActivity";
4659
+ const METHOD_NAME_LIST_ACTIVITY = "LookupUtils.listActivity";
4660
+ /** Logger service injected as DI singleton */
4661
+ const LOGGER_SERVICE$7 = new LoggerService();
4662
+ /**
4663
+ * Builds the composite {@link Key} used to register an activity in `_lookupMap`.
4664
+ *
4665
+ * Mirrors the {@link Key} type construction: appends `frameName` only when provided
4666
+ * (typical for backtest), then a `"backtest"` / `"live"` discriminator suffix.
4667
+ *
4668
+ * @param symbol - Trading pair symbol.
4669
+ * @param strategyName - Strategy schema name.
4670
+ * @param exchangeName - Exchange schema name.
4671
+ * @param frameName - Frame schema name; omitted from the key when falsy.
4672
+ * @param backtest - `true` for backtest, `false` for live.
4673
+ * @returns Colon-joined composite key.
4674
+ */
4675
+ const CREATE_KEY_FN$y = (symbol, strategyName, exchangeName, frameName, backtest) => {
4676
+ const parts = [symbol, strategyName, exchangeName];
4677
+ if (frameName)
4678
+ parts.push(frameName);
4679
+ parts.push(backtest ? "backtest" : "live");
4680
+ return parts.join(":");
4681
+ };
4682
+ /**
4683
+ * In-memory registry of currently running backtest and live activities.
4684
+ *
4685
+ * Purpose:
4686
+ * - Each `Backtest.run` / `Live.run` / per-strategy walker iteration registers an
4687
+ * {@link IActivityEntry} on start and removes it on completion.
4688
+ * - `Candle.spinLock` consults {@link isParallel} to decide whether the event-loop
4689
+ * hand-off (post-candle-fetch spin) is worth performing. With a single active
4690
+ * workload there is no peer to yield to, so the spin is skipped entirely.
4691
+ *
4692
+ * Exposed as the `Lookup` singleton; no constructor parameters.
4693
+ *
4694
+ * @example
4695
+ * ```typescript
4696
+ * Lookup.addActivity({ symbol: "BTCUSDT", context, backtest: true });
4697
+ * try {
4698
+ * for await (const _ of run(symbol, context)) { ... }
4699
+ * } finally {
4700
+ * Lookup.removeActivity({ symbol: "BTCUSDT", context, backtest: true });
4701
+ * }
4702
+ * ```
4703
+ */
4704
+ class LookupUtils {
4705
+ constructor() {
4706
+ /** Active entries keyed by their composite {@link Key}. */
4707
+ this._lookupMap = new Map();
4708
+ /**
4709
+ * Registers a backtest or live activity in the lookup map.
4710
+ * Idempotent for identical keys — duplicate calls overwrite the existing entry.
4711
+ *
4712
+ * @param activity - Activity descriptor identifying the running workload.
4713
+ */
4714
+ this.addActivity = (activity) => {
4715
+ LOGGER_SERVICE$7.info(METHOD_NAME_ADD_ACTIVITY, {
4716
+ activity,
4717
+ });
4718
+ const key = CREATE_KEY_FN$y(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
4719
+ this._lookupMap.set(key, activity);
4720
+ };
4721
+ /**
4722
+ * Removes a previously registered activity from the lookup map.
4723
+ * Must be paired with a prior {@link addActivity}, typically in a `finally` block,
4724
+ * so a thrown error in the underlying run does not leave a stale entry behind.
4725
+ *
4726
+ * @param activity - Activity descriptor matching the one passed to {@link addActivity}.
4727
+ */
4728
+ this.removeActivity = (activity) => {
4729
+ LOGGER_SERVICE$7.info(METHOD_NAME_REMOVE_ACTIVITY, {
4730
+ activity,
4731
+ });
4732
+ const key = CREATE_KEY_FN$y(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
4733
+ this._lookupMap.delete(key);
4734
+ };
4735
+ /**
4736
+ * Returns a snapshot of currently active entries.
4737
+ *
4738
+ * @returns Array of all activities present in the lookup map at call time.
4739
+ */
4740
+ this.listActivity = () => {
4741
+ LOGGER_SERVICE$7.info(METHOD_NAME_LIST_ACTIVITY);
4742
+ return Array.from(this._lookupMap.values());
4743
+ };
4744
+ }
4745
+ /**
4746
+ * `true` when more than one activity is currently registered.
4747
+ * Used by `Candle.spinLock` to decide whether yielding the event loop is useful.
4748
+ */
4749
+ get isParallel() {
4750
+ return this._lookupMap.size > 1;
4751
+ }
4752
+ }
4753
+ /**
4754
+ * Process-wide singleton instance of {@link LookupUtils}.
4755
+ * Imported by `Backtest`, `Live`, `WalkerLogicPrivateService` (registration sites)
4756
+ * and by `Candle` (read-only consumer via `isParallel`).
4757
+ */
4758
+ const Lookup = new LookupUtils();
4759
+
4604
4760
  const METHOD_NAME_ACQUIRE_LOCK = "CandleUtils.acquireLock";
4605
4761
  const METHOD_NAME_RELEASE_LOCK = "CandleUtils.releaseLock";
4762
+ const METHOD_NAME_SPIN_LOCK = "CandleUtils.spinLock";
4763
+ /**
4764
+ * Upper bound (ms) on how long `spinLock` may park before falling through.
4765
+ * If no peer backtest acquires the candle-fetch mutex within this window,
4766
+ * the spinner proceeds without further yielding.
4767
+ */
4768
+ const ROTATE_DELAY = 50;
4606
4769
  /** Logger service injected as DI singleton */
4607
4770
  const LOGGER_SERVICE$6 = new LoggerService();
4771
+ /**
4772
+ * Process-wide coordinator for candle-fetch serialization and cooperative
4773
+ * yielding between parallel backtests.
4774
+ *
4775
+ * Two complementary primitives are exposed:
4776
+ * - **Mutex** via {@link acquireLock} / {@link releaseLock}: prevents concurrent
4777
+ * candle fetches from racing on the same exchange.
4778
+ * - **Spin hand-off** via {@link spinLock}: invoked after a fetch completes to
4779
+ * give peer backtests waiting on the mutex a chance to run, so multiple
4780
+ * `Backtest.run` workloads interleave instead of one monopolizing the loop.
4781
+ *
4782
+ * All three operations are no-ops when `CC_ENABLE_CANDLE_FETCH_MUTEX` is `false`.
4783
+ * The spin additionally requires `CC_ENABLE_BACKTEST_PARALLEL_SPIN` and at least
4784
+ * two registered activities in `Lookup` (see `Lookup.isParallel`).
4785
+ *
4786
+ * @example
4787
+ * ```typescript
4788
+ * await Candle.acquireLock("ClientExchange GET_CANDLES_FN");
4789
+ * try {
4790
+ * const candles = await fetchFromExchange(...);
4791
+ * return candles;
4792
+ * } finally {
4793
+ * await Candle.releaseLock("ClientExchange GET_CANDLES_FN");
4794
+ * }
4795
+ * // Elsewhere, after a fetch completes inside a backtest loop:
4796
+ * await Candle.spinLock("BacktestLogicPrivateService GET_CANDLES_FN");
4797
+ * ```
4798
+ */
4608
4799
  class CandleUtils {
4609
4800
  constructor() {
4801
+ /** Underlying mutex serializing candle fetches across concurrent callers. */
4610
4802
  this._lock = new Lock();
4611
4803
  /**
4612
- * Acquires the candle fetch mutex if CC_ENABLE_CANDLE_FETCH_MUTEX is enabled.
4804
+ * Emits whenever {@link acquireLock} successfully takes the mutex.
4805
+ * Awaited by {@link spinLock} to detect that a peer backtest has just
4806
+ * started its own fetch — the signal that yielding now will be productive.
4807
+ */
4808
+ this._spin = new functoolsKit.Subject();
4809
+ /**
4810
+ * Acquires the candle fetch mutex if `CC_ENABLE_CANDLE_FETCH_MUTEX` is enabled.
4613
4811
  * Prevents concurrent candle fetches from the same exchange.
4614
4812
  *
4615
- * @param source - Caller identifier for logging
4813
+ * On successful acquisition, emits on the internal spin subject so any
4814
+ * peer parked inside {@link spinLock} can wake up and proceed.
4815
+ *
4816
+ * @param source - Caller identifier for logging.
4616
4817
  */
4617
4818
  this.acquireLock = async (source) => {
4618
4819
  LOGGER_SERVICE$6.info(METHOD_NAME_ACQUIRE_LOCK, {
@@ -4621,13 +4822,14 @@ class CandleUtils {
4621
4822
  if (!GLOBAL_CONFIG.CC_ENABLE_CANDLE_FETCH_MUTEX) {
4622
4823
  return;
4623
4824
  }
4624
- return await this._lock.acquireLock();
4825
+ await this._lock.acquireLock();
4826
+ await this._spin.next();
4625
4827
  };
4626
4828
  /**
4627
- * Releases the candle fetch mutex if CC_ENABLE_CANDLE_FETCH_MUTEX is enabled.
4628
- * Must be called after acquireLock, typically in a finally block.
4829
+ * Releases the candle fetch mutex if `CC_ENABLE_CANDLE_FETCH_MUTEX` is enabled.
4830
+ * Must be called after {@link acquireLock}, typically in a `finally` block.
4629
4831
  *
4630
- * @param source - Caller identifier for logging
4832
+ * @param source - Caller identifier for logging.
4631
4833
  */
4632
4834
  this.releaseLock = async (source) => {
4633
4835
  LOGGER_SERVICE$6.info(METHOD_NAME_RELEASE_LOCK, {
@@ -4638,8 +4840,47 @@ class CandleUtils {
4638
4840
  }
4639
4841
  return await this._lock.releaseLock();
4640
4842
  };
4843
+ /**
4844
+ * Cooperative event-loop hand-off invoked by `BacktestLogicPrivateService`
4845
+ * after a successful `getNextCandles`. Allows peer backtests waiting on the
4846
+ * candle-fetch mutex to run before the current backtest fetches the next chunk.
4847
+ *
4848
+ * Waits for one of:
4849
+ * - a peer calling {@link acquireLock} (signalled via the spin subject), or
4850
+ * - a `ROTATE_DELAY` ms timeout, so the caller never parks indefinitely.
4851
+ *
4852
+ * Returns immediately as a no-op when any of these is true:
4853
+ * - `CC_ENABLE_CANDLE_FETCH_MUTEX` is disabled (mutex is off entirely),
4854
+ * - `CC_ENABLE_BACKTEST_PARALLEL_SPIN` is disabled (cooperative yielding off),
4855
+ * - `Lookup.isParallel` is `false` (only one active workload — no peer to yield to).
4856
+ *
4857
+ * @param source - Caller identifier for logging.
4858
+ */
4859
+ this.spinLock = async (source) => {
4860
+ LOGGER_SERVICE$6.info(METHOD_NAME_SPIN_LOCK, {
4861
+ source,
4862
+ });
4863
+ if (!GLOBAL_CONFIG.CC_ENABLE_CANDLE_FETCH_MUTEX) {
4864
+ return;
4865
+ }
4866
+ if (!GLOBAL_CONFIG.CC_ENABLE_BACKTEST_PARALLEL_SPIN) {
4867
+ return;
4868
+ }
4869
+ if (!Lookup.isParallel) {
4870
+ return;
4871
+ }
4872
+ await Promise.race([
4873
+ this._spin.toPromise(),
4874
+ functoolsKit.sleep(ROTATE_DELAY),
4875
+ ]);
4876
+ };
4641
4877
  }
4642
4878
  }
4879
+ /**
4880
+ * Process-wide singleton instance of {@link CandleUtils}.
4881
+ * Imported by `ClientExchange` (mutex around exchange fetches) and by
4882
+ * `BacktestLogicPrivateService` (spin hand-off between parallel backtests).
4883
+ */
4643
4884
  const Candle = new CandleUtils();
4644
4885
 
4645
4886
  const MS_PER_MINUTE$7 = 60000;
@@ -19173,9 +19414,28 @@ const TICK_FN = async (self, symbol, when) => {
19173
19414
  return { type: "error", __error__: SYMBOL_FN_ERROR, reason: "TICK_FN", message: functoolsKit.getErrorMessage(error) };
19174
19415
  }
19175
19416
  };
19417
+ /**
19418
+ * Wraps `exchangeCoreService.getNextCandles` with error capture and a cooperative
19419
+ * event-loop hand-off after a successful fetch.
19420
+ *
19421
+ * Calls `Candle.spinLock(...)` on success: when multiple backtests run in parallel
19422
+ * (`Lookup.isParallel === true`), this yields the event loop so a peer waiting on
19423
+ * the candle-fetch mutex can take its turn, producing round-robin interleaving
19424
+ * instead of one backtest monopolizing the loop until completion. The spin is a
19425
+ * no-op for single-workload runs.
19426
+ *
19427
+ * @param self - Owning service instance, used for logging and exchange access.
19428
+ * @param symbol - Trading pair symbol.
19429
+ * @param candlesNeeded - Number of 1m candles to request.
19430
+ * @param bufferStartTime - Inclusive start time for the fetch window.
19431
+ * @param logMeta - Extra structured fields appended to the warn log on failure.
19432
+ * @returns Fetched candles, or a {@link TFnError} discriminated union on failure.
19433
+ */
19176
19434
  const GET_CANDLES_FN = async (self, symbol, candlesNeeded, bufferStartTime, logMeta) => {
19177
19435
  try {
19178
- return await self.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
19436
+ const result = await self.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
19437
+ await Candle.spinLock("BacktestLogicPrivateService GET_CANDLES_FN");
19438
+ return result;
19179
19439
  }
19180
19440
  catch (error) {
19181
19441
  console.error(`backtestLogicPrivateService getNextCandles failed symbol=${symbol} strategyName=${self.methodContextService.context.strategyName} exchangeName=${self.methodContextService.context.exchangeName}`);
@@ -20061,6 +20321,15 @@ class WalkerLogicPrivateService {
20061
20321
  exchangeName: context.exchangeName,
20062
20322
  frameName: context.frameName,
20063
20323
  });
20324
+ Lookup.addActivity({
20325
+ symbol,
20326
+ context: {
20327
+ strategyName,
20328
+ exchangeName: context.exchangeName,
20329
+ frameName: context.frameName,
20330
+ },
20331
+ backtest: true,
20332
+ });
20064
20333
  try {
20065
20334
  await functoolsKit.resolveDocuments(iterator);
20066
20335
  }
@@ -20076,6 +20345,17 @@ class WalkerLogicPrivateService {
20076
20345
  await CALL_STRATEGY_ERROR_CALLBACKS_FN(this, walkerSchema, strategyName, symbol, error);
20077
20346
  continue;
20078
20347
  }
20348
+ finally {
20349
+ Lookup.removeActivity({
20350
+ symbol,
20351
+ context: {
20352
+ strategyName,
20353
+ exchangeName: context.exchangeName,
20354
+ frameName: context.frameName,
20355
+ },
20356
+ backtest: true,
20357
+ });
20358
+ }
20079
20359
  this.loggerService.info("walkerLogicPrivateService backtest complete", {
20080
20360
  strategyName,
20081
20361
  symbol,
@@ -20152,6 +20432,110 @@ class WalkerLogicPrivateService {
20152
20432
  }
20153
20433
  }
20154
20434
 
20435
+ /**
20436
+ * Run iterator function for backtest logic.
20437
+ *
20438
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20439
+ * @param context - Execution context with strategy, exchange, and frame names
20440
+ * @param self - Instance of BacktestLogicPublicService
20441
+ * @returns Async iterator for backtest results
20442
+ */
20443
+ const RUN_ITERATOR_FN$1 = (self, symbol, context) => {
20444
+ return MethodContextService.runAsyncIterator(self.backtestLogicPrivateService.run(symbol), {
20445
+ exchangeName: context.exchangeName,
20446
+ strategyName: context.strategyName,
20447
+ frameName: context.frameName,
20448
+ });
20449
+ };
20450
+ /**
20451
+ * Call before start execution for backtest logic.
20452
+ * This function is responsible for triggering the beforeStartSubject
20453
+ * with the appropriate context and symbol information.
20454
+ */
20455
+ const CALL_BEFORE_START_FN$1 = functoolsKit.trycatch(async (self, symbol, context) => {
20456
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20457
+ const when = alignToInterval(startDate, "1m");
20458
+ await MethodContextService.runInContext(async () => {
20459
+ await ExecutionContextService.runInContext(async () => {
20460
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20461
+ await beforeStartSubject.next({
20462
+ symbol,
20463
+ exchangeName: context.exchangeName,
20464
+ strategyName: context.strategyName,
20465
+ frameName: context.frameName,
20466
+ backtest: true,
20467
+ when,
20468
+ timestamp: when.getTime(),
20469
+ currentPrice,
20470
+ });
20471
+ }, {
20472
+ symbol,
20473
+ when,
20474
+ backtest: true,
20475
+ });
20476
+ }, {
20477
+ exchangeName: context.exchangeName,
20478
+ strategyName: context.strategyName,
20479
+ frameName: context.frameName,
20480
+ });
20481
+ }, {
20482
+ fallback: (error, self) => {
20483
+ const message = "BacktestLogicPublicService CALL_BEFORE_START_FN thrown";
20484
+ const payload = {
20485
+ error: functoolsKit.errorData(error),
20486
+ message: functoolsKit.getErrorMessage(error),
20487
+ };
20488
+ self.loggerService.warn(message, payload);
20489
+ console.error(message, payload);
20490
+ errorEmitter.next(error);
20491
+ },
20492
+ });
20493
+ /**
20494
+ * Call after end execution for backtest logic.
20495
+ * This function is responsible for triggering the afterEndSubject
20496
+ * with the appropriate context and symbol information.
20497
+ */
20498
+ const CALL_AFTER_END_FN$1 = functoolsKit.trycatch(async (self, symbol, context) => {
20499
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20500
+ const timestamp = self.timeMetaService.hasTimestamp(symbol, context, true)
20501
+ ? await self.timeMetaService.getTimestamp(symbol, context, true)
20502
+ : startDate.getTime();
20503
+ const when = new Date(timestamp);
20504
+ await MethodContextService.runInContext(async () => {
20505
+ await ExecutionContextService.runInContext(async () => {
20506
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20507
+ await afterEndSubject.next({
20508
+ symbol,
20509
+ exchangeName: context.exchangeName,
20510
+ strategyName: context.strategyName,
20511
+ frameName: context.frameName,
20512
+ backtest: true,
20513
+ when,
20514
+ timestamp,
20515
+ currentPrice,
20516
+ });
20517
+ }, {
20518
+ symbol,
20519
+ when,
20520
+ backtest: true,
20521
+ });
20522
+ }, {
20523
+ exchangeName: context.exchangeName,
20524
+ strategyName: context.strategyName,
20525
+ frameName: context.frameName,
20526
+ });
20527
+ }, {
20528
+ fallback: (error, self) => {
20529
+ const message = "BacktestLogicPublicService CALL_AFTER_END_FN thrown";
20530
+ const payload = {
20531
+ error: functoolsKit.errorData(error),
20532
+ message: functoolsKit.getErrorMessage(error),
20533
+ };
20534
+ self.loggerService.warn(message, payload);
20535
+ console.error(message, payload);
20536
+ errorEmitter.next(error);
20537
+ },
20538
+ });
20155
20539
  /**
20156
20540
  * Public service for backtest orchestration with context management.
20157
20541
  *
@@ -20180,30 +20564,134 @@ class BacktestLogicPublicService {
20180
20564
  constructor() {
20181
20565
  this.loggerService = inject(TYPES.loggerService);
20182
20566
  this.backtestLogicPrivateService = inject(TYPES.backtestLogicPrivateService);
20183
- /**
20184
- * Runs backtest for a symbol with context propagation.
20185
- *
20186
- * Streams closed signals as async generator. Context is automatically
20187
- * injected into all framework functions called during iteration.
20188
- *
20189
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20190
- * @param context - Execution context with strategy, exchange, and frame names
20191
- * @returns Async generator yielding closed signals with PNL
20192
- */
20193
- this.run = (symbol, context) => {
20194
- this.loggerService.log("backtestLogicPublicService run", {
20567
+ this.timeMetaService = inject(TYPES.timeMetaService);
20568
+ this.frameSchemaService = inject(TYPES.frameSchemaService);
20569
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20570
+ }
20571
+ /**
20572
+ * Runs backtest for a symbol with context propagation.
20573
+ *
20574
+ * Streams closed signals as async generator. Context is automatically
20575
+ * injected into all framework functions called during iteration.
20576
+ *
20577
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20578
+ * @param context - Execution context with strategy, exchange, and frame names
20579
+ * @returns Async generator yielding closed signals with PNL
20580
+ */
20581
+ async *run(symbol, context) {
20582
+ this.loggerService.log("backtestLogicPublicService run", {
20583
+ symbol,
20584
+ context,
20585
+ });
20586
+ await CALL_BEFORE_START_FN$1(this, symbol, context);
20587
+ try {
20588
+ yield* RUN_ITERATOR_FN$1(this, symbol, context);
20589
+ }
20590
+ finally {
20591
+ await CALL_AFTER_END_FN$1(this, symbol, context);
20592
+ }
20593
+ }
20594
+ }
20595
+
20596
+ /**
20597
+ * Run iterator function for live logic.
20598
+ *
20599
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20600
+ * @param context - Execution context with strategy and exchange names
20601
+ * @param self - Instance of LiveLogicPublicService
20602
+ * @returns Async iterator for live trading results
20603
+ */
20604
+ const RUN_ITERATOR_FN = (self, symbol, context) => {
20605
+ return MethodContextService.runAsyncIterator(self.liveLogicPrivateService.run(symbol), {
20606
+ exchangeName: context.exchangeName,
20607
+ strategyName: context.strategyName,
20608
+ frameName: "",
20609
+ });
20610
+ };
20611
+ /**
20612
+ * Call before start execution for live logic.
20613
+ * This function is responsible for triggering the beforeStartSubject
20614
+ * with the appropriate context and symbol information.
20615
+ */
20616
+ const CALL_BEFORE_START_FN = functoolsKit.trycatch(async (self, symbol, context) => {
20617
+ const when = alignToInterval(new Date(), "1m");
20618
+ await MethodContextService.runInContext(async () => {
20619
+ await ExecutionContextService.runInContext(async () => {
20620
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20621
+ await beforeStartSubject.next({
20195
20622
  symbol,
20196
- context,
20623
+ exchangeName: context.exchangeName,
20624
+ strategyName: context.strategyName,
20625
+ frameName: "",
20626
+ backtest: false,
20627
+ currentPrice,
20628
+ when,
20629
+ timestamp: when.getTime(),
20197
20630
  });
20198
- return MethodContextService.runAsyncIterator(this.backtestLogicPrivateService.run(symbol), {
20631
+ }, {
20632
+ symbol,
20633
+ when,
20634
+ backtest: false,
20635
+ });
20636
+ }, {
20637
+ exchangeName: context.exchangeName,
20638
+ strategyName: context.strategyName,
20639
+ frameName: "",
20640
+ });
20641
+ }, {
20642
+ fallback: (error, self) => {
20643
+ const message = "LiveLogicPublicService CALL_BEFORE_START_FN thrown";
20644
+ const payload = {
20645
+ error: functoolsKit.errorData(error),
20646
+ message: functoolsKit.getErrorMessage(error),
20647
+ };
20648
+ self.loggerService.warn(message, payload);
20649
+ console.error(message, payload);
20650
+ errorEmitter.next(error);
20651
+ },
20652
+ });
20653
+ /**
20654
+ * Call after end execution for live logic.
20655
+ * This function is responsible for triggering the afterEndSubject
20656
+ * with the appropriate context and symbol information.
20657
+ */
20658
+ const CALL_AFTER_END_FN = functoolsKit.trycatch(async (self, symbol, context) => {
20659
+ const when = alignToInterval(new Date(), "1m");
20660
+ await MethodContextService.runInContext(async () => {
20661
+ await ExecutionContextService.runInContext(async () => {
20662
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20663
+ await afterEndSubject.next({
20664
+ symbol,
20199
20665
  exchangeName: context.exchangeName,
20200
20666
  strategyName: context.strategyName,
20201
- frameName: context.frameName,
20667
+ frameName: "",
20668
+ backtest: false,
20669
+ currentPrice,
20670
+ when,
20671
+ timestamp: when.getTime(),
20202
20672
  });
20673
+ }, {
20674
+ symbol,
20675
+ when,
20676
+ backtest: false,
20677
+ });
20678
+ }, {
20679
+ exchangeName: context.exchangeName,
20680
+ strategyName: context.strategyName,
20681
+ frameName: "",
20682
+ });
20683
+ }, {
20684
+ fallback: (error, self) => {
20685
+ const message = "LiveLogicPublicService CALL_AFTER_END_FN thrown";
20686
+ const payload = {
20687
+ error: functoolsKit.errorData(error),
20688
+ message: functoolsKit.getErrorMessage(error),
20203
20689
  };
20204
- }
20205
- }
20206
-
20690
+ self.loggerService.warn(message, payload);
20691
+ console.error(message, payload);
20692
+ errorEmitter.next(error);
20693
+ },
20694
+ });
20207
20695
  /**
20208
20696
  * Public service for live trading orchestration with context management.
20209
20697
  *
@@ -20239,28 +20727,31 @@ class LiveLogicPublicService {
20239
20727
  constructor() {
20240
20728
  this.loggerService = inject(TYPES.loggerService);
20241
20729
  this.liveLogicPrivateService = inject(TYPES.liveLogicPrivateService);
20242
- /**
20243
- * Runs live trading for a symbol with context propagation.
20244
- *
20245
- * Streams opened and closed signals as infinite async generator.
20246
- * Context is automatically injected into all framework functions.
20247
- * Process can crash and restart - state will be recovered from disk.
20248
- *
20249
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20250
- * @param context - Execution context with strategy and exchange names
20251
- * @returns Infinite async generator yielding opened and closed signals
20252
- */
20253
- this.run = (symbol, context) => {
20254
- this.loggerService.log("liveLogicPublicService run", {
20255
- symbol,
20256
- context,
20257
- });
20258
- return MethodContextService.runAsyncIterator(this.liveLogicPrivateService.run(symbol), {
20259
- exchangeName: context.exchangeName,
20260
- strategyName: context.strategyName,
20261
- frameName: "",
20262
- });
20263
- };
20730
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20731
+ }
20732
+ /**
20733
+ * Runs live trading for a symbol with context propagation.
20734
+ *
20735
+ * Streams opened and closed signals as infinite async generator.
20736
+ * Context is automatically injected into all framework functions.
20737
+ * Process can crash and restart - state will be recovered from disk.
20738
+ *
20739
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20740
+ * @param context - Execution context with strategy and exchange names
20741
+ * @returns Infinite async generator yielding opened and closed signals
20742
+ */
20743
+ async *run(symbol, context) {
20744
+ this.loggerService.log("liveLogicPublicService run", {
20745
+ symbol,
20746
+ context,
20747
+ });
20748
+ await CALL_BEFORE_START_FN(this, symbol, context);
20749
+ try {
20750
+ yield* RUN_ITERATOR_FN(this, symbol, context);
20751
+ }
20752
+ finally {
20753
+ await CALL_AFTER_END_FN(this, symbol, context);
20754
+ }
20264
20755
  }
20265
20756
  }
20266
20757
 
@@ -34464,6 +34955,21 @@ class TimeMetaService {
34464
34955
  * Instances are cached until clear() is called.
34465
34956
  */
34466
34957
  this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
34958
+ /**
34959
+ * Checks if a timestamp exists for the given symbol and context.
34960
+ *
34961
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
34962
+ * @param context - Strategy, exchange, and frame identifiers
34963
+ * @param backtest - True if backtest mode, false if live mode
34964
+ * @returns True if a timestamp is available, false otherwise
34965
+ */
34966
+ this.hasTimestamp = (symbol, context, backtest) => {
34967
+ const key = CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
34968
+ if (!this.getSource.has(key)) {
34969
+ return false;
34970
+ }
34971
+ return !!this.getSource.get(key)?.data;
34972
+ };
34467
34973
  /**
34468
34974
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
34469
34975
  *
@@ -36095,6 +36601,7 @@ const Exchange = new ExchangeUtils();
36095
36601
 
36096
36602
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
36097
36603
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
36604
+ const CACHE_CANDLES_METHOD_NAME = "cache.cacheCandles";
36098
36605
  const MS_PER_MINUTE$3 = 60000;
36099
36606
  const INTERVAL_MINUTES$3 = {
36100
36607
  "1m": 1,
@@ -36126,6 +36633,34 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
36126
36633
  process.stdout.write("\n");
36127
36634
  }
36128
36635
  };
36636
+ /**
36637
+ * Retry-wrapped pipeline: validates the cache via `checkCandles` and, on miss,
36638
+ * fills it via `warmCandles` and rethrows to trigger a retry pass that
36639
+ * re-validates the freshly cached range. Limited to 2 attempts.
36640
+ */
36641
+ const CACHE_CANDLES_FN = functoolsKit.retry(async (interval, dto, onWarmStart, onCheckStart) => {
36642
+ try {
36643
+ onCheckStart && onCheckStart(dto.symbol, interval, dto.from, dto.to);
36644
+ await checkCandles({
36645
+ exchangeName: dto.exchangeName,
36646
+ from: dto.from,
36647
+ to: dto.to,
36648
+ symbol: dto.symbol,
36649
+ interval: interval,
36650
+ });
36651
+ }
36652
+ catch (error) {
36653
+ onWarmStart && onWarmStart(dto.symbol, interval, dto.from, dto.to);
36654
+ await warmCandles({
36655
+ symbol: dto.symbol,
36656
+ exchangeName: dto.exchangeName,
36657
+ from: dto.from,
36658
+ to: dto.to,
36659
+ interval: interval,
36660
+ });
36661
+ throw error;
36662
+ }
36663
+ }, 2);
36129
36664
  /**
36130
36665
  * Checks cached candle presence via the persist adapter.
36131
36666
  * Issues one ranged read; adapter-side `hasValue` covers each expected timestamp,
@@ -36201,6 +36736,34 @@ async function warmCandles(params) {
36201
36736
  PRINT_PROGRESS_FN(fetched, totalCandles, symbol, interval);
36202
36737
  }
36203
36738
  }
36739
+ /**
36740
+ * Ensures candles for the given range are present in persist storage.
36741
+ * Runs a check-then-warm pipeline with one retry: validates the cache first
36742
+ * and, on a miss, downloads the missing data and re-validates.
36743
+ *
36744
+ * @param params - Combined cache parameters with optional lifecycle callbacks
36745
+ */
36746
+ async function cacheCandles({ symbol, interval, from, to, exchangeName, onCheckStart = (symbol, interval, from, to) => {
36747
+ process.stdout.write("\n");
36748
+ process.stdout.write(`Checking candles cache for ${symbol} ${interval} from ${from} to ${to}\n`);
36749
+ }, onWarmStart = (symbol, interval, from, to) => {
36750
+ process.stdout.write("\n\n");
36751
+ process.stdout.write(`Caching candles for ${symbol} ${interval} from ${from} to ${to}\n`);
36752
+ }, }) {
36753
+ backtest.loggerService.info(CACHE_CANDLES_METHOD_NAME, {
36754
+ symbol,
36755
+ exchangeName,
36756
+ interval,
36757
+ from,
36758
+ to,
36759
+ });
36760
+ await CACHE_CANDLES_FN(interval, {
36761
+ exchangeName,
36762
+ from,
36763
+ to,
36764
+ symbol,
36765
+ }, onWarmStart, onCheckStart);
36766
+ }
36204
36767
 
36205
36768
  const METHOD_NAME = "validate.validate";
36206
36769
  /**
@@ -36620,11 +37183,6 @@ const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
36620
37183
  const GET_CLOSE_PRICE_METHOD_NAME = "exchange.getClosePrice";
36621
37184
  const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
36622
37185
  const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
36623
- const GET_DATE_METHOD_NAME = "exchange.getDate";
36624
- const GET_TIMESTAMP_METHOD_NAME = "exchange.getTimestamp";
36625
- const GET_MODE_METHOD_NAME = "exchange.getMode";
36626
- const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
36627
- const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
36628
37186
  const HAS_TRADE_CONTEXT_METHOD_NAME = "exchange.hasTradeContext";
36629
37187
  const GET_ORDER_BOOK_METHOD_NAME = "exchange.getOrderBook";
36630
37188
  const GET_RAW_CANDLES_METHOD_NAME = "exchange.getRawCandles";
@@ -36791,113 +37349,6 @@ async function formatQuantity(symbol, quantity) {
36791
37349
  }
36792
37350
  return await backtest.exchangeConnectionService.formatQuantity(symbol, quantity);
36793
37351
  }
36794
- /**
36795
- * Gets the current date from execution context.
36796
- *
36797
- * In backtest mode: returns the current timeframe date being processed
36798
- * In live mode: returns current real-time date
36799
- *
36800
- * @returns Promise resolving to current execution context date
36801
- *
36802
- * @example
36803
- * ```typescript
36804
- * const date = await getDate();
36805
- * console.log(date); // 2024-01-01T12:00:00.000Z
36806
- * ```
36807
- */
36808
- async function getDate() {
36809
- backtest.loggerService.info(GET_DATE_METHOD_NAME);
36810
- if (!ExecutionContextService.hasContext()) {
36811
- throw new Error("getDate requires an execution context");
36812
- }
36813
- const { when } = backtest.executionContextService.context;
36814
- return new Date(when.getTime());
36815
- }
36816
- /**
36817
- * Gets the current timestamp from execution context.
36818
- *
36819
- * In backtest mode: returns the current timeframe timestamp being processed
36820
- * In live mode: returns current real-time timestamp
36821
- *
36822
- * @returns Promise resolving to current execution context timestamp in milliseconds
36823
- * @example
36824
- * ```typescript
36825
- * const timestamp = await getTimestamp();
36826
- * console.log(timestamp); // 1700000000000
36827
- * ```
36828
- */
36829
- async function getTimestamp() {
36830
- backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
36831
- if (!ExecutionContextService.hasContext()) {
36832
- throw new Error("getTimestamp requires an execution context");
36833
- }
36834
- return getContextTimestamp();
36835
- }
36836
- /**
36837
- * Gets the current execution mode.
36838
- *
36839
- * @returns Promise resolving to "backtest" or "live"
36840
- *
36841
- * @example
36842
- * ```typescript
36843
- * const mode = await getMode();
36844
- * if (mode === "backtest") {
36845
- * console.log("Running in backtest mode");
36846
- * } else {
36847
- * console.log("Running in live mode");
36848
- * }
36849
- * ```
36850
- */
36851
- async function getMode() {
36852
- backtest.loggerService.info(GET_MODE_METHOD_NAME);
36853
- if (!ExecutionContextService.hasContext()) {
36854
- throw new Error("getMode requires an execution context");
36855
- }
36856
- const { backtest: bt } = backtest.executionContextService.context;
36857
- return bt ? "backtest" : "live";
36858
- }
36859
- /**
36860
- * Gets the current trading symbol from execution context.
36861
- *
36862
- * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
36863
- * @throws Error if execution context is not active
36864
- *
36865
- * @example
36866
- * ```typescript
36867
- * const symbol = await getSymbol();
36868
- * console.log(symbol); // "BTCUSDT"
36869
- * ```
36870
- */
36871
- async function getSymbol() {
36872
- backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
36873
- if (!ExecutionContextService.hasContext()) {
36874
- throw new Error("getSymbol requires an execution context");
36875
- }
36876
- const { symbol } = backtest.executionContextService.context;
36877
- return symbol;
36878
- }
36879
- /**
36880
- * Gets the current method context.
36881
- *
36882
- * Returns the context object from the method context service, which contains
36883
- * information about the current method execution environment.
36884
- *
36885
- * @returns Promise resolving to the current method context object
36886
- * @throws Error if method context is not active
36887
- *
36888
- * @example
36889
- * ```typescript
36890
- * const context = await getContext();
36891
- * console.log(context); // { ...method context data... }
36892
- * ```
36893
- */
36894
- async function getContext() {
36895
- backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
36896
- if (!MethodContextService.hasContext()) {
36897
- throw new Error("getContext requires a method context");
36898
- }
36899
- return backtest.methodContextService.context;
36900
- }
36901
37352
  /**
36902
37353
  * Fetches order book for a trading pair from the registered exchange.
36903
37354
  *
@@ -40438,6 +40889,10 @@ const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
40438
40889
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
40439
40890
  const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
40440
40891
  const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
40892
+ const LISTEN_BEFORE_START_METHOD_NAME = "event.listenBeforeStart";
40893
+ const LISTEN_BEFORE_START_ONCE_METHOD_NAME = "event.listenBeforeStartOnce";
40894
+ const LISTEN_AFTER_END_METHOD_NAME = "event.listenAfterEnd";
40895
+ const LISTEN_AFTER_END_ONCE_METHOD_NAME = "event.listenAfterEndOnce";
40441
40896
  /**
40442
40897
  * Subscribes to all signal events with queued async processing.
40443
40898
  *
@@ -41908,6 +42363,68 @@ function listenSignalNotifyOnce(filterFn, fn) {
41908
42363
  };
41909
42364
  return disposeFn = listenSignalNotify(wrappedFn);
41910
42365
  }
42366
+ /**
42367
+ * Subscribes to before start events with queued async processing.
42368
+ * Emits when the engine is about to start a new strategy execution for a symbol.
42369
+ * Events are processed sequentially in order received, even if callback is async.
42370
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42371
+ * @param fn - Callback function to handle before start events
42372
+ * @return Unsubscribe function to stop listening to events
42373
+ */
42374
+ function listenBeforeStart(fn) {
42375
+ backtest.loggerService.log(LISTEN_BEFORE_START_METHOD_NAME);
42376
+ return beforeStartSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
42377
+ }
42378
+ /**
42379
+ * Subscribes to filtered before start events with one-time execution.
42380
+ * Listens for events matching the filter predicate, then executes callback once
42381
+ * and automatically unsubscribes.
42382
+ * @param filterFn - Predicate to filter which events trigger the callback
42383
+ * @param fn - Callback function to handle the filtered event (called only once)
42384
+ * @return Unsubscribe function to cancel the listener before it fires
42385
+ */
42386
+ function listenBeforeStartOnce(filterFn, fn) {
42387
+ backtest.loggerService.log(LISTEN_BEFORE_START_ONCE_METHOD_NAME);
42388
+ let disposeFn;
42389
+ const wrappedFn = async (event) => {
42390
+ if (filterFn(event)) {
42391
+ await fn(event);
42392
+ disposeFn && disposeFn();
42393
+ }
42394
+ };
42395
+ return disposeFn = listenBeforeStart(wrappedFn);
42396
+ }
42397
+ /**
42398
+ * Subscribes to after end events with queued async processing.
42399
+ * Emits when the engine has completed processing a strategy execution for a symbol.
42400
+ * Events are processed sequentially in order received, even if callback is async.
42401
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42402
+ * @param fn - Callback function to handle after end events
42403
+ * @return Unsubscribe function to stop listening to events
42404
+ */
42405
+ function listenAfterEnd(fn) {
42406
+ backtest.loggerService.log(LISTEN_AFTER_END_METHOD_NAME);
42407
+ return afterEndSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
42408
+ }
42409
+ /**
42410
+ * Subscribes to filtered after end events with one-time execution.
42411
+ * Listens for events matching the filter predicate, then executes callback once
42412
+ * and automatically unsubscribes.
42413
+ * @param filterFn - Predicate to filter which events trigger the callback
42414
+ * @param fn - Callback function to handle the filtered event (called only once)
42415
+ * @return Unsubscribe function to cancel the listener before it fires
42416
+ */
42417
+ function listenAfterEndOnce(filterFn, fn) {
42418
+ backtest.loggerService.log(LISTEN_AFTER_END_ONCE_METHOD_NAME);
42419
+ let disposeFn;
42420
+ const wrappedFn = async (event) => {
42421
+ if (filterFn(event)) {
42422
+ await fn(event);
42423
+ disposeFn && disposeFn();
42424
+ }
42425
+ };
42426
+ return disposeFn = listenAfterEnd(wrappedFn);
42427
+ }
41911
42428
 
41912
42429
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
41913
42430
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -41986,11 +42503,21 @@ const INSTANCE_TASK_FN$2 = async (symbol, context, self) => {
41986
42503
  self._isStopped = false;
41987
42504
  self._isDone = false;
41988
42505
  }
42506
+ Lookup.addActivity({
42507
+ symbol,
42508
+ context,
42509
+ backtest: true,
42510
+ });
41989
42511
  for await (const _ of self.run(symbol, context)) {
41990
42512
  if (self._isStopped) {
41991
42513
  break;
41992
42514
  }
41993
42515
  }
42516
+ Lookup.removeActivity({
42517
+ symbol,
42518
+ context,
42519
+ backtest: true,
42520
+ });
41994
42521
  if (!self._isDone) {
41995
42522
  await doneBacktestSubject.next({
41996
42523
  exchangeName: context.exchangeName,
@@ -44637,11 +45164,21 @@ const INSTANCE_TASK_FN$1 = async (symbol, context, self) => {
44637
45164
  self._isStopped = false;
44638
45165
  self._isDone = false;
44639
45166
  }
45167
+ Lookup.addActivity({
45168
+ symbol,
45169
+ context,
45170
+ backtest: false,
45171
+ });
44640
45172
  for await (const signal of self.run(symbol, context)) {
44641
45173
  if (signal?.action === "closed" && self._isStopped) {
44642
45174
  break;
44643
45175
  }
44644
45176
  }
45177
+ Lookup.removeActivity({
45178
+ symbol,
45179
+ context,
45180
+ backtest: false,
45181
+ });
44645
45182
  if (!self._isDone) {
44646
45183
  await doneLiveSubject.next({
44647
45184
  exchangeName: context.exchangeName,
@@ -48576,6 +49113,128 @@ async function listRiskSchema() {
48576
49113
  return await backtest.riskValidationService.list();
48577
49114
  }
48578
49115
 
49116
+ const GET_DATE_METHOD_NAME = "meta.getDate";
49117
+ const GET_TIMESTAMP_METHOD_NAME = "meta.getTimestamp";
49118
+ const GET_MODE_METHOD_NAME = "meta.getMode";
49119
+ const GET_SYMBOL_METHOD_NAME = "meta.getSymbol";
49120
+ const GET_CONTEXT_METHOD_NAME = "meta.getContext";
49121
+ /**
49122
+ * Gets the current date from execution context.
49123
+ *
49124
+ * In backtest mode: returns the current timeframe date being processed
49125
+ * In live mode: returns current real-time date
49126
+ *
49127
+ * @returns Promise resolving to current execution context date
49128
+ *
49129
+ * @example
49130
+ * ```typescript
49131
+ * const date = await getDate();
49132
+ * console.log(date); // 2024-01-01T12:00:00.000Z
49133
+ * ```
49134
+ */
49135
+ async function getDate() {
49136
+ backtest.loggerService.info(GET_DATE_METHOD_NAME);
49137
+ if (!ExecutionContextService.hasContext()) {
49138
+ throw new Error("getDate requires an execution context");
49139
+ }
49140
+ const { when } = backtest.executionContextService.context;
49141
+ return new Date(when.getTime());
49142
+ }
49143
+ /**
49144
+ * Gets the current timestamp from execution context.
49145
+ *
49146
+ * In backtest mode: returns the current timeframe timestamp being processed
49147
+ * In live mode: returns current real-time timestamp
49148
+ *
49149
+ * @returns Promise resolving to current execution context timestamp in milliseconds
49150
+ * @example
49151
+ * ```typescript
49152
+ * const timestamp = await getTimestamp();
49153
+ * console.log(timestamp); // 1700000000000
49154
+ * ```
49155
+ */
49156
+ async function getTimestamp() {
49157
+ backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
49158
+ if (!ExecutionContextService.hasContext()) {
49159
+ throw new Error("getTimestamp requires an execution context");
49160
+ }
49161
+ if (!MethodContextService.hasContext()) {
49162
+ throw new Error("getTimestamp requires a method context");
49163
+ }
49164
+ const { symbol, backtest: isBacktest } = backtest.executionContextService.context;
49165
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49166
+ return backtest.timeMetaService.getTimestamp(symbol, {
49167
+ exchangeName,
49168
+ frameName,
49169
+ strategyName,
49170
+ }, isBacktest);
49171
+ }
49172
+ /**
49173
+ * Gets the current execution mode.
49174
+ *
49175
+ * @returns Promise resolving to "backtest" or "live"
49176
+ *
49177
+ * @example
49178
+ * ```typescript
49179
+ * const mode = await getMode();
49180
+ * if (mode === "backtest") {
49181
+ * console.log("Running in backtest mode");
49182
+ * } else {
49183
+ * console.log("Running in live mode");
49184
+ * }
49185
+ * ```
49186
+ */
49187
+ async function getMode() {
49188
+ backtest.loggerService.info(GET_MODE_METHOD_NAME);
49189
+ if (!ExecutionContextService.hasContext()) {
49190
+ throw new Error("getMode requires an execution context");
49191
+ }
49192
+ const { backtest: bt } = backtest.executionContextService.context;
49193
+ return bt ? "backtest" : "live";
49194
+ }
49195
+ /**
49196
+ * Gets the current trading symbol from execution context.
49197
+ *
49198
+ * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
49199
+ * @throws Error if execution context is not active
49200
+ *
49201
+ * @example
49202
+ * ```typescript
49203
+ * const symbol = await getSymbol();
49204
+ * console.log(symbol); // "BTCUSDT"
49205
+ * ```
49206
+ */
49207
+ async function getSymbol() {
49208
+ backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
49209
+ if (!ExecutionContextService.hasContext()) {
49210
+ throw new Error("getSymbol requires an execution context");
49211
+ }
49212
+ const { symbol } = backtest.executionContextService.context;
49213
+ return symbol;
49214
+ }
49215
+ /**
49216
+ * Gets the current method context.
49217
+ *
49218
+ * Returns the context object from the method context service, which contains
49219
+ * information about the current method execution environment.
49220
+ *
49221
+ * @returns Promise resolving to the current method context object
49222
+ * @throws Error if method context is not active
49223
+ *
49224
+ * @example
49225
+ * ```typescript
49226
+ * const context = await getContext();
49227
+ * console.log(context); // { ...method context data... }
49228
+ * ```
49229
+ */
49230
+ async function getContext() {
49231
+ backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
49232
+ if (!MethodContextService.hasContext()) {
49233
+ throw new Error("getContext requires a method context");
49234
+ }
49235
+ return backtest.methodContextService.context;
49236
+ }
49237
+
48579
49238
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
48580
49239
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
48581
49240
  const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
@@ -54620,6 +55279,8 @@ const SUBJECT_ISOLATION_LIST = [
54620
55279
  syncSubject,
54621
55280
  validationSubject,
54622
55281
  signalNotifySubject,
55282
+ beforeStartSubject,
55283
+ afterEndSubject
54623
55284
  ];
54624
55285
  /**
54625
55286
  * Creates a snapshot function for a given subject by clearing its internal
@@ -63767,6 +64428,7 @@ exports.HighestProfit = HighestProfit;
63767
64428
  exports.Interval = Interval;
63768
64429
  exports.Live = Live;
63769
64430
  exports.Log = Log;
64431
+ exports.Lookup = Lookup;
63770
64432
  exports.Markdown = Markdown;
63771
64433
  exports.MarkdownFileBase = MarkdownFileBase;
63772
64434
  exports.MarkdownFolderBase = MarkdownFolderBase;
@@ -63848,6 +64510,7 @@ exports.addSizingSchema = addSizingSchema;
63848
64510
  exports.addStrategySchema = addStrategySchema;
63849
64511
  exports.addWalkerSchema = addWalkerSchema;
63850
64512
  exports.alignToInterval = alignToInterval;
64513
+ exports.cacheCandles = cacheCandles;
63851
64514
  exports.checkCandles = checkCandles;
63852
64515
  exports.commitActivateScheduled = commitActivateScheduled;
63853
64516
  exports.commitAverageBuy = commitAverageBuy;
@@ -63956,7 +64619,11 @@ exports.listStrategySchema = listStrategySchema;
63956
64619
  exports.listWalkerSchema = listWalkerSchema;
63957
64620
  exports.listenActivePing = listenActivePing;
63958
64621
  exports.listenActivePingOnce = listenActivePingOnce;
64622
+ exports.listenAfterEnd = listenAfterEnd;
64623
+ exports.listenAfterEndOnce = listenAfterEndOnce;
63959
64624
  exports.listenBacktestProgress = listenBacktestProgress;
64625
+ exports.listenBeforeStart = listenBeforeStart;
64626
+ exports.listenBeforeStartOnce = listenBeforeStartOnce;
63960
64627
  exports.listenBreakevenAvailable = listenBreakevenAvailable;
63961
64628
  exports.listenBreakevenAvailableOnce = listenBreakevenAvailableOnce;
63962
64629
  exports.listenDoneBacktest = listenDoneBacktest;