backtest-kit 6.8.1 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -888,7 +888,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
888
888
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
889
889
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
890
890
  // Calculate step in milliseconds for candle close time validation
891
- const INTERVAL_MINUTES$8 = {
891
+ const INTERVAL_MINUTES$9 = {
892
892
  "1m": 1,
893
893
  "3m": 3,
894
894
  "5m": 5,
@@ -902,7 +902,7 @@ const INTERVAL_MINUTES$8 = {
902
902
  "1d": 1440,
903
903
  "1w": 10080,
904
904
  };
905
- const MS_PER_MINUTE$6 = 60000;
905
+ const MS_PER_MINUTE$7 = 60000;
906
906
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
907
907
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
908
908
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -961,8 +961,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
961
961
  const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
962
962
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
963
963
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
964
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
965
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
964
966
  const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
965
967
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
968
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
969
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
970
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
971
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
972
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
973
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
974
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
975
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
966
976
  const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
967
977
  const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
968
978
  const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
@@ -1875,7 +1885,7 @@ class PersistCandleUtils {
1875
1885
  const isInitial = !this.getCandlesStorage.has(key);
1876
1886
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1877
1887
  await stateStorage.waitForInit(isInitial);
1878
- const stepMs = INTERVAL_MINUTES$8[interval] * MS_PER_MINUTE$6;
1888
+ const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
1879
1889
  // Calculate expected timestamps and fetch each candle directly
1880
1890
  const cachedCandles = [];
1881
1891
  for (let i = 0; i < limit; i++) {
@@ -1931,7 +1941,7 @@ class PersistCandleUtils {
1931
1941
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1932
1942
  await stateStorage.waitForInit(isInitial);
1933
1943
  // Calculate step in milliseconds to determine candle close time
1934
- const stepMs = INTERVAL_MINUTES$8[interval] * MS_PER_MINUTE$6;
1944
+ const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
1935
1945
  const now = Date.now();
1936
1946
  // Write each candle as a separate file, skipping incomplete candles
1937
1947
  for (const candle of candles) {
@@ -2357,7 +2367,8 @@ class PersistMeasureUtils {
2357
2367
  const stateStorage = this.getMeasureStorage(bucket);
2358
2368
  await stateStorage.waitForInit(isInitial);
2359
2369
  if (await stateStorage.hasValue(key)) {
2360
- return await stateStorage.readValue(key);
2370
+ const data = await stateStorage.readValue(key);
2371
+ return data.removed ? null : data;
2361
2372
  }
2362
2373
  return null;
2363
2374
  };
@@ -2379,6 +2390,27 @@ class PersistMeasureUtils {
2379
2390
  await stateStorage.waitForInit(isInitial);
2380
2391
  await stateStorage.writeValue(key, data);
2381
2392
  };
2393
+ /**
2394
+ * Marks a cached entry as removed (soft delete — file is kept on disk).
2395
+ * After this call `readMeasureData` for the same key returns `null`.
2396
+ *
2397
+ * @param bucket - Storage bucket
2398
+ * @param key - Dynamic cache key within the bucket
2399
+ * @returns Promise that resolves when removal is complete
2400
+ */
2401
+ this.removeMeasureData = async (bucket, key) => {
2402
+ LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
2403
+ bucket,
2404
+ key,
2405
+ });
2406
+ const isInitial = !this.getMeasureStorage.has(bucket);
2407
+ const stateStorage = this.getMeasureStorage(bucket);
2408
+ await stateStorage.waitForInit(isInitial);
2409
+ const data = await stateStorage.readValue(key);
2410
+ if (data) {
2411
+ await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
2412
+ }
2413
+ };
2382
2414
  }
2383
2415
  /**
2384
2416
  * Registers a custom persistence adapter.
@@ -2389,6 +2421,27 @@ class PersistMeasureUtils {
2389
2421
  LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
2390
2422
  this.PersistMeasureFactory = Ctor;
2391
2423
  }
2424
+ /**
2425
+ * Async generator yielding all non-removed entity keys for a given bucket.
2426
+ * Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
2427
+ *
2428
+ * @param bucket - Storage bucket
2429
+ * @returns AsyncGenerator yielding entity keys
2430
+ */
2431
+ async *listMeasureData(bucket) {
2432
+ LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
2433
+ const isInitial = !this.getMeasureStorage.has(bucket);
2434
+ const stateStorage = this.getMeasureStorage(bucket);
2435
+ await stateStorage.waitForInit(isInitial);
2436
+ for await (const key of stateStorage.keys()) {
2437
+ const data = await stateStorage.readValue(String(key));
2438
+ if (data === null || data.removed) {
2439
+ continue;
2440
+ }
2441
+ yield String(key);
2442
+ }
2443
+ }
2444
+ ;
2392
2445
  /**
2393
2446
  * Clears the memoized storage cache.
2394
2447
  * Call this when process.cwd() changes between strategy iterations
@@ -2418,6 +2471,140 @@ class PersistMeasureUtils {
2418
2471
  * Used by Cache.file for persistent caching of external API responses.
2419
2472
  */
2420
2473
  const PersistMeasureAdapter = new PersistMeasureUtils();
2474
+ /**
2475
+ * Persistence layer for Interval.file once-per-interval signal firing.
2476
+ *
2477
+ * Stores fired-interval markers under `./dump/data/interval/`.
2478
+ * A record's presence means the interval has already fired for that bucket+key;
2479
+ * absence means the function has not yet fired (or returned null last time).
2480
+ */
2481
+ class PersistIntervalUtils {
2482
+ constructor() {
2483
+ this.PersistIntervalFactory = PersistBase;
2484
+ this.getIntervalStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
2485
+ bucket,
2486
+ `./dump/data/interval/`,
2487
+ ]));
2488
+ /**
2489
+ * Reads interval data for a given bucket and key.
2490
+ *
2491
+ * @param bucket - Storage bucket (instance name + interval + index)
2492
+ * @param key - Entity key within the bucket (symbol + aligned timestamp)
2493
+ * @returns Promise resolving to stored value or null if not found
2494
+ */
2495
+ this.readIntervalData = async (bucket, key) => {
2496
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
2497
+ bucket,
2498
+ key,
2499
+ });
2500
+ const isInitial = !this.getIntervalStorage.has(bucket);
2501
+ const stateStorage = this.getIntervalStorage(bucket);
2502
+ await stateStorage.waitForInit(isInitial);
2503
+ if (await stateStorage.hasValue(key)) {
2504
+ const data = await stateStorage.readValue(key);
2505
+ return data.removed ? null : data;
2506
+ }
2507
+ return null;
2508
+ };
2509
+ /**
2510
+ * Writes interval data to disk.
2511
+ *
2512
+ * @param data - Data to store
2513
+ * @param bucket - Storage bucket
2514
+ * @param key - Entity key within the bucket
2515
+ * @returns Promise that resolves when write is complete
2516
+ */
2517
+ this.writeIntervalData = async (data, bucket, key) => {
2518
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
2519
+ bucket,
2520
+ key,
2521
+ });
2522
+ const isInitial = !this.getIntervalStorage.has(bucket);
2523
+ const stateStorage = this.getIntervalStorage(bucket);
2524
+ await stateStorage.waitForInit(isInitial);
2525
+ await stateStorage.writeValue(key, data);
2526
+ };
2527
+ /**
2528
+ * Marks an interval entry as removed (soft delete — file is kept on disk).
2529
+ * After this call `readIntervalData` for the same key returns `null`,
2530
+ * so the function will fire again on the next `IntervalFileInstance.run` call.
2531
+ *
2532
+ * @param bucket - Storage bucket
2533
+ * @param key - Entity key within the bucket
2534
+ * @returns Promise that resolves when removal is complete
2535
+ */
2536
+ this.removeIntervalData = async (bucket, key) => {
2537
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
2538
+ bucket,
2539
+ key,
2540
+ });
2541
+ const isInitial = !this.getIntervalStorage.has(bucket);
2542
+ const stateStorage = this.getIntervalStorage(bucket);
2543
+ await stateStorage.waitForInit(isInitial);
2544
+ const data = await stateStorage.readValue(key);
2545
+ if (data) {
2546
+ await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
2547
+ }
2548
+ };
2549
+ }
2550
+ /**
2551
+ * Registers a custom persistence adapter.
2552
+ *
2553
+ * @param Ctor - Custom PersistBase constructor
2554
+ */
2555
+ usePersistIntervalAdapter(Ctor) {
2556
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
2557
+ this.PersistIntervalFactory = Ctor;
2558
+ }
2559
+ /**
2560
+ * Async generator yielding all non-removed entity keys for a given bucket.
2561
+ * Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
2562
+ *
2563
+ * @param bucket - Storage bucket
2564
+ * @returns AsyncGenerator yielding entity keys
2565
+ */
2566
+ async *listIntervalData(bucket) {
2567
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
2568
+ const isInitial = !this.getIntervalStorage.has(bucket);
2569
+ const stateStorage = this.getIntervalStorage(bucket);
2570
+ await stateStorage.waitForInit(isInitial);
2571
+ for await (const key of stateStorage.keys()) {
2572
+ const data = await stateStorage.readValue(String(key));
2573
+ if (data === null || data.removed) {
2574
+ continue;
2575
+ }
2576
+ yield String(key);
2577
+ }
2578
+ }
2579
+ ;
2580
+ /**
2581
+ * Clears the memoized storage cache.
2582
+ * Call this when process.cwd() changes between strategy iterations.
2583
+ */
2584
+ clear() {
2585
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
2586
+ this.getIntervalStorage.clear();
2587
+ }
2588
+ /**
2589
+ * Switches to the default JSON persist adapter.
2590
+ */
2591
+ useJson() {
2592
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
2593
+ this.usePersistIntervalAdapter(PersistBase);
2594
+ }
2595
+ /**
2596
+ * Switches to a dummy persist adapter that discards all writes.
2597
+ */
2598
+ useDummy() {
2599
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
2600
+ this.usePersistIntervalAdapter(PersistDummy);
2601
+ }
2602
+ }
2603
+ /**
2604
+ * Global singleton instance of PersistIntervalUtils.
2605
+ * Used by Interval.file for persistent once-per-interval signal firing.
2606
+ */
2607
+ const PersistIntervalAdapter = new PersistIntervalUtils();
2421
2608
  /**
2422
2609
  * Utility class for managing memory entry persistence.
2423
2610
  *
@@ -2765,8 +2952,8 @@ class CandleUtils {
2765
2952
  }
2766
2953
  const Candle = new CandleUtils();
2767
2954
 
2768
- const MS_PER_MINUTE$5 = 60000;
2769
- const INTERVAL_MINUTES$7 = {
2955
+ const MS_PER_MINUTE$6 = 60000;
2956
+ const INTERVAL_MINUTES$8 = {
2770
2957
  "1m": 1,
2771
2958
  "3m": 3,
2772
2959
  "5m": 5,
@@ -2798,7 +2985,7 @@ const INTERVAL_MINUTES$7 = {
2798
2985
  * @returns Aligned timestamp rounded down to interval boundary
2799
2986
  */
2800
2987
  const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
2801
- const intervalMs = intervalMinutes * MS_PER_MINUTE$5;
2988
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
2802
2989
  return Math.floor(timestamp / intervalMs) * intervalMs;
2803
2990
  };
2804
2991
  /**
@@ -2952,9 +3139,9 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
2952
3139
  * @returns Promise resolving to array of candle data
2953
3140
  */
2954
3141
  const GET_CANDLES_FN$1 = async (dto, since, self) => {
2955
- const step = INTERVAL_MINUTES$7[dto.interval];
3142
+ const step = INTERVAL_MINUTES$8[dto.interval];
2956
3143
  const sinceTimestamp = since.getTime();
2957
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$5;
3144
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
2958
3145
  await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
2959
3146
  try {
2960
3147
  // Try to read from cache first
@@ -3068,11 +3255,11 @@ class ClientExchange {
3068
3255
  interval,
3069
3256
  limit,
3070
3257
  });
3071
- const step = INTERVAL_MINUTES$7[interval];
3258
+ const step = INTERVAL_MINUTES$8[interval];
3072
3259
  if (!step) {
3073
3260
  throw new Error(`ClientExchange unknown interval=${interval}`);
3074
3261
  }
3075
- const stepMs = step * MS_PER_MINUTE$5;
3262
+ const stepMs = step * MS_PER_MINUTE$6;
3076
3263
  // Align when down to interval boundary
3077
3264
  const whenTimestamp = this.params.execution.context.when.getTime();
3078
3265
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
@@ -3149,11 +3336,11 @@ class ClientExchange {
3149
3336
  if (!this.params.execution.context.backtest) {
3150
3337
  throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
3151
3338
  }
3152
- const step = INTERVAL_MINUTES$7[interval];
3339
+ const step = INTERVAL_MINUTES$8[interval];
3153
3340
  if (!step) {
3154
3341
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
3155
3342
  }
3156
- const stepMs = step * MS_PER_MINUTE$5;
3343
+ const stepMs = step * MS_PER_MINUTE$6;
3157
3344
  const now = Date.now();
3158
3345
  // Align when down to interval boundary
3159
3346
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -3317,11 +3504,11 @@ class ClientExchange {
3317
3504
  sDate,
3318
3505
  eDate,
3319
3506
  });
3320
- const step = INTERVAL_MINUTES$7[interval];
3507
+ const step = INTERVAL_MINUTES$8[interval];
3321
3508
  if (!step) {
3322
3509
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
3323
3510
  }
3324
- const stepMs = step * MS_PER_MINUTE$5;
3511
+ const stepMs = step * MS_PER_MINUTE$6;
3325
3512
  const whenTimestamp = this.params.execution.context.when.getTime();
3326
3513
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
3327
3514
  let sinceTimestamp;
@@ -3450,7 +3637,7 @@ class ClientExchange {
3450
3637
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
3451
3638
  const to = new Date(alignedTo);
3452
3639
  const from = new Date(alignedTo -
3453
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$5);
3640
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
3454
3641
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
3455
3642
  }
3456
3643
  /**
@@ -3479,7 +3666,7 @@ class ClientExchange {
3479
3666
  const whenTimestamp = this.params.execution.context.when.getTime();
3480
3667
  // Align to 1-minute boundary to prevent look-ahead bias
3481
3668
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
3482
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$5 - MS_PER_MINUTE$5;
3669
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
3483
3670
  // No limit: fetch a single window and return as-is
3484
3671
  if (limit === undefined) {
3485
3672
  const to = new Date(alignedTo);
@@ -4458,7 +4645,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
4458
4645
  }
4459
4646
  };
4460
4647
 
4461
- const INTERVAL_MINUTES$6 = {
4648
+ const INTERVAL_MINUTES$7 = {
4462
4649
  "1m": 1,
4463
4650
  "3m": 3,
4464
4651
  "5m": 5,
@@ -4842,7 +5029,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4842
5029
  }
4843
5030
  const currentTime = self.params.execution.context.when.getTime();
4844
5031
  {
4845
- const intervalMinutes = INTERVAL_MINUTES$6[self.params.interval];
5032
+ const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
4846
5033
  const intervalMs = intervalMinutes * 60 * 1000;
4847
5034
  const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
4848
5035
  // Проверяем что наступил новый интервал (по aligned timestamp)
@@ -9866,7 +10053,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
9866
10053
  * @param backtest - Whether running in backtest mode
9867
10054
  * @returns Unique string key for memoization
9868
10055
  */
9869
- const CREATE_KEY_FN$s = (symbol, strategyName, exchangeName, frameName, backtest) => {
10056
+ const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
9870
10057
  const parts = [symbol, strategyName, exchangeName];
9871
10058
  if (frameName)
9872
10059
  parts.push(frameName);
@@ -10133,7 +10320,7 @@ class StrategyConnectionService {
10133
10320
  * @param backtest - Whether running in backtest mode
10134
10321
  * @returns Configured ClientStrategy instance
10135
10322
  */
10136
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10323
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10137
10324
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10138
10325
  return new ClientStrategy({
10139
10326
  symbol,
@@ -10928,7 +11115,7 @@ class StrategyConnectionService {
10928
11115
  }
10929
11116
  return;
10930
11117
  }
10931
- const key = CREATE_KEY_FN$s(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11118
+ const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
10932
11119
  if (!this.getStrategy.has(key)) {
10933
11120
  return;
10934
11121
  }
@@ -11330,7 +11517,7 @@ class StrategyConnectionService {
11330
11517
  * Maps FrameInterval to minutes for timestamp calculation.
11331
11518
  * Used to generate timeframe arrays with proper spacing.
11332
11519
  */
11333
- const INTERVAL_MINUTES$5 = {
11520
+ const INTERVAL_MINUTES$6 = {
11334
11521
  "1m": 1,
11335
11522
  "3m": 3,
11336
11523
  "5m": 5,
@@ -11386,7 +11573,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
11386
11573
  symbol,
11387
11574
  });
11388
11575
  const { interval, startDate, endDate } = self.params;
11389
- const intervalMinutes = INTERVAL_MINUTES$5[interval];
11576
+ const intervalMinutes = INTERVAL_MINUTES$6[interval];
11390
11577
  if (!intervalMinutes) {
11391
11578
  throw new Error(`ClientFrame unknown interval: ${interval}`);
11392
11579
  }
@@ -11751,8 +11938,8 @@ const get = (object, path) => {
11751
11938
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
11752
11939
  };
11753
11940
 
11754
- const MS_PER_MINUTE$4 = 60000;
11755
- const INTERVAL_MINUTES$4 = {
11941
+ const MS_PER_MINUTE$5 = 60000;
11942
+ const INTERVAL_MINUTES$5 = {
11756
11943
  "1m": 1,
11757
11944
  "3m": 3,
11758
11945
  "5m": 5,
@@ -11784,11 +11971,11 @@ const INTERVAL_MINUTES$4 = {
11784
11971
  * @returns New Date aligned down to interval boundary
11785
11972
  */
11786
11973
  const alignToInterval = (date, interval) => {
11787
- const minutes = INTERVAL_MINUTES$4[interval];
11974
+ const minutes = INTERVAL_MINUTES$5[interval];
11788
11975
  if (minutes === undefined) {
11789
11976
  throw new Error(`alignToInterval: unknown interval=${interval}`);
11790
11977
  }
11791
- const intervalMs = minutes * MS_PER_MINUTE$4;
11978
+ const intervalMs = minutes * MS_PER_MINUTE$5;
11792
11979
  return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
11793
11980
  };
11794
11981
 
@@ -12097,7 +12284,7 @@ class ClientRisk {
12097
12284
  * @param backtest - Whether running in backtest mode
12098
12285
  * @returns Unique string key for memoization
12099
12286
  */
12100
- const CREATE_KEY_FN$r = (riskName, exchangeName, frameName, backtest) => {
12287
+ const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12101
12288
  const parts = [riskName, exchangeName];
12102
12289
  if (frameName)
12103
12290
  parts.push(frameName);
@@ -12197,7 +12384,7 @@ class RiskConnectionService {
12197
12384
  * @param backtest - True if backtest mode, false if live mode
12198
12385
  * @returns Configured ClientRisk instance
12199
12386
  */
12200
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12387
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12201
12388
  const schema = this.riskSchemaService.get(riskName);
12202
12389
  return new ClientRisk({
12203
12390
  ...schema,
@@ -12266,7 +12453,7 @@ class RiskConnectionService {
12266
12453
  payload,
12267
12454
  });
12268
12455
  if (payload) {
12269
- const key = CREATE_KEY_FN$r(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12456
+ const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12270
12457
  this.getRisk.clear(key);
12271
12458
  }
12272
12459
  else {
@@ -13310,7 +13497,7 @@ class ClientAction {
13310
13497
  * @param backtest - Whether running in backtest mode
13311
13498
  * @returns Unique string key for memoization
13312
13499
  */
13313
- const CREATE_KEY_FN$q = (actionName, strategyName, exchangeName, frameName, backtest) => {
13500
+ const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13314
13501
  const parts = [actionName, strategyName, exchangeName];
13315
13502
  if (frameName)
13316
13503
  parts.push(frameName);
@@ -13362,7 +13549,7 @@ class ActionConnectionService {
13362
13549
  * @param backtest - True if backtest mode, false if live mode
13363
13550
  * @returns Configured ClientAction instance
13364
13551
  */
13365
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$q(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13552
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13366
13553
  const schema = this.actionSchemaService.get(actionName);
13367
13554
  return new ClientAction({
13368
13555
  ...schema,
@@ -13573,7 +13760,7 @@ class ActionConnectionService {
13573
13760
  await Promise.all(actions.map(async (action) => await action.dispose()));
13574
13761
  return;
13575
13762
  }
13576
- const key = CREATE_KEY_FN$q(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13763
+ const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13577
13764
  if (!this.getAction.has(key)) {
13578
13765
  return;
13579
13766
  }
@@ -13591,7 +13778,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
13591
13778
  * @param exchangeName - Exchange name
13592
13779
  * @returns Unique string key for memoization
13593
13780
  */
13594
- const CREATE_KEY_FN$p = (exchangeName) => {
13781
+ const CREATE_KEY_FN$q = (exchangeName) => {
13595
13782
  return exchangeName;
13596
13783
  };
13597
13784
  /**
@@ -13615,7 +13802,7 @@ class ExchangeCoreService {
13615
13802
  * @param exchangeName - Name of the exchange to validate
13616
13803
  * @returns Promise that resolves when validation is complete
13617
13804
  */
13618
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$p(exchangeName), async (exchangeName) => {
13805
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
13619
13806
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
13620
13807
  exchangeName,
13621
13808
  });
@@ -13867,7 +14054,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
13867
14054
  * @param context - Execution context with strategyName, exchangeName, frameName
13868
14055
  * @returns Unique string key for memoization
13869
14056
  */
13870
- const CREATE_KEY_FN$o = (context) => {
14057
+ const CREATE_KEY_FN$p = (context) => {
13871
14058
  const parts = [context.strategyName, context.exchangeName];
13872
14059
  if (context.frameName)
13873
14060
  parts.push(context.frameName);
@@ -13899,7 +14086,7 @@ class StrategyCoreService {
13899
14086
  * @param context - Execution context with strategyName, exchangeName, frameName
13900
14087
  * @returns Promise that resolves when validation is complete
13901
14088
  */
13902
- this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
14089
+ this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
13903
14090
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
13904
14091
  context,
13905
14092
  });
@@ -15123,7 +15310,7 @@ class SizingGlobalService {
15123
15310
  * @param context - Context with riskName, exchangeName, frameName
15124
15311
  * @returns Unique string key for memoization
15125
15312
  */
15126
- const CREATE_KEY_FN$n = (context) => {
15313
+ const CREATE_KEY_FN$o = (context) => {
15127
15314
  const parts = [context.riskName, context.exchangeName];
15128
15315
  if (context.frameName)
15129
15316
  parts.push(context.frameName);
@@ -15149,7 +15336,7 @@ class RiskGlobalService {
15149
15336
  * @param payload - Payload with riskName, exchangeName and frameName
15150
15337
  * @returns Promise that resolves when validation is complete
15151
15338
  */
15152
- this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15339
+ this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15153
15340
  this.loggerService.log("riskGlobalService validate", {
15154
15341
  context,
15155
15342
  });
@@ -15227,7 +15414,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
15227
15414
  * @param context - Execution context with strategyName, exchangeName, frameName
15228
15415
  * @returns Unique string key for memoization
15229
15416
  */
15230
- const CREATE_KEY_FN$m = (context) => {
15417
+ const CREATE_KEY_FN$n = (context) => {
15231
15418
  const parts = [context.strategyName, context.exchangeName];
15232
15419
  if (context.frameName)
15233
15420
  parts.push(context.frameName);
@@ -15271,7 +15458,7 @@ class ActionCoreService {
15271
15458
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15272
15459
  * @returns Promise that resolves when all validations complete
15273
15460
  */
15274
- this.validate = memoize(([context]) => CREATE_KEY_FN$m(context), async (context) => {
15461
+ this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15275
15462
  this.loggerService.log(METHOD_NAME_VALIDATE, {
15276
15463
  context,
15277
15464
  });
@@ -16866,8 +17053,8 @@ class BacktestLogicPrivateService {
16866
17053
  }
16867
17054
 
16868
17055
  const EMITTER_CHECK_INTERVAL = 5000;
16869
- const MS_PER_MINUTE$3 = 60000;
16870
- const INTERVAL_MINUTES$3 = {
17056
+ const MS_PER_MINUTE$4 = 60000;
17057
+ const INTERVAL_MINUTES$4 = {
16871
17058
  "1m": 1,
16872
17059
  "3m": 3,
16873
17060
  "5m": 5,
@@ -16883,7 +17070,7 @@ const INTERVAL_MINUTES$3 = {
16883
17070
  };
16884
17071
  const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
16885
17072
  const tickSubject = new Subject();
16886
- const intervalMs = INTERVAL_MINUTES$3[interval] * MS_PER_MINUTE$3;
17073
+ const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
16887
17074
  {
16888
17075
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
16889
17076
  Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -20267,7 +20454,7 @@ const ReportWriter = new ReportWriterAdapter();
20267
20454
  * @param backtest - Whether running in backtest mode
20268
20455
  * @returns Unique string key for memoization
20269
20456
  */
20270
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
20457
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20271
20458
  const parts = [symbol, strategyName, exchangeName];
20272
20459
  if (frameName)
20273
20460
  parts.push(frameName);
@@ -20513,7 +20700,7 @@ class BacktestMarkdownService {
20513
20700
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20514
20701
  * Each combination gets its own isolated storage instance.
20515
20702
  */
20516
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
20703
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
20517
20704
  /**
20518
20705
  * Processes tick events and accumulates closed signals.
20519
20706
  * Should be called from IStrategyCallbacks.onTick.
@@ -20670,7 +20857,7 @@ class BacktestMarkdownService {
20670
20857
  payload,
20671
20858
  });
20672
20859
  if (payload) {
20673
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20860
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20674
20861
  this.getStorage.clear(key);
20675
20862
  }
20676
20863
  else {
@@ -20732,7 +20919,7 @@ class BacktestMarkdownService {
20732
20919
  * @param backtest - Whether running in backtest mode
20733
20920
  * @returns Unique string key for memoization
20734
20921
  */
20735
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
20922
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
20736
20923
  const parts = [symbol, strategyName, exchangeName];
20737
20924
  if (frameName)
20738
20925
  parts.push(frameName);
@@ -21227,7 +21414,7 @@ class LiveMarkdownService {
21227
21414
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21228
21415
  * Each combination gets its own isolated storage instance.
21229
21416
  */
21230
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21417
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21231
21418
  /**
21232
21419
  * Subscribes to live signal emitter to receive tick events.
21233
21420
  * Protected against multiple subscriptions.
@@ -21445,7 +21632,7 @@ class LiveMarkdownService {
21445
21632
  payload,
21446
21633
  });
21447
21634
  if (payload) {
21448
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21635
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21449
21636
  this.getStorage.clear(key);
21450
21637
  }
21451
21638
  else {
@@ -21465,7 +21652,7 @@ class LiveMarkdownService {
21465
21652
  * @param backtest - Whether running in backtest mode
21466
21653
  * @returns Unique string key for memoization
21467
21654
  */
21468
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
21655
+ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
21469
21656
  const parts = [symbol, strategyName, exchangeName];
21470
21657
  if (frameName)
21471
21658
  parts.push(frameName);
@@ -21754,7 +21941,7 @@ class ScheduleMarkdownService {
21754
21941
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21755
21942
  * Each combination gets its own isolated storage instance.
21756
21943
  */
21757
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
21944
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
21758
21945
  /**
21759
21946
  * Subscribes to signal emitter to receive scheduled signal events.
21760
21947
  * Protected against multiple subscriptions.
@@ -21957,7 +22144,7 @@ class ScheduleMarkdownService {
21957
22144
  payload,
21958
22145
  });
21959
22146
  if (payload) {
21960
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22147
+ const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21961
22148
  this.getStorage.clear(key);
21962
22149
  }
21963
22150
  else {
@@ -21977,7 +22164,7 @@ class ScheduleMarkdownService {
21977
22164
  * @param backtest - Whether running in backtest mode
21978
22165
  * @returns Unique string key for memoization
21979
22166
  */
21980
- const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
22167
+ const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
21981
22168
  const parts = [symbol, strategyName, exchangeName];
21982
22169
  if (frameName)
21983
22170
  parts.push(frameName);
@@ -22222,7 +22409,7 @@ class PerformanceMarkdownService {
22222
22409
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22223
22410
  * Each combination gets its own isolated storage instance.
22224
22411
  */
22225
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22412
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22226
22413
  /**
22227
22414
  * Subscribes to performance emitter to receive performance events.
22228
22415
  * Protected against multiple subscriptions.
@@ -22389,7 +22576,7 @@ class PerformanceMarkdownService {
22389
22576
  payload,
22390
22577
  });
22391
22578
  if (payload) {
22392
- const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22579
+ const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22393
22580
  this.getStorage.clear(key);
22394
22581
  }
22395
22582
  else {
@@ -22868,7 +23055,7 @@ class WalkerMarkdownService {
22868
23055
  * @param backtest - Whether running in backtest mode
22869
23056
  * @returns Unique string key for memoization
22870
23057
  */
22871
- const CREATE_KEY_FN$h = (exchangeName, frameName, backtest) => {
23058
+ const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
22872
23059
  const parts = [exchangeName];
22873
23060
  if (frameName)
22874
23061
  parts.push(frameName);
@@ -23315,7 +23502,7 @@ class HeatMarkdownService {
23315
23502
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23316
23503
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23317
23504
  */
23318
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23505
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23319
23506
  /**
23320
23507
  * Subscribes to signal emitter to receive tick events.
23321
23508
  * Protected against multiple subscriptions.
@@ -23533,7 +23720,7 @@ class HeatMarkdownService {
23533
23720
  payload,
23534
23721
  });
23535
23722
  if (payload) {
23536
- const key = CREATE_KEY_FN$h(payload.exchangeName, payload.frameName, payload.backtest);
23723
+ const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
23537
23724
  this.getStorage.clear(key);
23538
23725
  }
23539
23726
  else {
@@ -24564,7 +24751,7 @@ class ClientPartial {
24564
24751
  * @param backtest - Whether running in backtest mode
24565
24752
  * @returns Unique string key for memoization
24566
24753
  */
24567
- const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24754
+ const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24568
24755
  /**
24569
24756
  * Creates a callback function for emitting profit events to partialProfitSubject.
24570
24757
  *
@@ -24686,7 +24873,7 @@ class PartialConnectionService {
24686
24873
  * Key format: "signalId:backtest" or "signalId:live"
24687
24874
  * Value: ClientPartial instance with logger and event emitters
24688
24875
  */
24689
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
24876
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
24690
24877
  return new ClientPartial({
24691
24878
  signalId,
24692
24879
  logger: this.loggerService,
@@ -24776,7 +24963,7 @@ class PartialConnectionService {
24776
24963
  const partial = this.getPartial(data.id, backtest);
24777
24964
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24778
24965
  await partial.clear(symbol, data, priceClose, backtest);
24779
- const key = CREATE_KEY_FN$g(data.id, backtest);
24966
+ const key = CREATE_KEY_FN$h(data.id, backtest);
24780
24967
  this.getPartial.clear(key);
24781
24968
  };
24782
24969
  }
@@ -24792,7 +24979,7 @@ class PartialConnectionService {
24792
24979
  * @param backtest - Whether running in backtest mode
24793
24980
  * @returns Unique string key for memoization
24794
24981
  */
24795
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
24982
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
24796
24983
  const parts = [symbol, strategyName, exchangeName];
24797
24984
  if (frameName)
24798
24985
  parts.push(frameName);
@@ -25015,7 +25202,7 @@ class PartialMarkdownService {
25015
25202
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25016
25203
  * Each combination gets its own isolated storage instance.
25017
25204
  */
25018
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25205
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25019
25206
  /**
25020
25207
  * Subscribes to partial profit/loss signal emitters to receive events.
25021
25208
  * Protected against multiple subscriptions.
@@ -25225,7 +25412,7 @@ class PartialMarkdownService {
25225
25412
  payload,
25226
25413
  });
25227
25414
  if (payload) {
25228
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25415
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25229
25416
  this.getStorage.clear(key);
25230
25417
  }
25231
25418
  else {
@@ -25241,7 +25428,7 @@ class PartialMarkdownService {
25241
25428
  * @param context - Context with strategyName, exchangeName, frameName
25242
25429
  * @returns Unique string key for memoization
25243
25430
  */
25244
- const CREATE_KEY_FN$e = (context) => {
25431
+ const CREATE_KEY_FN$f = (context) => {
25245
25432
  const parts = [context.strategyName, context.exchangeName];
25246
25433
  if (context.frameName)
25247
25434
  parts.push(context.frameName);
@@ -25315,7 +25502,7 @@ class PartialGlobalService {
25315
25502
  * @param context - Context with strategyName, exchangeName and frameName
25316
25503
  * @param methodName - Name of the calling method for error tracking
25317
25504
  */
25318
- this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
25505
+ this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
25319
25506
  this.loggerService.log("partialGlobalService validate", {
25320
25507
  context,
25321
25508
  methodName,
@@ -25770,7 +25957,7 @@ class ClientBreakeven {
25770
25957
  * @param backtest - Whether running in backtest mode
25771
25958
  * @returns Unique string key for memoization
25772
25959
  */
25773
- const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25960
+ const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25774
25961
  /**
25775
25962
  * Creates a callback function for emitting breakeven events to breakevenSubject.
25776
25963
  *
@@ -25856,7 +26043,7 @@ class BreakevenConnectionService {
25856
26043
  * Key format: "signalId:backtest" or "signalId:live"
25857
26044
  * Value: ClientBreakeven instance with logger and event emitter
25858
26045
  */
25859
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
26046
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
25860
26047
  return new ClientBreakeven({
25861
26048
  signalId,
25862
26049
  logger: this.loggerService,
@@ -25917,7 +26104,7 @@ class BreakevenConnectionService {
25917
26104
  const breakeven = this.getBreakeven(data.id, backtest);
25918
26105
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25919
26106
  await breakeven.clear(symbol, data, priceClose, backtest);
25920
- const key = CREATE_KEY_FN$d(data.id, backtest);
26107
+ const key = CREATE_KEY_FN$e(data.id, backtest);
25921
26108
  this.getBreakeven.clear(key);
25922
26109
  };
25923
26110
  }
@@ -25933,7 +26120,7 @@ class BreakevenConnectionService {
25933
26120
  * @param backtest - Whether running in backtest mode
25934
26121
  * @returns Unique string key for memoization
25935
26122
  */
25936
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
26123
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
25937
26124
  const parts = [symbol, strategyName, exchangeName];
25938
26125
  if (frameName)
25939
26126
  parts.push(frameName);
@@ -26108,7 +26295,7 @@ class BreakevenMarkdownService {
26108
26295
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26109
26296
  * Each combination gets its own isolated storage instance.
26110
26297
  */
26111
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26298
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26112
26299
  /**
26113
26300
  * Subscribes to breakeven signal emitter to receive events.
26114
26301
  * Protected against multiple subscriptions.
@@ -26297,7 +26484,7 @@ class BreakevenMarkdownService {
26297
26484
  payload,
26298
26485
  });
26299
26486
  if (payload) {
26300
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26487
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26301
26488
  this.getStorage.clear(key);
26302
26489
  }
26303
26490
  else {
@@ -26313,7 +26500,7 @@ class BreakevenMarkdownService {
26313
26500
  * @param context - Context with strategyName, exchangeName, frameName
26314
26501
  * @returns Unique string key for memoization
26315
26502
  */
26316
- const CREATE_KEY_FN$b = (context) => {
26503
+ const CREATE_KEY_FN$c = (context) => {
26317
26504
  const parts = [context.strategyName, context.exchangeName];
26318
26505
  if (context.frameName)
26319
26506
  parts.push(context.frameName);
@@ -26387,7 +26574,7 @@ class BreakevenGlobalService {
26387
26574
  * @param context - Context with strategyName, exchangeName and frameName
26388
26575
  * @param methodName - Name of the calling method for error tracking
26389
26576
  */
26390
- this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
26577
+ this.validate = memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
26391
26578
  this.loggerService.log("breakevenGlobalService validate", {
26392
26579
  context,
26393
26580
  methodName,
@@ -26608,7 +26795,7 @@ class ConfigValidationService {
26608
26795
  * @param backtest - Whether running in backtest mode
26609
26796
  * @returns Unique string key for memoization
26610
26797
  */
26611
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
26798
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
26612
26799
  const parts = [symbol, strategyName, exchangeName];
26613
26800
  if (frameName)
26614
26801
  parts.push(frameName);
@@ -26775,7 +26962,7 @@ class RiskMarkdownService {
26775
26962
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26776
26963
  * Each combination gets its own isolated storage instance.
26777
26964
  */
26778
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
26965
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
26779
26966
  /**
26780
26967
  * Subscribes to risk rejection emitter to receive rejection events.
26781
26968
  * Protected against multiple subscriptions.
@@ -26964,7 +27151,7 @@ class RiskMarkdownService {
26964
27151
  payload,
26965
27152
  });
26966
27153
  if (payload) {
26967
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27154
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26968
27155
  this.getStorage.clear(key);
26969
27156
  }
26970
27157
  else {
@@ -29343,7 +29530,7 @@ class HighestProfitReportService {
29343
29530
  * @returns Colon-separated key string for memoization
29344
29531
  * @internal
29345
29532
  */
29346
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29533
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
29347
29534
  const parts = [symbol, strategyName, exchangeName];
29348
29535
  if (frameName)
29349
29536
  parts.push(frameName);
@@ -29585,7 +29772,7 @@ class StrategyMarkdownService {
29585
29772
  *
29586
29773
  * @internal
29587
29774
  */
29588
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
29775
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
29589
29776
  /**
29590
29777
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
29591
29778
  *
@@ -30153,7 +30340,7 @@ class StrategyMarkdownService {
30153
30340
  this.clear = async (payload) => {
30154
30341
  this.loggerService.log("strategyMarkdownService clear", { payload });
30155
30342
  if (payload) {
30156
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30343
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30157
30344
  this.getStorage.clear(key);
30158
30345
  }
30159
30346
  else {
@@ -30261,7 +30448,7 @@ class StrategyMarkdownService {
30261
30448
  * Creates a unique key for memoizing ReportStorage instances.
30262
30449
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30263
30450
  */
30264
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30451
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30265
30452
  const parts = [symbol, strategyName, exchangeName];
30266
30453
  if (frameName)
30267
30454
  parts.push(frameName);
@@ -30454,7 +30641,7 @@ let ReportStorage$2 = class ReportStorage {
30454
30641
  class SyncMarkdownService {
30455
30642
  constructor() {
30456
30643
  this.loggerService = inject(TYPES.loggerService);
30457
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
30644
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
30458
30645
  /**
30459
30646
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
30460
30647
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -30650,7 +30837,7 @@ class SyncMarkdownService {
30650
30837
  this.clear = async (payload) => {
30651
30838
  this.loggerService.log("syncMarkdownService clear", { payload });
30652
30839
  if (payload) {
30653
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30840
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30654
30841
  this.getStorage.clear(key);
30655
30842
  }
30656
30843
  else {
@@ -30663,7 +30850,7 @@ class SyncMarkdownService {
30663
30850
  /**
30664
30851
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
30665
30852
  */
30666
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30853
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30667
30854
  const parts = [symbol, strategyName, exchangeName];
30668
30855
  if (frameName)
30669
30856
  parts.push(frameName);
@@ -30839,7 +31026,7 @@ let ReportStorage$1 = class ReportStorage {
30839
31026
  class HighestProfitMarkdownService {
30840
31027
  constructor() {
30841
31028
  this.loggerService = inject(TYPES.loggerService);
30842
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31029
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
30843
31030
  /**
30844
31031
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
30845
31032
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31005,7 +31192,7 @@ class HighestProfitMarkdownService {
31005
31192
  this.clear = async (payload) => {
31006
31193
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31007
31194
  if (payload) {
31008
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31195
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31009
31196
  this.getStorage.clear(key);
31010
31197
  }
31011
31198
  else {
@@ -31027,7 +31214,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31027
31214
  * @param backtest - Whether running in backtest mode
31028
31215
  * @returns Unique string key for memoization
31029
31216
  */
31030
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31217
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31031
31218
  const parts = [symbol, strategyName, exchangeName];
31032
31219
  if (frameName)
31033
31220
  parts.push(frameName);
@@ -31070,7 +31257,7 @@ class PriceMetaService {
31070
31257
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31071
31258
  * Instances are cached until clear() is called.
31072
31259
  */
31073
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31260
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31074
31261
  /**
31075
31262
  * Returns the current market price for the given symbol and context.
31076
31263
  *
@@ -31099,10 +31286,10 @@ class PriceMetaService {
31099
31286
  if (source.data) {
31100
31287
  return source.data;
31101
31288
  }
31102
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31289
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31103
31290
  const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31104
31291
  if (typeof currentPrice === "symbol") {
31105
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31292
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31106
31293
  }
31107
31294
  return currentPrice;
31108
31295
  };
@@ -31144,7 +31331,7 @@ class PriceMetaService {
31144
31331
  this.getSource.clear();
31145
31332
  return;
31146
31333
  }
31147
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31334
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31148
31335
  this.getSource.clear(key);
31149
31336
  };
31150
31337
  }
@@ -31162,7 +31349,7 @@ const LISTEN_TIMEOUT = 120000;
31162
31349
  * @param backtest - Whether running in backtest mode
31163
31350
  * @returns Unique string key for memoization
31164
31351
  */
31165
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31352
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31166
31353
  const parts = [symbol, strategyName, exchangeName];
31167
31354
  if (frameName)
31168
31355
  parts.push(frameName);
@@ -31205,7 +31392,7 @@ class TimeMetaService {
31205
31392
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31206
31393
  * Instances are cached until clear() is called.
31207
31394
  */
31208
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31395
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31209
31396
  /**
31210
31397
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31211
31398
  *
@@ -31233,10 +31420,10 @@ class TimeMetaService {
31233
31420
  if (source.data) {
31234
31421
  return source.data;
31235
31422
  }
31236
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31423
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31237
31424
  const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31238
31425
  if (typeof timestamp === "symbol") {
31239
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31426
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31240
31427
  }
31241
31428
  return timestamp;
31242
31429
  };
@@ -31278,7 +31465,7 @@ class TimeMetaService {
31278
31465
  this.getSource.clear();
31279
31466
  return;
31280
31467
  }
31281
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31468
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31282
31469
  this.getSource.clear(key);
31283
31470
  };
31284
31471
  }
@@ -31374,7 +31561,7 @@ class MaxDrawdownReportService {
31374
31561
  /**
31375
31562
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31376
31563
  */
31377
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31564
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31378
31565
  const parts = [symbol, strategyName, exchangeName];
31379
31566
  if (frameName)
31380
31567
  parts.push(frameName);
@@ -31498,7 +31685,7 @@ class ReportStorage {
31498
31685
  class MaxDrawdownMarkdownService {
31499
31686
  constructor() {
31500
31687
  this.loggerService = inject(TYPES.loggerService);
31501
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
31688
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
31502
31689
  /**
31503
31690
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
31504
31691
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -31577,7 +31764,7 @@ class MaxDrawdownMarkdownService {
31577
31764
  this.clear = async (payload) => {
31578
31765
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
31579
31766
  if (payload) {
31580
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31767
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31581
31768
  this.getStorage.clear(key);
31582
31769
  }
31583
31770
  else {
@@ -31831,7 +32018,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
31831
32018
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
31832
32019
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
31833
32020
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
31834
- const MS_PER_MINUTE$2 = 60000;
32021
+ const MS_PER_MINUTE$3 = 60000;
31835
32022
  /**
31836
32023
  * Gets current timestamp from execution context if available.
31837
32024
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -31898,7 +32085,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
31898
32085
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
31899
32086
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
31900
32087
  };
31901
- const INTERVAL_MINUTES$2 = {
32088
+ const INTERVAL_MINUTES$3 = {
31902
32089
  "1m": 1,
31903
32090
  "3m": 3,
31904
32091
  "5m": 5,
@@ -31930,7 +32117,7 @@ const INTERVAL_MINUTES$2 = {
31930
32117
  * @returns Aligned timestamp rounded down to interval boundary
31931
32118
  */
31932
32119
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
31933
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
32120
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
31934
32121
  return Math.floor(timestamp / intervalMs) * intervalMs;
31935
32122
  };
31936
32123
  /**
@@ -32070,11 +32257,11 @@ class ExchangeInstance {
32070
32257
  limit,
32071
32258
  });
32072
32259
  const getCandles = this._methods.getCandles;
32073
- const step = INTERVAL_MINUTES$2[interval];
32260
+ const step = INTERVAL_MINUTES$3[interval];
32074
32261
  if (!step) {
32075
32262
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
32076
32263
  }
32077
- const stepMs = step * MS_PER_MINUTE$2;
32264
+ const stepMs = step * MS_PER_MINUTE$3;
32078
32265
  // Align when down to interval boundary
32079
32266
  const when = await GET_TIMESTAMP_FN();
32080
32267
  const whenTimestamp = when.getTime();
@@ -32259,7 +32446,7 @@ class ExchangeInstance {
32259
32446
  const when = await GET_TIMESTAMP_FN();
32260
32447
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
32261
32448
  const to = new Date(alignedTo);
32262
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
32449
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
32263
32450
  const isBacktest = await GET_BACKTEST_FN();
32264
32451
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
32265
32452
  };
@@ -32291,7 +32478,7 @@ class ExchangeInstance {
32291
32478
  const when = await GET_TIMESTAMP_FN();
32292
32479
  // Align to 1-minute boundary to prevent look-ahead bias
32293
32480
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
32294
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$2 - MS_PER_MINUTE$2;
32481
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
32295
32482
  const isBacktest = await GET_BACKTEST_FN();
32296
32483
  // No limit: fetch a single window and return as-is
32297
32484
  if (limit === undefined) {
@@ -32354,11 +32541,11 @@ class ExchangeInstance {
32354
32541
  sDate,
32355
32542
  eDate,
32356
32543
  });
32357
- const step = INTERVAL_MINUTES$2[interval];
32544
+ const step = INTERVAL_MINUTES$3[interval];
32358
32545
  if (!step) {
32359
32546
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
32360
32547
  }
32361
- const stepMs = step * MS_PER_MINUTE$2;
32548
+ const stepMs = step * MS_PER_MINUTE$3;
32362
32549
  const when = await GET_TIMESTAMP_FN();
32363
32550
  const nowTimestamp = when.getTime();
32364
32551
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -32648,8 +32835,8 @@ const Exchange = new ExchangeUtils();
32648
32835
 
32649
32836
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
32650
32837
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
32651
- const MS_PER_MINUTE$1 = 60000;
32652
- const INTERVAL_MINUTES$1 = {
32838
+ const MS_PER_MINUTE$2 = 60000;
32839
+ const INTERVAL_MINUTES$2 = {
32653
32840
  "1m": 1,
32654
32841
  "3m": 3,
32655
32842
  "5m": 5,
@@ -32664,7 +32851,7 @@ const INTERVAL_MINUTES$1 = {
32664
32851
  "1w": 10080,
32665
32852
  };
32666
32853
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
32667
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
32854
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
32668
32855
  return Math.floor(timestamp / intervalMs) * intervalMs;
32669
32856
  };
32670
32857
  const BAR_LENGTH = 30;
@@ -32689,11 +32876,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
32689
32876
  async function checkCandles(params) {
32690
32877
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
32691
32878
  backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
32692
- const step = INTERVAL_MINUTES$1[interval];
32879
+ const step = INTERVAL_MINUTES$2[interval];
32693
32880
  if (!step) {
32694
32881
  throw new Error(`checkCandles: unsupported interval=${interval}`);
32695
32882
  }
32696
- const stepMs = step * MS_PER_MINUTE$1;
32883
+ const stepMs = step * MS_PER_MINUTE$2;
32697
32884
  const dir = join(baseDir, exchangeName, symbol, interval);
32698
32885
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
32699
32886
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -32763,11 +32950,11 @@ async function warmCandles(params) {
32763
32950
  from,
32764
32951
  to,
32765
32952
  });
32766
- const step = INTERVAL_MINUTES$1[interval];
32953
+ const step = INTERVAL_MINUTES$2[interval];
32767
32954
  if (!step) {
32768
32955
  throw new Error(`warmCandles: unsupported interval=${interval}`);
32769
32956
  }
32770
- const stepMs = step * MS_PER_MINUTE$1;
32957
+ const stepMs = step * MS_PER_MINUTE$2;
32771
32958
  const instance = new ExchangeInstance(exchangeName);
32772
32959
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
32773
32960
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -44176,7 +44363,7 @@ const createSearchIndex = () => {
44176
44363
  return { upsert, remove, list, search, read };
44177
44364
  };
44178
44365
 
44179
- const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44366
+ const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44180
44367
  const LIST_MEMORY_FN = ({ id, content }) => ({
44181
44368
  memoryId: id,
44182
44369
  content: content,
@@ -44498,7 +44685,7 @@ class MemoryDummyInstance {
44498
44685
  class MemoryAdapter {
44499
44686
  constructor() {
44500
44687
  this.MemoryFactory = MemoryPersistInstance;
44501
- this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
44688
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
44502
44689
  /**
44503
44690
  * Activates the adapter by subscribing to signal lifecycle events.
44504
44691
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -44509,7 +44696,7 @@ class MemoryAdapter {
44509
44696
  this.enable = singleshot(() => {
44510
44697
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
44511
44698
  const handleDispose = (signalId) => {
44512
- const prefix = CREATE_KEY_FN$3(signalId, "");
44699
+ const prefix = CREATE_KEY_FN$4(signalId, "");
44513
44700
  for (const key of this.getInstance.keys()) {
44514
44701
  if (key.startsWith(prefix)) {
44515
44702
  const instance = this.getInstance.get(key);
@@ -44554,7 +44741,7 @@ class MemoryAdapter {
44554
44741
  bucketName: dto.bucketName,
44555
44742
  memoryId: dto.memoryId,
44556
44743
  });
44557
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44744
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44558
44745
  const isInitial = !this.getInstance.has(key);
44559
44746
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44560
44747
  await instance.waitForInit(isInitial);
@@ -44576,7 +44763,7 @@ class MemoryAdapter {
44576
44763
  bucketName: dto.bucketName,
44577
44764
  query: dto.query,
44578
44765
  });
44579
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44766
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44580
44767
  const isInitial = !this.getInstance.has(key);
44581
44768
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44582
44769
  await instance.waitForInit(isInitial);
@@ -44596,7 +44783,7 @@ class MemoryAdapter {
44596
44783
  signalId: dto.signalId,
44597
44784
  bucketName: dto.bucketName,
44598
44785
  });
44599
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44786
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44600
44787
  const isInitial = !this.getInstance.has(key);
44601
44788
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44602
44789
  await instance.waitForInit(isInitial);
@@ -44617,7 +44804,7 @@ class MemoryAdapter {
44617
44804
  bucketName: dto.bucketName,
44618
44805
  memoryId: dto.memoryId,
44619
44806
  });
44620
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44807
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44621
44808
  const isInitial = !this.getInstance.has(key);
44622
44809
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44623
44810
  await instance.waitForInit(isInitial);
@@ -44640,7 +44827,7 @@ class MemoryAdapter {
44640
44827
  bucketName: dto.bucketName,
44641
44828
  memoryId: dto.memoryId,
44642
44829
  });
44643
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44830
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44644
44831
  const isInitial = !this.getInstance.has(key);
44645
44832
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44646
44833
  await instance.waitForInit(isInitial);
@@ -44932,7 +45119,7 @@ async function removeMemory(dto) {
44932
45119
  });
44933
45120
  }
44934
45121
 
44935
- const CREATE_KEY_FN$2 = (signalId, bucketName) => `${signalId}-${bucketName}`;
45122
+ const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44936
45123
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
44937
45124
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
44938
45125
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
@@ -45552,7 +45739,7 @@ class DumpDummyInstance {
45552
45739
  class DumpAdapter {
45553
45740
  constructor() {
45554
45741
  this.DumpFactory = DumpMarkdownInstance;
45555
- this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$2(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
45742
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
45556
45743
  /**
45557
45744
  * Activates the adapter by subscribing to signal lifecycle events.
45558
45745
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -45563,7 +45750,7 @@ class DumpAdapter {
45563
45750
  this.enable = singleshot(() => {
45564
45751
  backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
45565
45752
  const handleDispose = (signalId) => {
45566
- const prefix = CREATE_KEY_FN$2(signalId, "");
45753
+ const prefix = CREATE_KEY_FN$3(signalId, "");
45567
45754
  for (const key of this.getInstance.keys()) {
45568
45755
  if (key.startsWith(prefix)) {
45569
45756
  const instance = this.getInstance.get(key);
@@ -48497,6 +48684,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
48497
48684
  };
48498
48685
  const PositionSize = PositionSizeUtils;
48499
48686
 
48687
+ const METHOD_NAME_MOONBAG = "Position.moonbag";
48688
+ const METHOD_NAME_BRACKET = "Position.bracket";
48689
+ /**
48690
+ * Utilities for calculating take profit and stop loss price levels.
48691
+ * Automatically inverts direction based on position type (long/short).
48692
+ */
48693
+ class Position {
48694
+ }
48695
+ /**
48696
+ * Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
48697
+ * @param dto.position - position type: "long" or "short"
48698
+ * @param dto.currentPrice - current asset price
48699
+ * @param dto.percentStopLoss - stop loss percentage from 0 to 100
48700
+ * @returns priceTakeProfit and priceStopLoss in fiat
48701
+ */
48702
+ Position.moonbag = (dto) => {
48703
+ backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
48704
+ const percentTakeProfit = 50;
48705
+ const sign = dto.position === "long" ? 1 : -1;
48706
+ return {
48707
+ position: dto.position,
48708
+ priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
48709
+ priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
48710
+ };
48711
+ };
48712
+ /**
48713
+ * Calculates levels for a bracket order with custom TP and SL.
48714
+ * @param dto.position - position type: "long" or "short"
48715
+ * @param dto.currentPrice - current asset price
48716
+ * @param dto.percentStopLoss - stop loss percentage from 0 to 100
48717
+ * @param dto.percentTakeProfit - take profit percentage from 0 to 100
48718
+ * @returns priceTakeProfit and priceStopLoss in fiat
48719
+ */
48720
+ Position.bracket = (dto) => {
48721
+ backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
48722
+ const sign = dto.position === "long" ? 1 : -1;
48723
+ return {
48724
+ position: dto.position,
48725
+ priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
48726
+ priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
48727
+ };
48728
+ };
48729
+
48500
48730
  const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
48501
48731
  const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
48502
48732
  const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
@@ -50699,7 +50929,7 @@ const StorageBacktest = new StorageBacktestAdapter();
50699
50929
  * Generates a unique key for notification identification.
50700
50930
  * @returns Random string identifier
50701
50931
  */
50702
- const CREATE_KEY_FN$1 = () => randomString();
50932
+ const CREATE_KEY_FN$2 = () => randomString();
50703
50933
  /**
50704
50934
  * Creates a notification model from signal tick result.
50705
50935
  * Handles opened, closed, scheduled, and cancelled signal actions.
@@ -50710,7 +50940,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50710
50940
  if (data.action === "opened") {
50711
50941
  return {
50712
50942
  type: "signal.opened",
50713
- id: CREATE_KEY_FN$1(),
50943
+ id: CREATE_KEY_FN$2(),
50714
50944
  timestamp: data.signal.pendingAt,
50715
50945
  backtest: data.backtest,
50716
50946
  symbol: data.symbol,
@@ -50744,7 +50974,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50744
50974
  const durationMin = Math.round(durationMs / 60000);
50745
50975
  return {
50746
50976
  type: "signal.closed",
50747
- id: CREATE_KEY_FN$1(),
50977
+ id: CREATE_KEY_FN$2(),
50748
50978
  timestamp: data.closeTimestamp,
50749
50979
  backtest: data.backtest,
50750
50980
  symbol: data.symbol,
@@ -50778,7 +51008,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50778
51008
  if (data.action === "scheduled") {
50779
51009
  return {
50780
51010
  type: "signal.scheduled",
50781
- id: CREATE_KEY_FN$1(),
51011
+ id: CREATE_KEY_FN$2(),
50782
51012
  timestamp: data.signal.scheduledAt,
50783
51013
  backtest: data.backtest,
50784
51014
  symbol: data.symbol,
@@ -50811,7 +51041,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50811
51041
  const durationMin = Math.round(durationMs / 60000);
50812
51042
  return {
50813
51043
  type: "signal.cancelled",
50814
- id: CREATE_KEY_FN$1(),
51044
+ id: CREATE_KEY_FN$2(),
50815
51045
  timestamp: data.closeTimestamp,
50816
51046
  backtest: data.backtest,
50817
51047
  symbol: data.symbol,
@@ -50844,7 +51074,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50844
51074
  */
50845
51075
  const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
50846
51076
  type: "partial_profit.available",
50847
- id: CREATE_KEY_FN$1(),
51077
+ id: CREATE_KEY_FN$2(),
50848
51078
  timestamp: data.timestamp,
50849
51079
  backtest: data.backtest,
50850
51080
  symbol: data.symbol,
@@ -50879,7 +51109,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
50879
51109
  */
50880
51110
  const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
50881
51111
  type: "partial_loss.available",
50882
- id: CREATE_KEY_FN$1(),
51112
+ id: CREATE_KEY_FN$2(),
50883
51113
  timestamp: data.timestamp,
50884
51114
  backtest: data.backtest,
50885
51115
  symbol: data.symbol,
@@ -50914,7 +51144,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
50914
51144
  */
50915
51145
  const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
50916
51146
  type: "breakeven.available",
50917
- id: CREATE_KEY_FN$1(),
51147
+ id: CREATE_KEY_FN$2(),
50918
51148
  timestamp: data.timestamp,
50919
51149
  backtest: data.backtest,
50920
51150
  symbol: data.symbol,
@@ -50952,7 +51182,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
50952
51182
  if (data.action === "partial-profit") {
50953
51183
  return {
50954
51184
  type: "partial_profit.commit",
50955
- id: CREATE_KEY_FN$1(),
51185
+ id: CREATE_KEY_FN$2(),
50956
51186
  timestamp: data.timestamp,
50957
51187
  backtest: data.backtest,
50958
51188
  symbol: data.symbol,
@@ -50984,7 +51214,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
50984
51214
  if (data.action === "partial-loss") {
50985
51215
  return {
50986
51216
  type: "partial_loss.commit",
50987
- id: CREATE_KEY_FN$1(),
51217
+ id: CREATE_KEY_FN$2(),
50988
51218
  timestamp: data.timestamp,
50989
51219
  backtest: data.backtest,
50990
51220
  symbol: data.symbol,
@@ -51016,7 +51246,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51016
51246
  if (data.action === "breakeven") {
51017
51247
  return {
51018
51248
  type: "breakeven.commit",
51019
- id: CREATE_KEY_FN$1(),
51249
+ id: CREATE_KEY_FN$2(),
51020
51250
  timestamp: data.timestamp,
51021
51251
  backtest: data.backtest,
51022
51252
  symbol: data.symbol,
@@ -51047,7 +51277,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51047
51277
  if (data.action === "trailing-stop") {
51048
51278
  return {
51049
51279
  type: "trailing_stop.commit",
51050
- id: CREATE_KEY_FN$1(),
51280
+ id: CREATE_KEY_FN$2(),
51051
51281
  timestamp: data.timestamp,
51052
51282
  backtest: data.backtest,
51053
51283
  symbol: data.symbol,
@@ -51079,7 +51309,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51079
51309
  if (data.action === "trailing-take") {
51080
51310
  return {
51081
51311
  type: "trailing_take.commit",
51082
- id: CREATE_KEY_FN$1(),
51312
+ id: CREATE_KEY_FN$2(),
51083
51313
  timestamp: data.timestamp,
51084
51314
  backtest: data.backtest,
51085
51315
  symbol: data.symbol,
@@ -51111,7 +51341,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51111
51341
  if (data.action === "activate-scheduled") {
51112
51342
  return {
51113
51343
  type: "activate_scheduled.commit",
51114
- id: CREATE_KEY_FN$1(),
51344
+ id: CREATE_KEY_FN$2(),
51115
51345
  timestamp: data.timestamp,
51116
51346
  backtest: data.backtest,
51117
51347
  symbol: data.symbol,
@@ -51143,7 +51373,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51143
51373
  if (data.action === "average-buy") {
51144
51374
  return {
51145
51375
  type: "average_buy.commit",
51146
- id: CREATE_KEY_FN$1(),
51376
+ id: CREATE_KEY_FN$2(),
51147
51377
  timestamp: data.timestamp,
51148
51378
  backtest: data.backtest,
51149
51379
  symbol: data.symbol,
@@ -51176,7 +51406,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51176
51406
  if (data.action === "cancel-scheduled") {
51177
51407
  return {
51178
51408
  type: "cancel_scheduled.commit",
51179
- id: CREATE_KEY_FN$1(),
51409
+ id: CREATE_KEY_FN$2(),
51180
51410
  timestamp: data.timestamp,
51181
51411
  backtest: data.backtest,
51182
51412
  symbol: data.symbol,
@@ -51199,7 +51429,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51199
51429
  if (data.action === "close-pending") {
51200
51430
  return {
51201
51431
  type: "close_pending.commit",
51202
- id: CREATE_KEY_FN$1(),
51432
+ id: CREATE_KEY_FN$2(),
51203
51433
  timestamp: data.timestamp,
51204
51434
  backtest: data.backtest,
51205
51435
  symbol: data.symbol,
@@ -51231,7 +51461,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51231
51461
  if (data.action === "signal-open") {
51232
51462
  return {
51233
51463
  type: "signal_sync.open",
51234
- id: CREATE_KEY_FN$1(),
51464
+ id: CREATE_KEY_FN$2(),
51235
51465
  timestamp: data.timestamp,
51236
51466
  backtest: data.backtest,
51237
51467
  symbol: data.symbol,
@@ -51263,7 +51493,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51263
51493
  if (data.action === "signal-close") {
51264
51494
  return {
51265
51495
  type: "signal_sync.close",
51266
- id: CREATE_KEY_FN$1(),
51496
+ id: CREATE_KEY_FN$2(),
51267
51497
  timestamp: data.timestamp,
51268
51498
  backtest: data.backtest,
51269
51499
  symbol: data.symbol,
@@ -51301,7 +51531,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51301
51531
  */
51302
51532
  const CREATE_RISK_NOTIFICATION_FN = (data) => ({
51303
51533
  type: "risk.rejection",
51304
- id: CREATE_KEY_FN$1(),
51534
+ id: CREATE_KEY_FN$2(),
51305
51535
  timestamp: data.timestamp,
51306
51536
  backtest: data.backtest,
51307
51537
  symbol: data.symbol,
@@ -51327,7 +51557,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
51327
51557
  */
51328
51558
  const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
51329
51559
  type: "error.info",
51330
- id: CREATE_KEY_FN$1(),
51560
+ id: CREATE_KEY_FN$2(),
51331
51561
  error: errorData(error),
51332
51562
  message: getErrorMessage(error),
51333
51563
  backtest: false,
@@ -51339,7 +51569,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
51339
51569
  */
51340
51570
  const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
51341
51571
  type: "error.critical",
51342
- id: CREATE_KEY_FN$1(),
51572
+ id: CREATE_KEY_FN$2(),
51343
51573
  error: errorData(error),
51344
51574
  message: getErrorMessage(error),
51345
51575
  backtest: false,
@@ -51351,7 +51581,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
51351
51581
  */
51352
51582
  const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
51353
51583
  type: "error.validation",
51354
- id: CREATE_KEY_FN$1(),
51584
+ id: CREATE_KEY_FN$2(),
51355
51585
  error: errorData(error),
51356
51586
  message: getErrorMessage(error),
51357
51587
  backtest: false,
@@ -52780,7 +53010,7 @@ const NotificationLive = new NotificationLiveAdapter();
52780
53010
  */
52781
53011
  const NotificationBacktest = new NotificationBacktestAdapter();
52782
53012
 
52783
- const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
53013
+ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
52784
53014
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
52785
53015
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
52786
53016
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
@@ -52789,8 +53019,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
52789
53019
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
52790
53020
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
52791
53021
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
52792
- const MS_PER_MINUTE = 60000;
52793
- const INTERVAL_MINUTES = {
53022
+ const MS_PER_MINUTE$1 = 60000;
53023
+ const INTERVAL_MINUTES$1 = {
52794
53024
  "1m": 1,
52795
53025
  "3m": 3,
52796
53026
  "5m": 5,
@@ -52824,12 +53054,12 @@ const INTERVAL_MINUTES = {
52824
53054
  * // Returns timestamp for 2025-10-01T01:00:00Z
52825
53055
  * ```
52826
53056
  */
52827
- const align = (timestamp, interval) => {
52828
- const intervalMinutes = INTERVAL_MINUTES[interval];
53057
+ const align$1 = (timestamp, interval) => {
53058
+ const intervalMinutes = INTERVAL_MINUTES$1[interval];
52829
53059
  if (!intervalMinutes) {
52830
53060
  throw new Error(`align: unknown interval=${interval}`);
52831
53061
  }
52832
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
53062
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
52833
53063
  return Math.floor(timestamp / intervalMs) * intervalMs;
52834
53064
  };
52835
53065
  /**
@@ -52840,7 +53070,7 @@ const align = (timestamp, interval) => {
52840
53070
  * @param backtest - Whether running in backtest mode
52841
53071
  * @returns Cache key string
52842
53072
  */
52843
- const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
53073
+ const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
52844
53074
  const parts = [strategyName, exchangeName];
52845
53075
  if (frameName)
52846
53076
  parts.push(frameName);
@@ -52864,16 +53094,16 @@ const NEVER_VALUE = Symbol("never");
52864
53094
  *
52865
53095
  * @example
52866
53096
  * ```typescript
52867
- * const instance = new CacheInstance(myExpensiveFunction, "1h");
53097
+ * const instance = new CacheFnInstance(myExpensiveFunction, "1h");
52868
53098
  * const result = instance.run(arg1, arg2); // Computed
52869
53099
  * const result2 = instance.run(arg1, arg2); // Cached (within same hour)
52870
53100
  * // After 1 hour passes
52871
53101
  * const result3 = instance.run(arg1, arg2); // Recomputed
52872
53102
  * ```
52873
53103
  */
52874
- class CacheInstance {
53104
+ class CacheFnInstance {
52875
53105
  /**
52876
- * Creates a new CacheInstance for a specific function and interval.
53106
+ * Creates a new CacheFnInstance for a specific function and interval.
52877
53107
  *
52878
53108
  * @param fn - Function to cache
52879
53109
  * @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
@@ -52910,7 +53140,7 @@ class CacheInstance {
52910
53140
  *
52911
53141
  * @example
52912
53142
  * ```typescript
52913
- * const instance = new CacheInstance(calculateIndicator, "15m");
53143
+ * const instance = new CacheFnInstance(calculateIndicator, "15m");
52914
53144
  * const result = instance.run("BTCUSDT", 100);
52915
53145
  * console.log(result.value); // Calculated value
52916
53146
  * console.log(result.when); // Cache timestamp
@@ -52918,26 +53148,26 @@ class CacheInstance {
52918
53148
  */
52919
53149
  this.run = (...args) => {
52920
53150
  backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
52921
- const step = INTERVAL_MINUTES[this.interval];
53151
+ const step = INTERVAL_MINUTES$1[this.interval];
52922
53152
  {
52923
53153
  if (!MethodContextService.hasContext()) {
52924
- throw new Error("CacheInstance run requires method context");
53154
+ throw new Error("CacheFnInstance run requires method context");
52925
53155
  }
52926
53156
  if (!ExecutionContextService.hasContext()) {
52927
- throw new Error("CacheInstance run requires execution context");
53157
+ throw new Error("CacheFnInstance run requires execution context");
52928
53158
  }
52929
53159
  if (!step) {
52930
- throw new Error(`CacheInstance unknown cache ttl interval=${this.interval}`);
53160
+ throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
52931
53161
  }
52932
53162
  }
52933
- const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53163
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
52934
53164
  const argKey = String(this.key(args));
52935
53165
  const key = `${contextKey}:${argKey}`;
52936
53166
  const currentWhen = backtest.executionContextService.context.when;
52937
53167
  const cached = this._cacheMap.get(key);
52938
53168
  if (cached) {
52939
- const currentAligned = align(currentWhen.getTime(), this.interval);
52940
- const cachedAligned = align(cached.when.getTime(), this.interval);
53169
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
53170
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
52941
53171
  if (currentAligned === cachedAligned) {
52942
53172
  return cached;
52943
53173
  }
@@ -52960,7 +53190,7 @@ class CacheInstance {
52960
53190
  *
52961
53191
  * @example
52962
53192
  * ```typescript
52963
- * const instance = new CacheInstance(calculateIndicator, "1h");
53193
+ * const instance = new CacheFnInstance(calculateIndicator, "1h");
52964
53194
  * const result1 = instance.run("BTCUSDT", 14); // Computed
52965
53195
  * const result2 = instance.run("BTCUSDT", 14); // Cached
52966
53196
  *
@@ -52970,7 +53200,7 @@ class CacheInstance {
52970
53200
  * ```
52971
53201
  */
52972
53202
  this.clear = () => {
52973
- const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53203
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
52974
53204
  const prefix = `${contextKey}:`;
52975
53205
  for (const key of this._cacheMap.keys()) {
52976
53206
  if (key.startsWith(prefix)) {
@@ -52990,7 +53220,7 @@ class CacheInstance {
52990
53220
  *
52991
53221
  * @example
52992
53222
  * ```typescript
52993
- * const instance = new CacheInstance(calculateIndicator, "1h");
53223
+ * const instance = new CacheFnInstance(calculateIndicator, "1h");
52994
53224
  * instance.run("BTCUSDT", 14); // Cached at 10:00
52995
53225
  * instance.run("ETHUSDT", 14); // Cached at 10:00
52996
53226
  * // Time passes to 11:00
@@ -52999,10 +53229,10 @@ class CacheInstance {
52999
53229
  */
53000
53230
  this.gc = () => {
53001
53231
  const currentWhen = backtest.executionContextService.context.when;
53002
- const currentAligned = align(currentWhen.getTime(), this.interval);
53232
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
53003
53233
  let removed = 0;
53004
53234
  for (const [key, cached] of this._cacheMap.entries()) {
53005
- const cachedAligned = align(cached.when.getTime(), this.interval);
53235
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
53006
53236
  if (currentAligned !== cachedAligned) {
53007
53237
  this._cacheMap.delete(key);
53008
53238
  removed++;
@@ -53077,7 +53307,7 @@ class CacheFileInstance {
53077
53307
  */
53078
53308
  this.run = async (...args) => {
53079
53309
  backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
53080
- const step = INTERVAL_MINUTES[this.interval];
53310
+ const step = INTERVAL_MINUTES$1[this.interval];
53081
53311
  {
53082
53312
  if (!MethodContextService.hasContext()) {
53083
53313
  throw new Error("CacheFileInstance run requires method context");
@@ -53091,7 +53321,7 @@ class CacheFileInstance {
53091
53321
  }
53092
53322
  const [symbol, ...rest] = args;
53093
53323
  const { when } = backtest.executionContextService.context;
53094
- const alignedTs = align(when.getTime(), this.interval);
53324
+ const alignedTs = align$1(when.getTime(), this.interval);
53095
53325
  const bucket = `${this.name}_${this.interval}_${this.index}`;
53096
53326
  const entityKey = this.key([symbol, alignedTs, ...rest]);
53097
53327
  const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
@@ -53099,9 +53329,19 @@ class CacheFileInstance {
53099
53329
  return cached.data;
53100
53330
  }
53101
53331
  const result = await this.fn.call(null, ...args);
53102
- await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
53332
+ await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
53103
53333
  return result;
53104
53334
  };
53335
+ /**
53336
+ * Soft-delete all persisted records for this instance's bucket.
53337
+ * After this call the next `run()` will recompute and re-cache the value.
53338
+ */
53339
+ this.clear = async () => {
53340
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53341
+ for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
53342
+ await PersistMeasureAdapter.removeMeasureData(bucket, key);
53343
+ }
53344
+ };
53105
53345
  this.index = CacheFileInstance.createIndex();
53106
53346
  }
53107
53347
  }
@@ -53125,10 +53365,10 @@ CacheFileInstance._indexCounter = 0;
53125
53365
  class CacheUtils {
53126
53366
  constructor() {
53127
53367
  /**
53128
- * Memoized function to get or create CacheInstance for a function.
53368
+ * Memoized function to get or create CacheFnInstance for a function.
53129
53369
  * Each function gets its own isolated cache instance.
53130
53370
  */
53131
- this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
53371
+ this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
53132
53372
  /**
53133
53373
  * Memoized function to get or create CacheFileInstance for an async function.
53134
53374
  * Each function gets its own isolated file-cache instance.
@@ -53237,31 +53477,34 @@ class CacheUtils {
53237
53477
  */
53238
53478
  this.file = (run, context) => {
53239
53479
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
53480
+ {
53481
+ this._getFileInstance(run, context.interval, context.name, context.key);
53482
+ }
53240
53483
  const wrappedFn = (...args) => {
53241
53484
  const instance = this._getFileInstance(run, context.interval, context.name, context.key);
53242
53485
  return instance.run(...args);
53243
53486
  };
53244
- wrappedFn.clear = () => {
53487
+ wrappedFn.clear = async () => {
53245
53488
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
53246
- this._getFileInstance.clear(run);
53489
+ await this._getFileInstance.get(run)?.clear();
53247
53490
  };
53248
53491
  return wrappedFn;
53249
53492
  };
53250
53493
  /**
53251
- * Dispose (remove) the memoized CacheInstance for a specific function.
53494
+ * Dispose (remove) the memoized CacheFnInstance for a specific function.
53252
53495
  *
53253
- * Removes the CacheInstance from the internal memoization cache, discarding all cached
53496
+ * Removes the CacheFnInstance from the internal memoization cache, discarding all cached
53254
53497
  * results across all contexts (all strategy/exchange/mode combinations) for that function.
53255
- * The next call to the wrapped function will create a fresh CacheInstance.
53498
+ * The next call to the wrapped function will create a fresh CacheFnInstance.
53256
53499
  *
53257
53500
  * @template T - Function type
53258
- * @param run - Function whose CacheInstance should be disposed.
53501
+ * @param run - Function whose CacheFnInstance should be disposed.
53259
53502
  *
53260
53503
  * @example
53261
53504
  * ```typescript
53262
53505
  * const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
53263
53506
  *
53264
- * // Dispose CacheInstance for a specific function
53507
+ * // Dispose CacheFnInstance for a specific function
53265
53508
  * Cache.dispose(calculateIndicator);
53266
53509
  * ```
53267
53510
  */
@@ -53275,7 +53518,7 @@ class CacheUtils {
53275
53518
  }
53276
53519
  };
53277
53520
  /**
53278
- * Clears all memoized CacheInstance and CacheFileInstance objects.
53521
+ * Clears all memoized CacheFnInstance and CacheFileInstance objects.
53279
53522
  * Call this when process.cwd() changes between strategy iterations
53280
53523
  * so new instances are created with the updated base path.
53281
53524
  */
@@ -53305,6 +53548,426 @@ class CacheUtils {
53305
53548
  */
53306
53549
  const Cache = new CacheUtils();
53307
53550
 
53551
+ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
53552
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
53553
+ const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
53554
+ const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
53555
+ const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
53556
+ const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
53557
+ const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
53558
+ const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
53559
+ const MS_PER_MINUTE = 60000;
53560
+ const INTERVAL_MINUTES = {
53561
+ "1m": 1,
53562
+ "3m": 3,
53563
+ "5m": 5,
53564
+ "15m": 15,
53565
+ "30m": 30,
53566
+ "1h": 60,
53567
+ "2h": 120,
53568
+ "4h": 240,
53569
+ "6h": 360,
53570
+ "8h": 480,
53571
+ "1d": 1440,
53572
+ "1w": 10080,
53573
+ };
53574
+ /**
53575
+ * Aligns timestamp down to the nearest interval boundary.
53576
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
53577
+ *
53578
+ * @param timestamp - Timestamp in milliseconds
53579
+ * @param interval - Candle interval
53580
+ * @returns Aligned timestamp rounded down to interval boundary
53581
+ * @throws Error if interval is unknown
53582
+ *
53583
+ * @example
53584
+ * ```typescript
53585
+ * // Align to 15-minute boundary
53586
+ * const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
53587
+ * // Returns timestamp for 2025-10-01T00:30:00Z
53588
+ * ```
53589
+ */
53590
+ const align = (timestamp, interval) => {
53591
+ const intervalMinutes = INTERVAL_MINUTES[interval];
53592
+ if (!intervalMinutes) {
53593
+ throw new Error(`align: unknown interval=${interval}`);
53594
+ }
53595
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
53596
+ return Math.floor(timestamp / intervalMs) * intervalMs;
53597
+ };
53598
+ /**
53599
+ * Build a context key string from strategy name, exchange name, frame name, and execution mode.
53600
+ *
53601
+ * @param strategyName - Name of the strategy
53602
+ * @param exchangeName - Name of the exchange
53603
+ * @param frameName - Name of the backtest frame (omitted in live mode)
53604
+ * @param isBacktest - Whether running in backtest mode
53605
+ * @returns Context key string
53606
+ */
53607
+ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
53608
+ const parts = [strategyName, exchangeName];
53609
+ if (frameName)
53610
+ parts.push(frameName);
53611
+ parts.push(isBacktest ? "backtest" : "live");
53612
+ return parts.join(":");
53613
+ };
53614
+ /**
53615
+ * Instance class for firing a function exactly once per interval boundary.
53616
+ *
53617
+ * On the first call within a new interval the wrapped function is invoked and
53618
+ * its result is returned. Every subsequent call within the same interval returns
53619
+ * `null` without invoking the function again.
53620
+ * If the function itself returns `null`, the interval countdown does not start —
53621
+ * the next call will retry the function.
53622
+ *
53623
+ * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
53624
+ *
53625
+ * @example
53626
+ * ```typescript
53627
+ * const instance = new IntervalFnInstance(mySignalFn, "1h");
53628
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called)
53629
+ * await instance.run("BTCUSDT"); // → null (skipped, same interval)
53630
+ * // After 1 hour passes:
53631
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called again)
53632
+ * ```
53633
+ */
53634
+ class IntervalFnInstance {
53635
+ /**
53636
+ * Creates a new IntervalFnInstance.
53637
+ *
53638
+ * @param fn - Signal function to fire once per interval
53639
+ * @param interval - Candle interval that controls the firing boundary
53640
+ */
53641
+ constructor(fn, interval) {
53642
+ this.fn = fn;
53643
+ this.interval = interval;
53644
+ /** Stores the last aligned timestamp per context+symbol key. */
53645
+ this._stateMap = new Map();
53646
+ /**
53647
+ * Execute the signal function with once-per-interval enforcement.
53648
+ *
53649
+ * Algorithm:
53650
+ * 1. Align the current execution context `when` to the interval boundary.
53651
+ * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
53652
+ * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
53653
+ * the signal. If it returns `null`, leave state unchanged so the next call retries.
53654
+ *
53655
+ * Requires active method context and execution context.
53656
+ *
53657
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
53658
+ * @returns The signal returned by `fn` on the first non-null fire, `null` on all subsequent calls
53659
+ * within the same interval or when `fn` itself returned `null`
53660
+ * @throws Error if method context, execution context, or interval is missing
53661
+ */
53662
+ this.run = async (symbol) => {
53663
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
53664
+ const step = INTERVAL_MINUTES[this.interval];
53665
+ {
53666
+ if (!MethodContextService.hasContext()) {
53667
+ throw new Error("IntervalFnInstance run requires method context");
53668
+ }
53669
+ if (!ExecutionContextService.hasContext()) {
53670
+ throw new Error("IntervalFnInstance run requires execution context");
53671
+ }
53672
+ if (!step) {
53673
+ throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
53674
+ }
53675
+ }
53676
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53677
+ const key = `${contextKey}:${symbol}`;
53678
+ const currentWhen = backtest.executionContextService.context.when;
53679
+ const currentAligned = align(currentWhen.getTime(), this.interval);
53680
+ if (this._stateMap.get(key) === currentAligned) {
53681
+ return null;
53682
+ }
53683
+ const result = await this.fn(symbol, currentWhen);
53684
+ if (result !== null) {
53685
+ this._stateMap.set(key, currentAligned);
53686
+ }
53687
+ return result;
53688
+ };
53689
+ /**
53690
+ * Clear fired-interval state for the current execution context.
53691
+ *
53692
+ * Removes all entries for the current strategy/exchange/frame/mode combination
53693
+ * from this instance's state map. The next `run()` call will invoke the function again.
53694
+ *
53695
+ * Requires active method context and execution context.
53696
+ */
53697
+ this.clear = () => {
53698
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53699
+ const prefix = `${contextKey}:`;
53700
+ for (const key of this._stateMap.keys()) {
53701
+ if (key.startsWith(prefix)) {
53702
+ this._stateMap.delete(key);
53703
+ }
53704
+ }
53705
+ };
53706
+ }
53707
+ }
53708
+ /**
53709
+ * Instance class for firing an async function exactly once per interval boundary,
53710
+ * with the fired state persisted to disk via `PersistIntervalAdapter`.
53711
+ *
53712
+ * On the first call within a new interval the wrapped function is invoked.
53713
+ * If it returns a non-null signal, that result is written to disk and returned.
53714
+ * Every subsequent call within the same interval returns `null` (record exists on disk).
53715
+ * If the function returns `null`, nothing is written and the next call retries.
53716
+ *
53717
+ * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
53718
+ *
53719
+ * @template T - Async function type: `(symbol: string, ...args) => Promise<ISignalIntervalDto | null>`
53720
+ *
53721
+ * @example
53722
+ * ```typescript
53723
+ * const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
53724
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called, result written to disk)
53725
+ * await instance.run("BTCUSDT"); // → null (record exists, already fired)
53726
+ * ```
53727
+ */
53728
+ class IntervalFileInstance {
53729
+ /**
53730
+ * Allocates a new unique index. Called once in the constructor to give each
53731
+ * IntervalFileInstance its own namespace in the persistent key space.
53732
+ */
53733
+ static createIndex() {
53734
+ return IntervalFileInstance._indexCounter++;
53735
+ }
53736
+ /**
53737
+ * Resets the index counter to zero.
53738
+ * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
53739
+ */
53740
+ static clearCounter() {
53741
+ IntervalFileInstance._indexCounter = 0;
53742
+ }
53743
+ /**
53744
+ * Creates a new IntervalFileInstance.
53745
+ *
53746
+ * @param fn - Async signal function to fire once per interval
53747
+ * @param interval - Candle interval that controls the firing boundary
53748
+ * @param name - Human-readable bucket name used as the directory prefix
53749
+ */
53750
+ constructor(fn, interval, name) {
53751
+ this.fn = fn;
53752
+ this.interval = interval;
53753
+ this.name = name;
53754
+ /**
53755
+ * Execute the async signal function with persistent once-per-interval enforcement.
53756
+ *
53757
+ * Algorithm:
53758
+ * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
53759
+ * 2. Align execution context `when` to interval boundary → `alignedTs`.
53760
+ * 3. Build entity key = `${symbol}_${alignedTs}`.
53761
+ * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
53762
+ * 5. On hit — return `null` (interval already fired).
53763
+ * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
53764
+ *
53765
+ * Requires active method context and execution context.
53766
+ *
53767
+ * @param args - Arguments forwarded to the wrapped function (first must be `symbol: string`)
53768
+ * @returns The signal on the first non-null fire, `null` if already fired this interval
53769
+ * or if `fn` itself returned `null`
53770
+ * @throws Error if method context, execution context, or interval is missing
53771
+ */
53772
+ this.run = async (...args) => {
53773
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
53774
+ const step = INTERVAL_MINUTES[this.interval];
53775
+ {
53776
+ if (!MethodContextService.hasContext()) {
53777
+ throw new Error("IntervalFileInstance run requires method context");
53778
+ }
53779
+ if (!ExecutionContextService.hasContext()) {
53780
+ throw new Error("IntervalFileInstance run requires execution context");
53781
+ }
53782
+ if (!step) {
53783
+ throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
53784
+ }
53785
+ }
53786
+ const [symbol] = args;
53787
+ const { when } = backtest.executionContextService.context;
53788
+ const alignedTs = align(when.getTime(), this.interval);
53789
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53790
+ const entityKey = `${symbol}_${alignedTs}`;
53791
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
53792
+ if (cached !== null) {
53793
+ return null;
53794
+ }
53795
+ const result = await this.fn.call(null, ...args);
53796
+ if (result !== null) {
53797
+ await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
53798
+ }
53799
+ return result;
53800
+ };
53801
+ /**
53802
+ * Soft-delete all persisted records for this instance's bucket.
53803
+ * After this call the function will fire again on the next `run()`.
53804
+ */
53805
+ this.clear = async () => {
53806
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53807
+ for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
53808
+ await PersistIntervalAdapter.removeIntervalData(bucket, key);
53809
+ }
53810
+ };
53811
+ this.index = IntervalFileInstance.createIndex();
53812
+ }
53813
+ }
53814
+ /** Global counter — incremented once per IntervalFileInstance construction. */
53815
+ IntervalFileInstance._indexCounter = 0;
53816
+ /**
53817
+ * Utility class for wrapping signal functions with once-per-interval firing.
53818
+ * Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
53819
+ * Exported as singleton instance `Interval` for convenient usage.
53820
+ *
53821
+ * @example
53822
+ * ```typescript
53823
+ * import { Interval } from "./classes/Interval";
53824
+ *
53825
+ * const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
53826
+ * await fireOncePerHour("BTCUSDT", when); // fn called — returns its result
53827
+ * await fireOncePerHour("BTCUSDT", when); // returns null (same interval)
53828
+ * ```
53829
+ */
53830
+ class IntervalUtils {
53831
+ constructor() {
53832
+ /**
53833
+ * Memoized factory to get or create an `IntervalFnInstance` for a function.
53834
+ * Each function reference gets its own isolated instance.
53835
+ */
53836
+ this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
53837
+ /**
53838
+ * Memoized factory to get or create an `IntervalFileInstance` for an async function.
53839
+ * Each function reference gets its own isolated persistent instance.
53840
+ */
53841
+ this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
53842
+ /**
53843
+ * Wrap a signal function with in-memory once-per-interval firing.
53844
+ *
53845
+ * Returns a wrapped version of the function that fires at most once per interval boundary.
53846
+ * If the function returns `null`, the countdown does not start and the next call retries.
53847
+ *
53848
+ * The `run` function reference is used as the memoization key for the underlying
53849
+ * `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
53850
+ *
53851
+ * @param run - Signal function to wrap
53852
+ * @param context.interval - Candle interval that controls the firing boundary
53853
+ * @returns Wrapped function with the same signature as `TIntervalFn`, plus a `clear()` method
53854
+ *
53855
+ * @example
53856
+ * ```typescript
53857
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
53858
+ *
53859
+ * await fireOnce("BTCUSDT", when); // → signal or null (fn called)
53860
+ * await fireOnce("BTCUSDT", when); // → null (same interval, skipped)
53861
+ * ```
53862
+ */
53863
+ this.fn = (run, context) => {
53864
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
53865
+ const wrappedFn = (symbol, _when) => {
53866
+ const instance = this._getInstance(run, context.interval);
53867
+ return instance.run(symbol);
53868
+ };
53869
+ wrappedFn.clear = () => {
53870
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
53871
+ if (!MethodContextService.hasContext()) {
53872
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
53873
+ return;
53874
+ }
53875
+ if (!ExecutionContextService.hasContext()) {
53876
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
53877
+ return;
53878
+ }
53879
+ this._getInstance.get(run)?.clear();
53880
+ };
53881
+ return wrappedFn;
53882
+ };
53883
+ /**
53884
+ * Wrap an async signal function with persistent file-based once-per-interval firing.
53885
+ *
53886
+ * Returns a wrapped version of the function that reads from disk on hit (returns `null`)
53887
+ * and writes the fired signal to disk on the first successful fire.
53888
+ * Fired state survives process restarts.
53889
+ *
53890
+ * The `run` function reference is used as the memoization key for the underlying
53891
+ * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
53892
+ *
53893
+ * @template T - Async function type to wrap
53894
+ * @param run - Async signal function to wrap with persistent once-per-interval firing
53895
+ * @param context.interval - Candle interval that controls the firing boundary
53896
+ * @param context.name - Human-readable bucket name; becomes the directory prefix
53897
+ * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
53898
+ * that deletes persisted records from disk and disposes the memoized instance
53899
+ *
53900
+ * @example
53901
+ * ```typescript
53902
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
53903
+ * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
53904
+ * await fireOnce.clear(); // delete disk records so the function fires again next call
53905
+ * ```
53906
+ */
53907
+ this.file = (run, context) => {
53908
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
53909
+ {
53910
+ this._getFileInstance(run, context.interval, context.name);
53911
+ }
53912
+ const wrappedFn = (...args) => {
53913
+ const instance = this._getFileInstance(run, context.interval, context.name);
53914
+ return instance.run(...args);
53915
+ };
53916
+ wrappedFn.clear = async () => {
53917
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
53918
+ await this._getFileInstance.get(run)?.clear();
53919
+ };
53920
+ return wrappedFn;
53921
+ };
53922
+ /**
53923
+ * Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
53924
+ *
53925
+ * Removes the instance from the internal memoization cache, discarding all in-memory
53926
+ * fired-interval state across all contexts for that function.
53927
+ * The next call to the wrapped function will create a fresh `IntervalFnInstance`.
53928
+ *
53929
+ * @param run - Function whose `IntervalFnInstance` should be disposed
53930
+ *
53931
+ * @example
53932
+ * ```typescript
53933
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
53934
+ * Interval.dispose(mySignalFn);
53935
+ * ```
53936
+ */
53937
+ this.dispose = (run) => {
53938
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
53939
+ this._getInstance.clear(run);
53940
+ };
53941
+ /**
53942
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
53943
+ * resets the `IntervalFileInstance` index counter.
53944
+ * Call this when `process.cwd()` changes between strategy iterations
53945
+ * so new instances are created with the updated base path.
53946
+ */
53947
+ this.clear = () => {
53948
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
53949
+ this._getInstance.clear();
53950
+ this._getFileInstance.clear();
53951
+ IntervalFileInstance.clearCounter();
53952
+ };
53953
+ }
53954
+ }
53955
+ /**
53956
+ * Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
53957
+ *
53958
+ * @example
53959
+ * ```typescript
53960
+ * import { Interval } from "./classes/Interval";
53961
+ *
53962
+ * // In-memory: fires once per hour, resets on process restart
53963
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
53964
+ *
53965
+ * // Persistent: fired state survives restarts
53966
+ * const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
53967
+ * ```
53968
+ */
53969
+ const Interval = new IntervalUtils();
53970
+
53308
53971
  const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
53309
53972
  const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
53310
53973
  const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
@@ -54419,4 +55082,4 @@ const validateSignal = (signal, currentPrice) => {
54419
55082
  return !errors.length;
54420
55083
  };
54421
55084
 
54422
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
55085
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };