backtest-kit 6.8.0 → 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.cjs CHANGED
@@ -908,7 +908,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
908
908
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
909
909
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
910
910
  // Calculate step in milliseconds for candle close time validation
911
- const INTERVAL_MINUTES$8 = {
911
+ const INTERVAL_MINUTES$9 = {
912
912
  "1m": 1,
913
913
  "3m": 3,
914
914
  "5m": 5,
@@ -922,7 +922,7 @@ const INTERVAL_MINUTES$8 = {
922
922
  "1d": 1440,
923
923
  "1w": 10080,
924
924
  };
925
- const MS_PER_MINUTE$6 = 60000;
925
+ const MS_PER_MINUTE$7 = 60000;
926
926
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
927
927
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
928
928
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -981,8 +981,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
981
981
  const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
982
982
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
983
983
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
984
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
985
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
984
986
  const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
985
987
  const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
988
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
989
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
990
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
991
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
992
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
993
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
994
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
995
+ const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
986
996
  const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
987
997
  const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
988
998
  const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
@@ -1895,7 +1905,7 @@ class PersistCandleUtils {
1895
1905
  const isInitial = !this.getCandlesStorage.has(key);
1896
1906
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1897
1907
  await stateStorage.waitForInit(isInitial);
1898
- const stepMs = INTERVAL_MINUTES$8[interval] * MS_PER_MINUTE$6;
1908
+ const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
1899
1909
  // Calculate expected timestamps and fetch each candle directly
1900
1910
  const cachedCandles = [];
1901
1911
  for (let i = 0; i < limit; i++) {
@@ -1951,7 +1961,7 @@ class PersistCandleUtils {
1951
1961
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1952
1962
  await stateStorage.waitForInit(isInitial);
1953
1963
  // Calculate step in milliseconds to determine candle close time
1954
- const stepMs = INTERVAL_MINUTES$8[interval] * MS_PER_MINUTE$6;
1964
+ const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
1955
1965
  const now = Date.now();
1956
1966
  // Write each candle as a separate file, skipping incomplete candles
1957
1967
  for (const candle of candles) {
@@ -2377,7 +2387,8 @@ class PersistMeasureUtils {
2377
2387
  const stateStorage = this.getMeasureStorage(bucket);
2378
2388
  await stateStorage.waitForInit(isInitial);
2379
2389
  if (await stateStorage.hasValue(key)) {
2380
- return await stateStorage.readValue(key);
2390
+ const data = await stateStorage.readValue(key);
2391
+ return data.removed ? null : data;
2381
2392
  }
2382
2393
  return null;
2383
2394
  };
@@ -2399,6 +2410,27 @@ class PersistMeasureUtils {
2399
2410
  await stateStorage.waitForInit(isInitial);
2400
2411
  await stateStorage.writeValue(key, data);
2401
2412
  };
2413
+ /**
2414
+ * Marks a cached entry as removed (soft delete — file is kept on disk).
2415
+ * After this call `readMeasureData` for the same key returns `null`.
2416
+ *
2417
+ * @param bucket - Storage bucket
2418
+ * @param key - Dynamic cache key within the bucket
2419
+ * @returns Promise that resolves when removal is complete
2420
+ */
2421
+ this.removeMeasureData = async (bucket, key) => {
2422
+ LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
2423
+ bucket,
2424
+ key,
2425
+ });
2426
+ const isInitial = !this.getMeasureStorage.has(bucket);
2427
+ const stateStorage = this.getMeasureStorage(bucket);
2428
+ await stateStorage.waitForInit(isInitial);
2429
+ const data = await stateStorage.readValue(key);
2430
+ if (data) {
2431
+ await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
2432
+ }
2433
+ };
2402
2434
  }
2403
2435
  /**
2404
2436
  * Registers a custom persistence adapter.
@@ -2409,6 +2441,27 @@ class PersistMeasureUtils {
2409
2441
  LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
2410
2442
  this.PersistMeasureFactory = Ctor;
2411
2443
  }
2444
+ /**
2445
+ * Async generator yielding all non-removed entity keys for a given bucket.
2446
+ * Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
2447
+ *
2448
+ * @param bucket - Storage bucket
2449
+ * @returns AsyncGenerator yielding entity keys
2450
+ */
2451
+ async *listMeasureData(bucket) {
2452
+ LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
2453
+ const isInitial = !this.getMeasureStorage.has(bucket);
2454
+ const stateStorage = this.getMeasureStorage(bucket);
2455
+ await stateStorage.waitForInit(isInitial);
2456
+ for await (const key of stateStorage.keys()) {
2457
+ const data = await stateStorage.readValue(String(key));
2458
+ if (data === null || data.removed) {
2459
+ continue;
2460
+ }
2461
+ yield String(key);
2462
+ }
2463
+ }
2464
+ ;
2412
2465
  /**
2413
2466
  * Clears the memoized storage cache.
2414
2467
  * Call this when process.cwd() changes between strategy iterations
@@ -2438,6 +2491,140 @@ class PersistMeasureUtils {
2438
2491
  * Used by Cache.file for persistent caching of external API responses.
2439
2492
  */
2440
2493
  const PersistMeasureAdapter = new PersistMeasureUtils();
2494
+ /**
2495
+ * Persistence layer for Interval.file once-per-interval signal firing.
2496
+ *
2497
+ * Stores fired-interval markers under `./dump/data/interval/`.
2498
+ * A record's presence means the interval has already fired for that bucket+key;
2499
+ * absence means the function has not yet fired (or returned null last time).
2500
+ */
2501
+ class PersistIntervalUtils {
2502
+ constructor() {
2503
+ this.PersistIntervalFactory = PersistBase;
2504
+ this.getIntervalStorage = functoolsKit.memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
2505
+ bucket,
2506
+ `./dump/data/interval/`,
2507
+ ]));
2508
+ /**
2509
+ * Reads interval data for a given bucket and key.
2510
+ *
2511
+ * @param bucket - Storage bucket (instance name + interval + index)
2512
+ * @param key - Entity key within the bucket (symbol + aligned timestamp)
2513
+ * @returns Promise resolving to stored value or null if not found
2514
+ */
2515
+ this.readIntervalData = async (bucket, key) => {
2516
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
2517
+ bucket,
2518
+ key,
2519
+ });
2520
+ const isInitial = !this.getIntervalStorage.has(bucket);
2521
+ const stateStorage = this.getIntervalStorage(bucket);
2522
+ await stateStorage.waitForInit(isInitial);
2523
+ if (await stateStorage.hasValue(key)) {
2524
+ const data = await stateStorage.readValue(key);
2525
+ return data.removed ? null : data;
2526
+ }
2527
+ return null;
2528
+ };
2529
+ /**
2530
+ * Writes interval data to disk.
2531
+ *
2532
+ * @param data - Data to store
2533
+ * @param bucket - Storage bucket
2534
+ * @param key - Entity key within the bucket
2535
+ * @returns Promise that resolves when write is complete
2536
+ */
2537
+ this.writeIntervalData = async (data, bucket, key) => {
2538
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
2539
+ bucket,
2540
+ key,
2541
+ });
2542
+ const isInitial = !this.getIntervalStorage.has(bucket);
2543
+ const stateStorage = this.getIntervalStorage(bucket);
2544
+ await stateStorage.waitForInit(isInitial);
2545
+ await stateStorage.writeValue(key, data);
2546
+ };
2547
+ /**
2548
+ * Marks an interval entry as removed (soft delete — file is kept on disk).
2549
+ * After this call `readIntervalData` for the same key returns `null`,
2550
+ * so the function will fire again on the next `IntervalFileInstance.run` call.
2551
+ *
2552
+ * @param bucket - Storage bucket
2553
+ * @param key - Entity key within the bucket
2554
+ * @returns Promise that resolves when removal is complete
2555
+ */
2556
+ this.removeIntervalData = async (bucket, key) => {
2557
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
2558
+ bucket,
2559
+ key,
2560
+ });
2561
+ const isInitial = !this.getIntervalStorage.has(bucket);
2562
+ const stateStorage = this.getIntervalStorage(bucket);
2563
+ await stateStorage.waitForInit(isInitial);
2564
+ const data = await stateStorage.readValue(key);
2565
+ if (data) {
2566
+ await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
2567
+ }
2568
+ };
2569
+ }
2570
+ /**
2571
+ * Registers a custom persistence adapter.
2572
+ *
2573
+ * @param Ctor - Custom PersistBase constructor
2574
+ */
2575
+ usePersistIntervalAdapter(Ctor) {
2576
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
2577
+ this.PersistIntervalFactory = Ctor;
2578
+ }
2579
+ /**
2580
+ * Async generator yielding all non-removed entity keys for a given bucket.
2581
+ * Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
2582
+ *
2583
+ * @param bucket - Storage bucket
2584
+ * @returns AsyncGenerator yielding entity keys
2585
+ */
2586
+ async *listIntervalData(bucket) {
2587
+ LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
2588
+ const isInitial = !this.getIntervalStorage.has(bucket);
2589
+ const stateStorage = this.getIntervalStorage(bucket);
2590
+ await stateStorage.waitForInit(isInitial);
2591
+ for await (const key of stateStorage.keys()) {
2592
+ const data = await stateStorage.readValue(String(key));
2593
+ if (data === null || data.removed) {
2594
+ continue;
2595
+ }
2596
+ yield String(key);
2597
+ }
2598
+ }
2599
+ ;
2600
+ /**
2601
+ * Clears the memoized storage cache.
2602
+ * Call this when process.cwd() changes between strategy iterations.
2603
+ */
2604
+ clear() {
2605
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
2606
+ this.getIntervalStorage.clear();
2607
+ }
2608
+ /**
2609
+ * Switches to the default JSON persist adapter.
2610
+ */
2611
+ useJson() {
2612
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
2613
+ this.usePersistIntervalAdapter(PersistBase);
2614
+ }
2615
+ /**
2616
+ * Switches to a dummy persist adapter that discards all writes.
2617
+ */
2618
+ useDummy() {
2619
+ LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
2620
+ this.usePersistIntervalAdapter(PersistDummy);
2621
+ }
2622
+ }
2623
+ /**
2624
+ * Global singleton instance of PersistIntervalUtils.
2625
+ * Used by Interval.file for persistent once-per-interval signal firing.
2626
+ */
2627
+ const PersistIntervalAdapter = new PersistIntervalUtils();
2441
2628
  /**
2442
2629
  * Utility class for managing memory entry persistence.
2443
2630
  *
@@ -2785,8 +2972,8 @@ class CandleUtils {
2785
2972
  }
2786
2973
  const Candle = new CandleUtils();
2787
2974
 
2788
- const MS_PER_MINUTE$5 = 60000;
2789
- const INTERVAL_MINUTES$7 = {
2975
+ const MS_PER_MINUTE$6 = 60000;
2976
+ const INTERVAL_MINUTES$8 = {
2790
2977
  "1m": 1,
2791
2978
  "3m": 3,
2792
2979
  "5m": 5,
@@ -2818,7 +3005,7 @@ const INTERVAL_MINUTES$7 = {
2818
3005
  * @returns Aligned timestamp rounded down to interval boundary
2819
3006
  */
2820
3007
  const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
2821
- const intervalMs = intervalMinutes * MS_PER_MINUTE$5;
3008
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
2822
3009
  return Math.floor(timestamp / intervalMs) * intervalMs;
2823
3010
  };
2824
3011
  /**
@@ -2972,9 +3159,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
2972
3159
  * @returns Promise resolving to array of candle data
2973
3160
  */
2974
3161
  const GET_CANDLES_FN$1 = async (dto, since, self) => {
2975
- const step = INTERVAL_MINUTES$7[dto.interval];
3162
+ const step = INTERVAL_MINUTES$8[dto.interval];
2976
3163
  const sinceTimestamp = since.getTime();
2977
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$5;
3164
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
2978
3165
  await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
2979
3166
  try {
2980
3167
  // Try to read from cache first
@@ -3088,11 +3275,11 @@ class ClientExchange {
3088
3275
  interval,
3089
3276
  limit,
3090
3277
  });
3091
- const step = INTERVAL_MINUTES$7[interval];
3278
+ const step = INTERVAL_MINUTES$8[interval];
3092
3279
  if (!step) {
3093
3280
  throw new Error(`ClientExchange unknown interval=${interval}`);
3094
3281
  }
3095
- const stepMs = step * MS_PER_MINUTE$5;
3282
+ const stepMs = step * MS_PER_MINUTE$6;
3096
3283
  // Align when down to interval boundary
3097
3284
  const whenTimestamp = this.params.execution.context.when.getTime();
3098
3285
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
@@ -3169,11 +3356,11 @@ class ClientExchange {
3169
3356
  if (!this.params.execution.context.backtest) {
3170
3357
  throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
3171
3358
  }
3172
- const step = INTERVAL_MINUTES$7[interval];
3359
+ const step = INTERVAL_MINUTES$8[interval];
3173
3360
  if (!step) {
3174
3361
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
3175
3362
  }
3176
- const stepMs = step * MS_PER_MINUTE$5;
3363
+ const stepMs = step * MS_PER_MINUTE$6;
3177
3364
  const now = Date.now();
3178
3365
  // Align when down to interval boundary
3179
3366
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -3337,11 +3524,11 @@ class ClientExchange {
3337
3524
  sDate,
3338
3525
  eDate,
3339
3526
  });
3340
- const step = INTERVAL_MINUTES$7[interval];
3527
+ const step = INTERVAL_MINUTES$8[interval];
3341
3528
  if (!step) {
3342
3529
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
3343
3530
  }
3344
- const stepMs = step * MS_PER_MINUTE$5;
3531
+ const stepMs = step * MS_PER_MINUTE$6;
3345
3532
  const whenTimestamp = this.params.execution.context.when.getTime();
3346
3533
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
3347
3534
  let sinceTimestamp;
@@ -3470,7 +3657,7 @@ class ClientExchange {
3470
3657
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
3471
3658
  const to = new Date(alignedTo);
3472
3659
  const from = new Date(alignedTo -
3473
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$5);
3660
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
3474
3661
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
3475
3662
  }
3476
3663
  /**
@@ -3499,7 +3686,7 @@ class ClientExchange {
3499
3686
  const whenTimestamp = this.params.execution.context.when.getTime();
3500
3687
  // Align to 1-minute boundary to prevent look-ahead bias
3501
3688
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
3502
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$5 - MS_PER_MINUTE$5;
3689
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
3503
3690
  // No limit: fetch a single window and return as-is
3504
3691
  if (limit === undefined) {
3505
3692
  const to = new Date(alignedTo);
@@ -4478,7 +4665,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
4478
4665
  }
4479
4666
  };
4480
4667
 
4481
- const INTERVAL_MINUTES$6 = {
4668
+ const INTERVAL_MINUTES$7 = {
4482
4669
  "1m": 1,
4483
4670
  "3m": 3,
4484
4671
  "5m": 5,
@@ -4862,7 +5049,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4862
5049
  }
4863
5050
  const currentTime = self.params.execution.context.when.getTime();
4864
5051
  {
4865
- const intervalMinutes = INTERVAL_MINUTES$6[self.params.interval];
5052
+ const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
4866
5053
  const intervalMs = intervalMinutes * 60 * 1000;
4867
5054
  const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
4868
5055
  // Проверяем что наступил новый интервал (по aligned timestamp)
@@ -9886,7 +10073,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
9886
10073
  * @param backtest - Whether running in backtest mode
9887
10074
  * @returns Unique string key for memoization
9888
10075
  */
9889
- const CREATE_KEY_FN$s = (symbol, strategyName, exchangeName, frameName, backtest) => {
10076
+ const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
9890
10077
  const parts = [symbol, strategyName, exchangeName];
9891
10078
  if (frameName)
9892
10079
  parts.push(frameName);
@@ -10153,7 +10340,7 @@ class StrategyConnectionService {
10153
10340
  * @param backtest - Whether running in backtest mode
10154
10341
  * @returns Configured ClientStrategy instance
10155
10342
  */
10156
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10343
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10157
10344
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10158
10345
  return new ClientStrategy({
10159
10346
  symbol,
@@ -10948,7 +11135,7 @@ class StrategyConnectionService {
10948
11135
  }
10949
11136
  return;
10950
11137
  }
10951
- const key = CREATE_KEY_FN$s(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11138
+ const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
10952
11139
  if (!this.getStrategy.has(key)) {
10953
11140
  return;
10954
11141
  }
@@ -11350,7 +11537,7 @@ class StrategyConnectionService {
11350
11537
  * Maps FrameInterval to minutes for timestamp calculation.
11351
11538
  * Used to generate timeframe arrays with proper spacing.
11352
11539
  */
11353
- const INTERVAL_MINUTES$5 = {
11540
+ const INTERVAL_MINUTES$6 = {
11354
11541
  "1m": 1,
11355
11542
  "3m": 3,
11356
11543
  "5m": 5,
@@ -11406,7 +11593,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
11406
11593
  symbol,
11407
11594
  });
11408
11595
  const { interval, startDate, endDate } = self.params;
11409
- const intervalMinutes = INTERVAL_MINUTES$5[interval];
11596
+ const intervalMinutes = INTERVAL_MINUTES$6[interval];
11410
11597
  if (!intervalMinutes) {
11411
11598
  throw new Error(`ClientFrame unknown interval: ${interval}`);
11412
11599
  }
@@ -11771,8 +11958,8 @@ const get = (object, path) => {
11771
11958
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
11772
11959
  };
11773
11960
 
11774
- const MS_PER_MINUTE$4 = 60000;
11775
- const INTERVAL_MINUTES$4 = {
11961
+ const MS_PER_MINUTE$5 = 60000;
11962
+ const INTERVAL_MINUTES$5 = {
11776
11963
  "1m": 1,
11777
11964
  "3m": 3,
11778
11965
  "5m": 5,
@@ -11804,11 +11991,11 @@ const INTERVAL_MINUTES$4 = {
11804
11991
  * @returns New Date aligned down to interval boundary
11805
11992
  */
11806
11993
  const alignToInterval = (date, interval) => {
11807
- const minutes = INTERVAL_MINUTES$4[interval];
11994
+ const minutes = INTERVAL_MINUTES$5[interval];
11808
11995
  if (minutes === undefined) {
11809
11996
  throw new Error(`alignToInterval: unknown interval=${interval}`);
11810
11997
  }
11811
- const intervalMs = minutes * MS_PER_MINUTE$4;
11998
+ const intervalMs = minutes * MS_PER_MINUTE$5;
11812
11999
  return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
11813
12000
  };
11814
12001
 
@@ -12117,7 +12304,7 @@ class ClientRisk {
12117
12304
  * @param backtest - Whether running in backtest mode
12118
12305
  * @returns Unique string key for memoization
12119
12306
  */
12120
- const CREATE_KEY_FN$r = (riskName, exchangeName, frameName, backtest) => {
12307
+ const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12121
12308
  const parts = [riskName, exchangeName];
12122
12309
  if (frameName)
12123
12310
  parts.push(frameName);
@@ -12217,7 +12404,7 @@ class RiskConnectionService {
12217
12404
  * @param backtest - True if backtest mode, false if live mode
12218
12405
  * @returns Configured ClientRisk instance
12219
12406
  */
12220
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12407
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12221
12408
  const schema = this.riskSchemaService.get(riskName);
12222
12409
  return new ClientRisk({
12223
12410
  ...schema,
@@ -12286,7 +12473,7 @@ class RiskConnectionService {
12286
12473
  payload,
12287
12474
  });
12288
12475
  if (payload) {
12289
- const key = CREATE_KEY_FN$r(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12476
+ const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12290
12477
  this.getRisk.clear(key);
12291
12478
  }
12292
12479
  else {
@@ -13330,7 +13517,7 @@ class ClientAction {
13330
13517
  * @param backtest - Whether running in backtest mode
13331
13518
  * @returns Unique string key for memoization
13332
13519
  */
13333
- const CREATE_KEY_FN$q = (actionName, strategyName, exchangeName, frameName, backtest) => {
13520
+ const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13334
13521
  const parts = [actionName, strategyName, exchangeName];
13335
13522
  if (frameName)
13336
13523
  parts.push(frameName);
@@ -13382,7 +13569,7 @@ class ActionConnectionService {
13382
13569
  * @param backtest - True if backtest mode, false if live mode
13383
13570
  * @returns Configured ClientAction instance
13384
13571
  */
13385
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$q(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13572
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13386
13573
  const schema = this.actionSchemaService.get(actionName);
13387
13574
  return new ClientAction({
13388
13575
  ...schema,
@@ -13593,7 +13780,7 @@ class ActionConnectionService {
13593
13780
  await Promise.all(actions.map(async (action) => await action.dispose()));
13594
13781
  return;
13595
13782
  }
13596
- const key = CREATE_KEY_FN$q(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13783
+ const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13597
13784
  if (!this.getAction.has(key)) {
13598
13785
  return;
13599
13786
  }
@@ -13611,7 +13798,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
13611
13798
  * @param exchangeName - Exchange name
13612
13799
  * @returns Unique string key for memoization
13613
13800
  */
13614
- const CREATE_KEY_FN$p = (exchangeName) => {
13801
+ const CREATE_KEY_FN$q = (exchangeName) => {
13615
13802
  return exchangeName;
13616
13803
  };
13617
13804
  /**
@@ -13635,7 +13822,7 @@ class ExchangeCoreService {
13635
13822
  * @param exchangeName - Name of the exchange to validate
13636
13823
  * @returns Promise that resolves when validation is complete
13637
13824
  */
13638
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$p(exchangeName), async (exchangeName) => {
13825
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
13639
13826
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
13640
13827
  exchangeName,
13641
13828
  });
@@ -13887,7 +14074,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
13887
14074
  * @param context - Execution context with strategyName, exchangeName, frameName
13888
14075
  * @returns Unique string key for memoization
13889
14076
  */
13890
- const CREATE_KEY_FN$o = (context) => {
14077
+ const CREATE_KEY_FN$p = (context) => {
13891
14078
  const parts = [context.strategyName, context.exchangeName];
13892
14079
  if (context.frameName)
13893
14080
  parts.push(context.frameName);
@@ -13919,7 +14106,7 @@ class StrategyCoreService {
13919
14106
  * @param context - Execution context with strategyName, exchangeName, frameName
13920
14107
  * @returns Promise that resolves when validation is complete
13921
14108
  */
13922
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
14109
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
13923
14110
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
13924
14111
  context,
13925
14112
  });
@@ -15143,7 +15330,7 @@ class SizingGlobalService {
15143
15330
  * @param context - Context with riskName, exchangeName, frameName
15144
15331
  * @returns Unique string key for memoization
15145
15332
  */
15146
- const CREATE_KEY_FN$n = (context) => {
15333
+ const CREATE_KEY_FN$o = (context) => {
15147
15334
  const parts = [context.riskName, context.exchangeName];
15148
15335
  if (context.frameName)
15149
15336
  parts.push(context.frameName);
@@ -15169,7 +15356,7 @@ class RiskGlobalService {
15169
15356
  * @param payload - Payload with riskName, exchangeName and frameName
15170
15357
  * @returns Promise that resolves when validation is complete
15171
15358
  */
15172
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15359
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15173
15360
  this.loggerService.log("riskGlobalService validate", {
15174
15361
  context,
15175
15362
  });
@@ -15247,7 +15434,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
15247
15434
  * @param context - Execution context with strategyName, exchangeName, frameName
15248
15435
  * @returns Unique string key for memoization
15249
15436
  */
15250
- const CREATE_KEY_FN$m = (context) => {
15437
+ const CREATE_KEY_FN$n = (context) => {
15251
15438
  const parts = [context.strategyName, context.exchangeName];
15252
15439
  if (context.frameName)
15253
15440
  parts.push(context.frameName);
@@ -15291,7 +15478,7 @@ class ActionCoreService {
15291
15478
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15292
15479
  * @returns Promise that resolves when all validations complete
15293
15480
  */
15294
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$m(context), async (context) => {
15481
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15295
15482
  this.loggerService.log(METHOD_NAME_VALIDATE, {
15296
15483
  context,
15297
15484
  });
@@ -16886,8 +17073,8 @@ class BacktestLogicPrivateService {
16886
17073
  }
16887
17074
 
16888
17075
  const EMITTER_CHECK_INTERVAL = 5000;
16889
- const MS_PER_MINUTE$3 = 60000;
16890
- const INTERVAL_MINUTES$3 = {
17076
+ const MS_PER_MINUTE$4 = 60000;
17077
+ const INTERVAL_MINUTES$4 = {
16891
17078
  "1m": 1,
16892
17079
  "3m": 3,
16893
17080
  "5m": 5,
@@ -16903,7 +17090,7 @@ const INTERVAL_MINUTES$3 = {
16903
17090
  };
16904
17091
  const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
16905
17092
  const tickSubject = new functoolsKit.Subject();
16906
- const intervalMs = INTERVAL_MINUTES$3[interval] * MS_PER_MINUTE$3;
17093
+ const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
16907
17094
  {
16908
17095
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
16909
17096
  functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -20287,7 +20474,7 @@ const ReportWriter = new ReportWriterAdapter();
20287
20474
  * @param backtest - Whether running in backtest mode
20288
20475
  * @returns Unique string key for memoization
20289
20476
  */
20290
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
20477
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20291
20478
  const parts = [symbol, strategyName, exchangeName];
20292
20479
  if (frameName)
20293
20480
  parts.push(frameName);
@@ -20533,7 +20720,7 @@ class BacktestMarkdownService {
20533
20720
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20534
20721
  * Each combination gets its own isolated storage instance.
20535
20722
  */
20536
- this.getStorage = functoolsKit.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));
20723
+ this.getStorage = functoolsKit.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));
20537
20724
  /**
20538
20725
  * Processes tick events and accumulates closed signals.
20539
20726
  * Should be called from IStrategyCallbacks.onTick.
@@ -20690,7 +20877,7 @@ class BacktestMarkdownService {
20690
20877
  payload,
20691
20878
  });
20692
20879
  if (payload) {
20693
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20880
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20694
20881
  this.getStorage.clear(key);
20695
20882
  }
20696
20883
  else {
@@ -20752,7 +20939,7 @@ class BacktestMarkdownService {
20752
20939
  * @param backtest - Whether running in backtest mode
20753
20940
  * @returns Unique string key for memoization
20754
20941
  */
20755
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
20942
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
20756
20943
  const parts = [symbol, strategyName, exchangeName];
20757
20944
  if (frameName)
20758
20945
  parts.push(frameName);
@@ -21247,7 +21434,7 @@ class LiveMarkdownService {
21247
21434
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21248
21435
  * Each combination gets its own isolated storage instance.
21249
21436
  */
21250
- this.getStorage = functoolsKit.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));
21437
+ this.getStorage = functoolsKit.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));
21251
21438
  /**
21252
21439
  * Subscribes to live signal emitter to receive tick events.
21253
21440
  * Protected against multiple subscriptions.
@@ -21465,7 +21652,7 @@ class LiveMarkdownService {
21465
21652
  payload,
21466
21653
  });
21467
21654
  if (payload) {
21468
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21655
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21469
21656
  this.getStorage.clear(key);
21470
21657
  }
21471
21658
  else {
@@ -21485,7 +21672,7 @@ class LiveMarkdownService {
21485
21672
  * @param backtest - Whether running in backtest mode
21486
21673
  * @returns Unique string key for memoization
21487
21674
  */
21488
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
21675
+ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
21489
21676
  const parts = [symbol, strategyName, exchangeName];
21490
21677
  if (frameName)
21491
21678
  parts.push(frameName);
@@ -21774,7 +21961,7 @@ class ScheduleMarkdownService {
21774
21961
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21775
21962
  * Each combination gets its own isolated storage instance.
21776
21963
  */
21777
- this.getStorage = functoolsKit.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));
21964
+ this.getStorage = functoolsKit.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));
21778
21965
  /**
21779
21966
  * Subscribes to signal emitter to receive scheduled signal events.
21780
21967
  * Protected against multiple subscriptions.
@@ -21977,7 +22164,7 @@ class ScheduleMarkdownService {
21977
22164
  payload,
21978
22165
  });
21979
22166
  if (payload) {
21980
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22167
+ const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21981
22168
  this.getStorage.clear(key);
21982
22169
  }
21983
22170
  else {
@@ -21997,7 +22184,7 @@ class ScheduleMarkdownService {
21997
22184
  * @param backtest - Whether running in backtest mode
21998
22185
  * @returns Unique string key for memoization
21999
22186
  */
22000
- const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
22187
+ const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
22001
22188
  const parts = [symbol, strategyName, exchangeName];
22002
22189
  if (frameName)
22003
22190
  parts.push(frameName);
@@ -22242,7 +22429,7 @@ class PerformanceMarkdownService {
22242
22429
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22243
22430
  * Each combination gets its own isolated storage instance.
22244
22431
  */
22245
- this.getStorage = functoolsKit.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));
22432
+ this.getStorage = functoolsKit.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));
22246
22433
  /**
22247
22434
  * Subscribes to performance emitter to receive performance events.
22248
22435
  * Protected against multiple subscriptions.
@@ -22409,7 +22596,7 @@ class PerformanceMarkdownService {
22409
22596
  payload,
22410
22597
  });
22411
22598
  if (payload) {
22412
- const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22599
+ const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22413
22600
  this.getStorage.clear(key);
22414
22601
  }
22415
22602
  else {
@@ -22888,7 +23075,7 @@ class WalkerMarkdownService {
22888
23075
  * @param backtest - Whether running in backtest mode
22889
23076
  * @returns Unique string key for memoization
22890
23077
  */
22891
- const CREATE_KEY_FN$h = (exchangeName, frameName, backtest) => {
23078
+ const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
22892
23079
  const parts = [exchangeName];
22893
23080
  if (frameName)
22894
23081
  parts.push(frameName);
@@ -23335,7 +23522,7 @@ class HeatMarkdownService {
23335
23522
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23336
23523
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23337
23524
  */
23338
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23525
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23339
23526
  /**
23340
23527
  * Subscribes to signal emitter to receive tick events.
23341
23528
  * Protected against multiple subscriptions.
@@ -23553,7 +23740,7 @@ class HeatMarkdownService {
23553
23740
  payload,
23554
23741
  });
23555
23742
  if (payload) {
23556
- const key = CREATE_KEY_FN$h(payload.exchangeName, payload.frameName, payload.backtest);
23743
+ const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
23557
23744
  this.getStorage.clear(key);
23558
23745
  }
23559
23746
  else {
@@ -24584,7 +24771,7 @@ class ClientPartial {
24584
24771
  * @param backtest - Whether running in backtest mode
24585
24772
  * @returns Unique string key for memoization
24586
24773
  */
24587
- const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24774
+ const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24588
24775
  /**
24589
24776
  * Creates a callback function for emitting profit events to partialProfitSubject.
24590
24777
  *
@@ -24706,7 +24893,7 @@ class PartialConnectionService {
24706
24893
  * Key format: "signalId:backtest" or "signalId:live"
24707
24894
  * Value: ClientPartial instance with logger and event emitters
24708
24895
  */
24709
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
24896
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
24710
24897
  return new ClientPartial({
24711
24898
  signalId,
24712
24899
  logger: this.loggerService,
@@ -24796,7 +24983,7 @@ class PartialConnectionService {
24796
24983
  const partial = this.getPartial(data.id, backtest);
24797
24984
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24798
24985
  await partial.clear(symbol, data, priceClose, backtest);
24799
- const key = CREATE_KEY_FN$g(data.id, backtest);
24986
+ const key = CREATE_KEY_FN$h(data.id, backtest);
24800
24987
  this.getPartial.clear(key);
24801
24988
  };
24802
24989
  }
@@ -24812,7 +24999,7 @@ class PartialConnectionService {
24812
24999
  * @param backtest - Whether running in backtest mode
24813
25000
  * @returns Unique string key for memoization
24814
25001
  */
24815
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
25002
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
24816
25003
  const parts = [symbol, strategyName, exchangeName];
24817
25004
  if (frameName)
24818
25005
  parts.push(frameName);
@@ -25035,7 +25222,7 @@ class PartialMarkdownService {
25035
25222
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25036
25223
  * Each combination gets its own isolated storage instance.
25037
25224
  */
25038
- this.getStorage = functoolsKit.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));
25225
+ this.getStorage = functoolsKit.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));
25039
25226
  /**
25040
25227
  * Subscribes to partial profit/loss signal emitters to receive events.
25041
25228
  * Protected against multiple subscriptions.
@@ -25245,7 +25432,7 @@ class PartialMarkdownService {
25245
25432
  payload,
25246
25433
  });
25247
25434
  if (payload) {
25248
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25435
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25249
25436
  this.getStorage.clear(key);
25250
25437
  }
25251
25438
  else {
@@ -25261,7 +25448,7 @@ class PartialMarkdownService {
25261
25448
  * @param context - Context with strategyName, exchangeName, frameName
25262
25449
  * @returns Unique string key for memoization
25263
25450
  */
25264
- const CREATE_KEY_FN$e = (context) => {
25451
+ const CREATE_KEY_FN$f = (context) => {
25265
25452
  const parts = [context.strategyName, context.exchangeName];
25266
25453
  if (context.frameName)
25267
25454
  parts.push(context.frameName);
@@ -25335,7 +25522,7 @@ class PartialGlobalService {
25335
25522
  * @param context - Context with strategyName, exchangeName and frameName
25336
25523
  * @param methodName - Name of the calling method for error tracking
25337
25524
  */
25338
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
25525
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
25339
25526
  this.loggerService.log("partialGlobalService validate", {
25340
25527
  context,
25341
25528
  methodName,
@@ -25790,7 +25977,7 @@ class ClientBreakeven {
25790
25977
  * @param backtest - Whether running in backtest mode
25791
25978
  * @returns Unique string key for memoization
25792
25979
  */
25793
- const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25980
+ const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25794
25981
  /**
25795
25982
  * Creates a callback function for emitting breakeven events to breakevenSubject.
25796
25983
  *
@@ -25876,7 +26063,7 @@ class BreakevenConnectionService {
25876
26063
  * Key format: "signalId:backtest" or "signalId:live"
25877
26064
  * Value: ClientBreakeven instance with logger and event emitter
25878
26065
  */
25879
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
26066
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
25880
26067
  return new ClientBreakeven({
25881
26068
  signalId,
25882
26069
  logger: this.loggerService,
@@ -25937,7 +26124,7 @@ class BreakevenConnectionService {
25937
26124
  const breakeven = this.getBreakeven(data.id, backtest);
25938
26125
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25939
26126
  await breakeven.clear(symbol, data, priceClose, backtest);
25940
- const key = CREATE_KEY_FN$d(data.id, backtest);
26127
+ const key = CREATE_KEY_FN$e(data.id, backtest);
25941
26128
  this.getBreakeven.clear(key);
25942
26129
  };
25943
26130
  }
@@ -25953,7 +26140,7 @@ class BreakevenConnectionService {
25953
26140
  * @param backtest - Whether running in backtest mode
25954
26141
  * @returns Unique string key for memoization
25955
26142
  */
25956
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
26143
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
25957
26144
  const parts = [symbol, strategyName, exchangeName];
25958
26145
  if (frameName)
25959
26146
  parts.push(frameName);
@@ -26128,7 +26315,7 @@ class BreakevenMarkdownService {
26128
26315
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26129
26316
  * Each combination gets its own isolated storage instance.
26130
26317
  */
26131
- this.getStorage = functoolsKit.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));
26318
+ this.getStorage = functoolsKit.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));
26132
26319
  /**
26133
26320
  * Subscribes to breakeven signal emitter to receive events.
26134
26321
  * Protected against multiple subscriptions.
@@ -26317,7 +26504,7 @@ class BreakevenMarkdownService {
26317
26504
  payload,
26318
26505
  });
26319
26506
  if (payload) {
26320
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26507
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26321
26508
  this.getStorage.clear(key);
26322
26509
  }
26323
26510
  else {
@@ -26333,7 +26520,7 @@ class BreakevenMarkdownService {
26333
26520
  * @param context - Context with strategyName, exchangeName, frameName
26334
26521
  * @returns Unique string key for memoization
26335
26522
  */
26336
- const CREATE_KEY_FN$b = (context) => {
26523
+ const CREATE_KEY_FN$c = (context) => {
26337
26524
  const parts = [context.strategyName, context.exchangeName];
26338
26525
  if (context.frameName)
26339
26526
  parts.push(context.frameName);
@@ -26407,7 +26594,7 @@ class BreakevenGlobalService {
26407
26594
  * @param context - Context with strategyName, exchangeName and frameName
26408
26595
  * @param methodName - Name of the calling method for error tracking
26409
26596
  */
26410
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
26597
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
26411
26598
  this.loggerService.log("breakevenGlobalService validate", {
26412
26599
  context,
26413
26600
  methodName,
@@ -26628,7 +26815,7 @@ class ConfigValidationService {
26628
26815
  * @param backtest - Whether running in backtest mode
26629
26816
  * @returns Unique string key for memoization
26630
26817
  */
26631
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
26818
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
26632
26819
  const parts = [symbol, strategyName, exchangeName];
26633
26820
  if (frameName)
26634
26821
  parts.push(frameName);
@@ -26795,7 +26982,7 @@ class RiskMarkdownService {
26795
26982
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26796
26983
  * Each combination gets its own isolated storage instance.
26797
26984
  */
26798
- this.getStorage = functoolsKit.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));
26985
+ this.getStorage = functoolsKit.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));
26799
26986
  /**
26800
26987
  * Subscribes to risk rejection emitter to receive rejection events.
26801
26988
  * Protected against multiple subscriptions.
@@ -26984,7 +27171,7 @@ class RiskMarkdownService {
26984
27171
  payload,
26985
27172
  });
26986
27173
  if (payload) {
26987
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27174
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26988
27175
  this.getStorage.clear(key);
26989
27176
  }
26990
27177
  else {
@@ -29363,7 +29550,7 @@ class HighestProfitReportService {
29363
29550
  * @returns Colon-separated key string for memoization
29364
29551
  * @internal
29365
29552
  */
29366
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29553
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
29367
29554
  const parts = [symbol, strategyName, exchangeName];
29368
29555
  if (frameName)
29369
29556
  parts.push(frameName);
@@ -29605,7 +29792,7 @@ class StrategyMarkdownService {
29605
29792
  *
29606
29793
  * @internal
29607
29794
  */
29608
- this.getStorage = functoolsKit.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));
29795
+ this.getStorage = functoolsKit.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));
29609
29796
  /**
29610
29797
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
29611
29798
  *
@@ -30173,7 +30360,7 @@ class StrategyMarkdownService {
30173
30360
  this.clear = async (payload) => {
30174
30361
  this.loggerService.log("strategyMarkdownService clear", { payload });
30175
30362
  if (payload) {
30176
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30363
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30177
30364
  this.getStorage.clear(key);
30178
30365
  }
30179
30366
  else {
@@ -30281,7 +30468,7 @@ class StrategyMarkdownService {
30281
30468
  * Creates a unique key for memoizing ReportStorage instances.
30282
30469
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30283
30470
  */
30284
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30471
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30285
30472
  const parts = [symbol, strategyName, exchangeName];
30286
30473
  if (frameName)
30287
30474
  parts.push(frameName);
@@ -30474,7 +30661,7 @@ let ReportStorage$2 = class ReportStorage {
30474
30661
  class SyncMarkdownService {
30475
30662
  constructor() {
30476
30663
  this.loggerService = inject(TYPES.loggerService);
30477
- this.getStorage = functoolsKit.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));
30664
+ this.getStorage = functoolsKit.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));
30478
30665
  /**
30479
30666
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
30480
30667
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -30670,7 +30857,7 @@ class SyncMarkdownService {
30670
30857
  this.clear = async (payload) => {
30671
30858
  this.loggerService.log("syncMarkdownService clear", { payload });
30672
30859
  if (payload) {
30673
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30860
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30674
30861
  this.getStorage.clear(key);
30675
30862
  }
30676
30863
  else {
@@ -30683,7 +30870,7 @@ class SyncMarkdownService {
30683
30870
  /**
30684
30871
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
30685
30872
  */
30686
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30873
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30687
30874
  const parts = [symbol, strategyName, exchangeName];
30688
30875
  if (frameName)
30689
30876
  parts.push(frameName);
@@ -30859,7 +31046,7 @@ let ReportStorage$1 = class ReportStorage {
30859
31046
  class HighestProfitMarkdownService {
30860
31047
  constructor() {
30861
31048
  this.loggerService = inject(TYPES.loggerService);
30862
- this.getStorage = functoolsKit.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));
31049
+ this.getStorage = functoolsKit.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));
30863
31050
  /**
30864
31051
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
30865
31052
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31025,7 +31212,7 @@ class HighestProfitMarkdownService {
31025
31212
  this.clear = async (payload) => {
31026
31213
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31027
31214
  if (payload) {
31028
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31215
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31029
31216
  this.getStorage.clear(key);
31030
31217
  }
31031
31218
  else {
@@ -31047,7 +31234,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31047
31234
  * @param backtest - Whether running in backtest mode
31048
31235
  * @returns Unique string key for memoization
31049
31236
  */
31050
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31237
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31051
31238
  const parts = [symbol, strategyName, exchangeName];
31052
31239
  if (frameName)
31053
31240
  parts.push(frameName);
@@ -31090,7 +31277,7 @@ class PriceMetaService {
31090
31277
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31091
31278
  * Instances are cached until clear() is called.
31092
31279
  */
31093
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31280
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31094
31281
  /**
31095
31282
  * Returns the current market price for the given symbol and context.
31096
31283
  *
@@ -31119,10 +31306,10 @@ class PriceMetaService {
31119
31306
  if (source.data) {
31120
31307
  return source.data;
31121
31308
  }
31122
- 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...`);
31309
+ 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...`);
31123
31310
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31124
31311
  if (typeof currentPrice === "symbol") {
31125
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31312
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31126
31313
  }
31127
31314
  return currentPrice;
31128
31315
  };
@@ -31164,7 +31351,7 @@ class PriceMetaService {
31164
31351
  this.getSource.clear();
31165
31352
  return;
31166
31353
  }
31167
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31354
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31168
31355
  this.getSource.clear(key);
31169
31356
  };
31170
31357
  }
@@ -31182,7 +31369,7 @@ const LISTEN_TIMEOUT = 120000;
31182
31369
  * @param backtest - Whether running in backtest mode
31183
31370
  * @returns Unique string key for memoization
31184
31371
  */
31185
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31372
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31186
31373
  const parts = [symbol, strategyName, exchangeName];
31187
31374
  if (frameName)
31188
31375
  parts.push(frameName);
@@ -31225,7 +31412,7 @@ class TimeMetaService {
31225
31412
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31226
31413
  * Instances are cached until clear() is called.
31227
31414
  */
31228
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31415
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31229
31416
  /**
31230
31417
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31231
31418
  *
@@ -31253,10 +31440,10 @@ class TimeMetaService {
31253
31440
  if (source.data) {
31254
31441
  return source.data;
31255
31442
  }
31256
- 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...`);
31443
+ 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...`);
31257
31444
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31258
31445
  if (typeof timestamp === "symbol") {
31259
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31446
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31260
31447
  }
31261
31448
  return timestamp;
31262
31449
  };
@@ -31298,7 +31485,7 @@ class TimeMetaService {
31298
31485
  this.getSource.clear();
31299
31486
  return;
31300
31487
  }
31301
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31488
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31302
31489
  this.getSource.clear(key);
31303
31490
  };
31304
31491
  }
@@ -31394,7 +31581,7 @@ class MaxDrawdownReportService {
31394
31581
  /**
31395
31582
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31396
31583
  */
31397
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31584
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31398
31585
  const parts = [symbol, strategyName, exchangeName];
31399
31586
  if (frameName)
31400
31587
  parts.push(frameName);
@@ -31518,7 +31705,7 @@ class ReportStorage {
31518
31705
  class MaxDrawdownMarkdownService {
31519
31706
  constructor() {
31520
31707
  this.loggerService = inject(TYPES.loggerService);
31521
- this.getStorage = functoolsKit.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));
31708
+ this.getStorage = functoolsKit.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));
31522
31709
  /**
31523
31710
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
31524
31711
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -31597,7 +31784,7 @@ class MaxDrawdownMarkdownService {
31597
31784
  this.clear = async (payload) => {
31598
31785
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
31599
31786
  if (payload) {
31600
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31787
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31601
31788
  this.getStorage.clear(key);
31602
31789
  }
31603
31790
  else {
@@ -31851,7 +32038,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
31851
32038
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
31852
32039
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
31853
32040
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
31854
- const MS_PER_MINUTE$2 = 60000;
32041
+ const MS_PER_MINUTE$3 = 60000;
31855
32042
  /**
31856
32043
  * Gets current timestamp from execution context if available.
31857
32044
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -31918,7 +32105,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
31918
32105
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
31919
32106
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
31920
32107
  };
31921
- const INTERVAL_MINUTES$2 = {
32108
+ const INTERVAL_MINUTES$3 = {
31922
32109
  "1m": 1,
31923
32110
  "3m": 3,
31924
32111
  "5m": 5,
@@ -31950,7 +32137,7 @@ const INTERVAL_MINUTES$2 = {
31950
32137
  * @returns Aligned timestamp rounded down to interval boundary
31951
32138
  */
31952
32139
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
31953
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
32140
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
31954
32141
  return Math.floor(timestamp / intervalMs) * intervalMs;
31955
32142
  };
31956
32143
  /**
@@ -32090,11 +32277,11 @@ class ExchangeInstance {
32090
32277
  limit,
32091
32278
  });
32092
32279
  const getCandles = this._methods.getCandles;
32093
- const step = INTERVAL_MINUTES$2[interval];
32280
+ const step = INTERVAL_MINUTES$3[interval];
32094
32281
  if (!step) {
32095
32282
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
32096
32283
  }
32097
- const stepMs = step * MS_PER_MINUTE$2;
32284
+ const stepMs = step * MS_PER_MINUTE$3;
32098
32285
  // Align when down to interval boundary
32099
32286
  const when = await GET_TIMESTAMP_FN();
32100
32287
  const whenTimestamp = when.getTime();
@@ -32279,7 +32466,7 @@ class ExchangeInstance {
32279
32466
  const when = await GET_TIMESTAMP_FN();
32280
32467
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
32281
32468
  const to = new Date(alignedTo);
32282
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
32469
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
32283
32470
  const isBacktest = await GET_BACKTEST_FN();
32284
32471
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
32285
32472
  };
@@ -32311,7 +32498,7 @@ class ExchangeInstance {
32311
32498
  const when = await GET_TIMESTAMP_FN();
32312
32499
  // Align to 1-minute boundary to prevent look-ahead bias
32313
32500
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
32314
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$2 - MS_PER_MINUTE$2;
32501
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
32315
32502
  const isBacktest = await GET_BACKTEST_FN();
32316
32503
  // No limit: fetch a single window and return as-is
32317
32504
  if (limit === undefined) {
@@ -32374,11 +32561,11 @@ class ExchangeInstance {
32374
32561
  sDate,
32375
32562
  eDate,
32376
32563
  });
32377
- const step = INTERVAL_MINUTES$2[interval];
32564
+ const step = INTERVAL_MINUTES$3[interval];
32378
32565
  if (!step) {
32379
32566
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
32380
32567
  }
32381
- const stepMs = step * MS_PER_MINUTE$2;
32568
+ const stepMs = step * MS_PER_MINUTE$3;
32382
32569
  const when = await GET_TIMESTAMP_FN();
32383
32570
  const nowTimestamp = when.getTime();
32384
32571
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -32668,8 +32855,8 @@ const Exchange = new ExchangeUtils();
32668
32855
 
32669
32856
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
32670
32857
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
32671
- const MS_PER_MINUTE$1 = 60000;
32672
- const INTERVAL_MINUTES$1 = {
32858
+ const MS_PER_MINUTE$2 = 60000;
32859
+ const INTERVAL_MINUTES$2 = {
32673
32860
  "1m": 1,
32674
32861
  "3m": 3,
32675
32862
  "5m": 5,
@@ -32684,7 +32871,7 @@ const INTERVAL_MINUTES$1 = {
32684
32871
  "1w": 10080,
32685
32872
  };
32686
32873
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
32687
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
32874
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
32688
32875
  return Math.floor(timestamp / intervalMs) * intervalMs;
32689
32876
  };
32690
32877
  const BAR_LENGTH = 30;
@@ -32709,11 +32896,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
32709
32896
  async function checkCandles(params) {
32710
32897
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
32711
32898
  backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
32712
- const step = INTERVAL_MINUTES$1[interval];
32899
+ const step = INTERVAL_MINUTES$2[interval];
32713
32900
  if (!step) {
32714
32901
  throw new Error(`checkCandles: unsupported interval=${interval}`);
32715
32902
  }
32716
- const stepMs = step * MS_PER_MINUTE$1;
32903
+ const stepMs = step * MS_PER_MINUTE$2;
32717
32904
  const dir = path.join(baseDir, exchangeName, symbol, interval);
32718
32905
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
32719
32906
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -32783,11 +32970,11 @@ async function warmCandles(params) {
32783
32970
  from,
32784
32971
  to,
32785
32972
  });
32786
- const step = INTERVAL_MINUTES$1[interval];
32973
+ const step = INTERVAL_MINUTES$2[interval];
32787
32974
  if (!step) {
32788
32975
  throw new Error(`warmCandles: unsupported interval=${interval}`);
32789
32976
  }
32790
- const stepMs = step * MS_PER_MINUTE$1;
32977
+ const stepMs = step * MS_PER_MINUTE$2;
32791
32978
  const instance = new ExchangeInstance(exchangeName);
32792
32979
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
32793
32980
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -44196,7 +44383,7 @@ const createSearchIndex = () => {
44196
44383
  return { upsert, remove, list, search, read };
44197
44384
  };
44198
44385
 
44199
- const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44386
+ const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44200
44387
  const LIST_MEMORY_FN = ({ id, content }) => ({
44201
44388
  memoryId: id,
44202
44389
  content: content,
@@ -44518,7 +44705,7 @@ class MemoryDummyInstance {
44518
44705
  class MemoryAdapter {
44519
44706
  constructor() {
44520
44707
  this.MemoryFactory = MemoryPersistInstance;
44521
- this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
44708
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
44522
44709
  /**
44523
44710
  * Activates the adapter by subscribing to signal lifecycle events.
44524
44711
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -44529,7 +44716,7 @@ class MemoryAdapter {
44529
44716
  this.enable = functoolsKit.singleshot(() => {
44530
44717
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
44531
44718
  const handleDispose = (signalId) => {
44532
- const prefix = CREATE_KEY_FN$3(signalId, "");
44719
+ const prefix = CREATE_KEY_FN$4(signalId, "");
44533
44720
  for (const key of this.getInstance.keys()) {
44534
44721
  if (key.startsWith(prefix)) {
44535
44722
  const instance = this.getInstance.get(key);
@@ -44574,7 +44761,7 @@ class MemoryAdapter {
44574
44761
  bucketName: dto.bucketName,
44575
44762
  memoryId: dto.memoryId,
44576
44763
  });
44577
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44764
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44578
44765
  const isInitial = !this.getInstance.has(key);
44579
44766
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44580
44767
  await instance.waitForInit(isInitial);
@@ -44596,7 +44783,7 @@ class MemoryAdapter {
44596
44783
  bucketName: dto.bucketName,
44597
44784
  query: dto.query,
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);
@@ -44616,7 +44803,7 @@ class MemoryAdapter {
44616
44803
  signalId: dto.signalId,
44617
44804
  bucketName: dto.bucketName,
44618
44805
  });
44619
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44806
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44620
44807
  const isInitial = !this.getInstance.has(key);
44621
44808
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44622
44809
  await instance.waitForInit(isInitial);
@@ -44637,7 +44824,7 @@ class MemoryAdapter {
44637
44824
  bucketName: dto.bucketName,
44638
44825
  memoryId: dto.memoryId,
44639
44826
  });
44640
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44827
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44641
44828
  const isInitial = !this.getInstance.has(key);
44642
44829
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44643
44830
  await instance.waitForInit(isInitial);
@@ -44660,7 +44847,7 @@ class MemoryAdapter {
44660
44847
  bucketName: dto.bucketName,
44661
44848
  memoryId: dto.memoryId,
44662
44849
  });
44663
- const key = CREATE_KEY_FN$3(dto.signalId, dto.bucketName);
44850
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
44664
44851
  const isInitial = !this.getInstance.has(key);
44665
44852
  const instance = this.getInstance(dto.signalId, dto.bucketName);
44666
44853
  await instance.waitForInit(isInitial);
@@ -44952,7 +45139,7 @@ async function removeMemory(dto) {
44952
45139
  });
44953
45140
  }
44954
45141
 
44955
- const CREATE_KEY_FN$2 = (signalId, bucketName) => `${signalId}-${bucketName}`;
45142
+ const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
44956
45143
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
44957
45144
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
44958
45145
  const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
@@ -45572,7 +45759,7 @@ class DumpDummyInstance {
45572
45759
  class DumpAdapter {
45573
45760
  constructor() {
45574
45761
  this.DumpFactory = DumpMarkdownInstance;
45575
- this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$2(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
45762
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
45576
45763
  /**
45577
45764
  * Activates the adapter by subscribing to signal lifecycle events.
45578
45765
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -45583,7 +45770,7 @@ class DumpAdapter {
45583
45770
  this.enable = functoolsKit.singleshot(() => {
45584
45771
  backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
45585
45772
  const handleDispose = (signalId) => {
45586
- const prefix = CREATE_KEY_FN$2(signalId, "");
45773
+ const prefix = CREATE_KEY_FN$3(signalId, "");
45587
45774
  for (const key of this.getInstance.keys()) {
45588
45775
  if (key.startsWith(prefix)) {
45589
45776
  const instance = this.getInstance.get(key);
@@ -48517,6 +48704,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
48517
48704
  };
48518
48705
  const PositionSize = PositionSizeUtils;
48519
48706
 
48707
+ const METHOD_NAME_MOONBAG = "Position.moonbag";
48708
+ const METHOD_NAME_BRACKET = "Position.bracket";
48709
+ /**
48710
+ * Utilities for calculating take profit and stop loss price levels.
48711
+ * Automatically inverts direction based on position type (long/short).
48712
+ */
48713
+ class Position {
48714
+ }
48715
+ /**
48716
+ * Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
48717
+ * @param dto.position - position type: "long" or "short"
48718
+ * @param dto.currentPrice - current asset price
48719
+ * @param dto.percentStopLoss - stop loss percentage from 0 to 100
48720
+ * @returns priceTakeProfit and priceStopLoss in fiat
48721
+ */
48722
+ Position.moonbag = (dto) => {
48723
+ backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
48724
+ const percentTakeProfit = 50;
48725
+ const sign = dto.position === "long" ? 1 : -1;
48726
+ return {
48727
+ position: dto.position,
48728
+ priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
48729
+ priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
48730
+ };
48731
+ };
48732
+ /**
48733
+ * Calculates levels for a bracket order with custom TP and SL.
48734
+ * @param dto.position - position type: "long" or "short"
48735
+ * @param dto.currentPrice - current asset price
48736
+ * @param dto.percentStopLoss - stop loss percentage from 0 to 100
48737
+ * @param dto.percentTakeProfit - take profit percentage from 0 to 100
48738
+ * @returns priceTakeProfit and priceStopLoss in fiat
48739
+ */
48740
+ Position.bracket = (dto) => {
48741
+ backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
48742
+ const sign = dto.position === "long" ? 1 : -1;
48743
+ return {
48744
+ position: dto.position,
48745
+ priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
48746
+ priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
48747
+ };
48748
+ };
48749
+
48520
48750
  const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
48521
48751
  const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
48522
48752
  const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
@@ -50719,7 +50949,7 @@ const StorageBacktest = new StorageBacktestAdapter();
50719
50949
  * Generates a unique key for notification identification.
50720
50950
  * @returns Random string identifier
50721
50951
  */
50722
- const CREATE_KEY_FN$1 = () => functoolsKit.randomString();
50952
+ const CREATE_KEY_FN$2 = () => functoolsKit.randomString();
50723
50953
  /**
50724
50954
  * Creates a notification model from signal tick result.
50725
50955
  * Handles opened, closed, scheduled, and cancelled signal actions.
@@ -50730,7 +50960,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50730
50960
  if (data.action === "opened") {
50731
50961
  return {
50732
50962
  type: "signal.opened",
50733
- id: CREATE_KEY_FN$1(),
50963
+ id: CREATE_KEY_FN$2(),
50734
50964
  timestamp: data.signal.pendingAt,
50735
50965
  backtest: data.backtest,
50736
50966
  symbol: data.symbol,
@@ -50764,7 +50994,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50764
50994
  const durationMin = Math.round(durationMs / 60000);
50765
50995
  return {
50766
50996
  type: "signal.closed",
50767
- id: CREATE_KEY_FN$1(),
50997
+ id: CREATE_KEY_FN$2(),
50768
50998
  timestamp: data.closeTimestamp,
50769
50999
  backtest: data.backtest,
50770
51000
  symbol: data.symbol,
@@ -50798,7 +51028,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50798
51028
  if (data.action === "scheduled") {
50799
51029
  return {
50800
51030
  type: "signal.scheduled",
50801
- id: CREATE_KEY_FN$1(),
51031
+ id: CREATE_KEY_FN$2(),
50802
51032
  timestamp: data.signal.scheduledAt,
50803
51033
  backtest: data.backtest,
50804
51034
  symbol: data.symbol,
@@ -50831,7 +51061,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50831
51061
  const durationMin = Math.round(durationMs / 60000);
50832
51062
  return {
50833
51063
  type: "signal.cancelled",
50834
- id: CREATE_KEY_FN$1(),
51064
+ id: CREATE_KEY_FN$2(),
50835
51065
  timestamp: data.closeTimestamp,
50836
51066
  backtest: data.backtest,
50837
51067
  symbol: data.symbol,
@@ -50864,7 +51094,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
50864
51094
  */
50865
51095
  const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
50866
51096
  type: "partial_profit.available",
50867
- id: CREATE_KEY_FN$1(),
51097
+ id: CREATE_KEY_FN$2(),
50868
51098
  timestamp: data.timestamp,
50869
51099
  backtest: data.backtest,
50870
51100
  symbol: data.symbol,
@@ -50899,7 +51129,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
50899
51129
  */
50900
51130
  const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
50901
51131
  type: "partial_loss.available",
50902
- id: CREATE_KEY_FN$1(),
51132
+ id: CREATE_KEY_FN$2(),
50903
51133
  timestamp: data.timestamp,
50904
51134
  backtest: data.backtest,
50905
51135
  symbol: data.symbol,
@@ -50934,7 +51164,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
50934
51164
  */
50935
51165
  const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
50936
51166
  type: "breakeven.available",
50937
- id: CREATE_KEY_FN$1(),
51167
+ id: CREATE_KEY_FN$2(),
50938
51168
  timestamp: data.timestamp,
50939
51169
  backtest: data.backtest,
50940
51170
  symbol: data.symbol,
@@ -50972,7 +51202,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
50972
51202
  if (data.action === "partial-profit") {
50973
51203
  return {
50974
51204
  type: "partial_profit.commit",
50975
- id: CREATE_KEY_FN$1(),
51205
+ id: CREATE_KEY_FN$2(),
50976
51206
  timestamp: data.timestamp,
50977
51207
  backtest: data.backtest,
50978
51208
  symbol: data.symbol,
@@ -51004,7 +51234,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51004
51234
  if (data.action === "partial-loss") {
51005
51235
  return {
51006
51236
  type: "partial_loss.commit",
51007
- id: CREATE_KEY_FN$1(),
51237
+ id: CREATE_KEY_FN$2(),
51008
51238
  timestamp: data.timestamp,
51009
51239
  backtest: data.backtest,
51010
51240
  symbol: data.symbol,
@@ -51036,7 +51266,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51036
51266
  if (data.action === "breakeven") {
51037
51267
  return {
51038
51268
  type: "breakeven.commit",
51039
- id: CREATE_KEY_FN$1(),
51269
+ id: CREATE_KEY_FN$2(),
51040
51270
  timestamp: data.timestamp,
51041
51271
  backtest: data.backtest,
51042
51272
  symbol: data.symbol,
@@ -51067,7 +51297,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51067
51297
  if (data.action === "trailing-stop") {
51068
51298
  return {
51069
51299
  type: "trailing_stop.commit",
51070
- id: CREATE_KEY_FN$1(),
51300
+ id: CREATE_KEY_FN$2(),
51071
51301
  timestamp: data.timestamp,
51072
51302
  backtest: data.backtest,
51073
51303
  symbol: data.symbol,
@@ -51099,7 +51329,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51099
51329
  if (data.action === "trailing-take") {
51100
51330
  return {
51101
51331
  type: "trailing_take.commit",
51102
- id: CREATE_KEY_FN$1(),
51332
+ id: CREATE_KEY_FN$2(),
51103
51333
  timestamp: data.timestamp,
51104
51334
  backtest: data.backtest,
51105
51335
  symbol: data.symbol,
@@ -51131,7 +51361,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51131
51361
  if (data.action === "activate-scheduled") {
51132
51362
  return {
51133
51363
  type: "activate_scheduled.commit",
51134
- id: CREATE_KEY_FN$1(),
51364
+ id: CREATE_KEY_FN$2(),
51135
51365
  timestamp: data.timestamp,
51136
51366
  backtest: data.backtest,
51137
51367
  symbol: data.symbol,
@@ -51163,7 +51393,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51163
51393
  if (data.action === "average-buy") {
51164
51394
  return {
51165
51395
  type: "average_buy.commit",
51166
- id: CREATE_KEY_FN$1(),
51396
+ id: CREATE_KEY_FN$2(),
51167
51397
  timestamp: data.timestamp,
51168
51398
  backtest: data.backtest,
51169
51399
  symbol: data.symbol,
@@ -51196,7 +51426,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51196
51426
  if (data.action === "cancel-scheduled") {
51197
51427
  return {
51198
51428
  type: "cancel_scheduled.commit",
51199
- id: CREATE_KEY_FN$1(),
51429
+ id: CREATE_KEY_FN$2(),
51200
51430
  timestamp: data.timestamp,
51201
51431
  backtest: data.backtest,
51202
51432
  symbol: data.symbol,
@@ -51219,7 +51449,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51219
51449
  if (data.action === "close-pending") {
51220
51450
  return {
51221
51451
  type: "close_pending.commit",
51222
- id: CREATE_KEY_FN$1(),
51452
+ id: CREATE_KEY_FN$2(),
51223
51453
  timestamp: data.timestamp,
51224
51454
  backtest: data.backtest,
51225
51455
  symbol: data.symbol,
@@ -51251,7 +51481,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51251
51481
  if (data.action === "signal-open") {
51252
51482
  return {
51253
51483
  type: "signal_sync.open",
51254
- id: CREATE_KEY_FN$1(),
51484
+ id: CREATE_KEY_FN$2(),
51255
51485
  timestamp: data.timestamp,
51256
51486
  backtest: data.backtest,
51257
51487
  symbol: data.symbol,
@@ -51283,7 +51513,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51283
51513
  if (data.action === "signal-close") {
51284
51514
  return {
51285
51515
  type: "signal_sync.close",
51286
- id: CREATE_KEY_FN$1(),
51516
+ id: CREATE_KEY_FN$2(),
51287
51517
  timestamp: data.timestamp,
51288
51518
  backtest: data.backtest,
51289
51519
  symbol: data.symbol,
@@ -51321,7 +51551,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51321
51551
  */
51322
51552
  const CREATE_RISK_NOTIFICATION_FN = (data) => ({
51323
51553
  type: "risk.rejection",
51324
- id: CREATE_KEY_FN$1(),
51554
+ id: CREATE_KEY_FN$2(),
51325
51555
  timestamp: data.timestamp,
51326
51556
  backtest: data.backtest,
51327
51557
  symbol: data.symbol,
@@ -51347,7 +51577,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
51347
51577
  */
51348
51578
  const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
51349
51579
  type: "error.info",
51350
- id: CREATE_KEY_FN$1(),
51580
+ id: CREATE_KEY_FN$2(),
51351
51581
  error: functoolsKit.errorData(error),
51352
51582
  message: functoolsKit.getErrorMessage(error),
51353
51583
  backtest: false,
@@ -51359,7 +51589,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
51359
51589
  */
51360
51590
  const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
51361
51591
  type: "error.critical",
51362
- id: CREATE_KEY_FN$1(),
51592
+ id: CREATE_KEY_FN$2(),
51363
51593
  error: functoolsKit.errorData(error),
51364
51594
  message: functoolsKit.getErrorMessage(error),
51365
51595
  backtest: false,
@@ -51371,7 +51601,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
51371
51601
  */
51372
51602
  const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
51373
51603
  type: "error.validation",
51374
- id: CREATE_KEY_FN$1(),
51604
+ id: CREATE_KEY_FN$2(),
51375
51605
  error: functoolsKit.errorData(error),
51376
51606
  message: functoolsKit.getErrorMessage(error),
51377
51607
  backtest: false,
@@ -52800,7 +53030,7 @@ const NotificationLive = new NotificationLiveAdapter();
52800
53030
  */
52801
53031
  const NotificationBacktest = new NotificationBacktestAdapter();
52802
53032
 
52803
- const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
53033
+ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
52804
53034
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
52805
53035
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
52806
53036
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
@@ -52809,8 +53039,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
52809
53039
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
52810
53040
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
52811
53041
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
52812
- const MS_PER_MINUTE = 60000;
52813
- const INTERVAL_MINUTES = {
53042
+ const MS_PER_MINUTE$1 = 60000;
53043
+ const INTERVAL_MINUTES$1 = {
52814
53044
  "1m": 1,
52815
53045
  "3m": 3,
52816
53046
  "5m": 5,
@@ -52844,12 +53074,12 @@ const INTERVAL_MINUTES = {
52844
53074
  * // Returns timestamp for 2025-10-01T01:00:00Z
52845
53075
  * ```
52846
53076
  */
52847
- const align = (timestamp, interval) => {
52848
- const intervalMinutes = INTERVAL_MINUTES[interval];
53077
+ const align$1 = (timestamp, interval) => {
53078
+ const intervalMinutes = INTERVAL_MINUTES$1[interval];
52849
53079
  if (!intervalMinutes) {
52850
53080
  throw new Error(`align: unknown interval=${interval}`);
52851
53081
  }
52852
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
53082
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
52853
53083
  return Math.floor(timestamp / intervalMs) * intervalMs;
52854
53084
  };
52855
53085
  /**
@@ -52860,7 +53090,7 @@ const align = (timestamp, interval) => {
52860
53090
  * @param backtest - Whether running in backtest mode
52861
53091
  * @returns Cache key string
52862
53092
  */
52863
- const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
53093
+ const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
52864
53094
  const parts = [strategyName, exchangeName];
52865
53095
  if (frameName)
52866
53096
  parts.push(frameName);
@@ -52884,16 +53114,16 @@ const NEVER_VALUE = Symbol("never");
52884
53114
  *
52885
53115
  * @example
52886
53116
  * ```typescript
52887
- * const instance = new CacheInstance(myExpensiveFunction, "1h");
53117
+ * const instance = new CacheFnInstance(myExpensiveFunction, "1h");
52888
53118
  * const result = instance.run(arg1, arg2); // Computed
52889
53119
  * const result2 = instance.run(arg1, arg2); // Cached (within same hour)
52890
53120
  * // After 1 hour passes
52891
53121
  * const result3 = instance.run(arg1, arg2); // Recomputed
52892
53122
  * ```
52893
53123
  */
52894
- class CacheInstance {
53124
+ class CacheFnInstance {
52895
53125
  /**
52896
- * Creates a new CacheInstance for a specific function and interval.
53126
+ * Creates a new CacheFnInstance for a specific function and interval.
52897
53127
  *
52898
53128
  * @param fn - Function to cache
52899
53129
  * @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
@@ -52930,7 +53160,7 @@ class CacheInstance {
52930
53160
  *
52931
53161
  * @example
52932
53162
  * ```typescript
52933
- * const instance = new CacheInstance(calculateIndicator, "15m");
53163
+ * const instance = new CacheFnInstance(calculateIndicator, "15m");
52934
53164
  * const result = instance.run("BTCUSDT", 100);
52935
53165
  * console.log(result.value); // Calculated value
52936
53166
  * console.log(result.when); // Cache timestamp
@@ -52938,26 +53168,26 @@ class CacheInstance {
52938
53168
  */
52939
53169
  this.run = (...args) => {
52940
53170
  backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
52941
- const step = INTERVAL_MINUTES[this.interval];
53171
+ const step = INTERVAL_MINUTES$1[this.interval];
52942
53172
  {
52943
53173
  if (!MethodContextService.hasContext()) {
52944
- throw new Error("CacheInstance run requires method context");
53174
+ throw new Error("CacheFnInstance run requires method context");
52945
53175
  }
52946
53176
  if (!ExecutionContextService.hasContext()) {
52947
- throw new Error("CacheInstance run requires execution context");
53177
+ throw new Error("CacheFnInstance run requires execution context");
52948
53178
  }
52949
53179
  if (!step) {
52950
- throw new Error(`CacheInstance unknown cache ttl interval=${this.interval}`);
53180
+ throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
52951
53181
  }
52952
53182
  }
52953
- const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53183
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
52954
53184
  const argKey = String(this.key(args));
52955
53185
  const key = `${contextKey}:${argKey}`;
52956
53186
  const currentWhen = backtest.executionContextService.context.when;
52957
53187
  const cached = this._cacheMap.get(key);
52958
53188
  if (cached) {
52959
- const currentAligned = align(currentWhen.getTime(), this.interval);
52960
- const cachedAligned = align(cached.when.getTime(), this.interval);
53189
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
53190
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
52961
53191
  if (currentAligned === cachedAligned) {
52962
53192
  return cached;
52963
53193
  }
@@ -52980,7 +53210,7 @@ class CacheInstance {
52980
53210
  *
52981
53211
  * @example
52982
53212
  * ```typescript
52983
- * const instance = new CacheInstance(calculateIndicator, "1h");
53213
+ * const instance = new CacheFnInstance(calculateIndicator, "1h");
52984
53214
  * const result1 = instance.run("BTCUSDT", 14); // Computed
52985
53215
  * const result2 = instance.run("BTCUSDT", 14); // Cached
52986
53216
  *
@@ -52990,7 +53220,7 @@ class CacheInstance {
52990
53220
  * ```
52991
53221
  */
52992
53222
  this.clear = () => {
52993
- const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53223
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
52994
53224
  const prefix = `${contextKey}:`;
52995
53225
  for (const key of this._cacheMap.keys()) {
52996
53226
  if (key.startsWith(prefix)) {
@@ -53010,7 +53240,7 @@ class CacheInstance {
53010
53240
  *
53011
53241
  * @example
53012
53242
  * ```typescript
53013
- * const instance = new CacheInstance(calculateIndicator, "1h");
53243
+ * const instance = new CacheFnInstance(calculateIndicator, "1h");
53014
53244
  * instance.run("BTCUSDT", 14); // Cached at 10:00
53015
53245
  * instance.run("ETHUSDT", 14); // Cached at 10:00
53016
53246
  * // Time passes to 11:00
@@ -53019,10 +53249,10 @@ class CacheInstance {
53019
53249
  */
53020
53250
  this.gc = () => {
53021
53251
  const currentWhen = backtest.executionContextService.context.when;
53022
- const currentAligned = align(currentWhen.getTime(), this.interval);
53252
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
53023
53253
  let removed = 0;
53024
53254
  for (const [key, cached] of this._cacheMap.entries()) {
53025
- const cachedAligned = align(cached.when.getTime(), this.interval);
53255
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
53026
53256
  if (currentAligned !== cachedAligned) {
53027
53257
  this._cacheMap.delete(key);
53028
53258
  removed++;
@@ -53097,7 +53327,7 @@ class CacheFileInstance {
53097
53327
  */
53098
53328
  this.run = async (...args) => {
53099
53329
  backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
53100
- const step = INTERVAL_MINUTES[this.interval];
53330
+ const step = INTERVAL_MINUTES$1[this.interval];
53101
53331
  {
53102
53332
  if (!MethodContextService.hasContext()) {
53103
53333
  throw new Error("CacheFileInstance run requires method context");
@@ -53111,7 +53341,7 @@ class CacheFileInstance {
53111
53341
  }
53112
53342
  const [symbol, ...rest] = args;
53113
53343
  const { when } = backtest.executionContextService.context;
53114
- const alignedTs = align(when.getTime(), this.interval);
53344
+ const alignedTs = align$1(when.getTime(), this.interval);
53115
53345
  const bucket = `${this.name}_${this.interval}_${this.index}`;
53116
53346
  const entityKey = this.key([symbol, alignedTs, ...rest]);
53117
53347
  const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
@@ -53119,9 +53349,19 @@ class CacheFileInstance {
53119
53349
  return cached.data;
53120
53350
  }
53121
53351
  const result = await this.fn.call(null, ...args);
53122
- await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
53352
+ await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
53123
53353
  return result;
53124
53354
  };
53355
+ /**
53356
+ * Soft-delete all persisted records for this instance's bucket.
53357
+ * After this call the next `run()` will recompute and re-cache the value.
53358
+ */
53359
+ this.clear = async () => {
53360
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53361
+ for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
53362
+ await PersistMeasureAdapter.removeMeasureData(bucket, key);
53363
+ }
53364
+ };
53125
53365
  this.index = CacheFileInstance.createIndex();
53126
53366
  }
53127
53367
  }
@@ -53145,10 +53385,10 @@ CacheFileInstance._indexCounter = 0;
53145
53385
  class CacheUtils {
53146
53386
  constructor() {
53147
53387
  /**
53148
- * Memoized function to get or create CacheInstance for a function.
53388
+ * Memoized function to get or create CacheFnInstance for a function.
53149
53389
  * Each function gets its own isolated cache instance.
53150
53390
  */
53151
- this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
53391
+ this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
53152
53392
  /**
53153
53393
  * Memoized function to get or create CacheFileInstance for an async function.
53154
53394
  * Each function gets its own isolated file-cache instance.
@@ -53257,31 +53497,34 @@ class CacheUtils {
53257
53497
  */
53258
53498
  this.file = (run, context) => {
53259
53499
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
53500
+ {
53501
+ this._getFileInstance(run, context.interval, context.name, context.key);
53502
+ }
53260
53503
  const wrappedFn = (...args) => {
53261
53504
  const instance = this._getFileInstance(run, context.interval, context.name, context.key);
53262
53505
  return instance.run(...args);
53263
53506
  };
53264
- wrappedFn.clear = () => {
53507
+ wrappedFn.clear = async () => {
53265
53508
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
53266
- this._getFileInstance.clear(run);
53509
+ await this._getFileInstance.get(run)?.clear();
53267
53510
  };
53268
53511
  return wrappedFn;
53269
53512
  };
53270
53513
  /**
53271
- * Dispose (remove) the memoized CacheInstance for a specific function.
53514
+ * Dispose (remove) the memoized CacheFnInstance for a specific function.
53272
53515
  *
53273
- * Removes the CacheInstance from the internal memoization cache, discarding all cached
53516
+ * Removes the CacheFnInstance from the internal memoization cache, discarding all cached
53274
53517
  * results across all contexts (all strategy/exchange/mode combinations) for that function.
53275
- * The next call to the wrapped function will create a fresh CacheInstance.
53518
+ * The next call to the wrapped function will create a fresh CacheFnInstance.
53276
53519
  *
53277
53520
  * @template T - Function type
53278
- * @param run - Function whose CacheInstance should be disposed.
53521
+ * @param run - Function whose CacheFnInstance should be disposed.
53279
53522
  *
53280
53523
  * @example
53281
53524
  * ```typescript
53282
53525
  * const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
53283
53526
  *
53284
- * // Dispose CacheInstance for a specific function
53527
+ * // Dispose CacheFnInstance for a specific function
53285
53528
  * Cache.dispose(calculateIndicator);
53286
53529
  * ```
53287
53530
  */
@@ -53295,7 +53538,7 @@ class CacheUtils {
53295
53538
  }
53296
53539
  };
53297
53540
  /**
53298
- * Clears all memoized CacheInstance and CacheFileInstance objects.
53541
+ * Clears all memoized CacheFnInstance and CacheFileInstance objects.
53299
53542
  * Call this when process.cwd() changes between strategy iterations
53300
53543
  * so new instances are created with the updated base path.
53301
53544
  */
@@ -53325,6 +53568,426 @@ class CacheUtils {
53325
53568
  */
53326
53569
  const Cache = new CacheUtils();
53327
53570
 
53571
+ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
53572
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
53573
+ const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
53574
+ const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
53575
+ const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
53576
+ const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
53577
+ const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
53578
+ const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
53579
+ const MS_PER_MINUTE = 60000;
53580
+ const INTERVAL_MINUTES = {
53581
+ "1m": 1,
53582
+ "3m": 3,
53583
+ "5m": 5,
53584
+ "15m": 15,
53585
+ "30m": 30,
53586
+ "1h": 60,
53587
+ "2h": 120,
53588
+ "4h": 240,
53589
+ "6h": 360,
53590
+ "8h": 480,
53591
+ "1d": 1440,
53592
+ "1w": 10080,
53593
+ };
53594
+ /**
53595
+ * Aligns timestamp down to the nearest interval boundary.
53596
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
53597
+ *
53598
+ * @param timestamp - Timestamp in milliseconds
53599
+ * @param interval - Candle interval
53600
+ * @returns Aligned timestamp rounded down to interval boundary
53601
+ * @throws Error if interval is unknown
53602
+ *
53603
+ * @example
53604
+ * ```typescript
53605
+ * // Align to 15-minute boundary
53606
+ * const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
53607
+ * // Returns timestamp for 2025-10-01T00:30:00Z
53608
+ * ```
53609
+ */
53610
+ const align = (timestamp, interval) => {
53611
+ const intervalMinutes = INTERVAL_MINUTES[interval];
53612
+ if (!intervalMinutes) {
53613
+ throw new Error(`align: unknown interval=${interval}`);
53614
+ }
53615
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
53616
+ return Math.floor(timestamp / intervalMs) * intervalMs;
53617
+ };
53618
+ /**
53619
+ * Build a context key string from strategy name, exchange name, frame name, and execution mode.
53620
+ *
53621
+ * @param strategyName - Name of the strategy
53622
+ * @param exchangeName - Name of the exchange
53623
+ * @param frameName - Name of the backtest frame (omitted in live mode)
53624
+ * @param isBacktest - Whether running in backtest mode
53625
+ * @returns Context key string
53626
+ */
53627
+ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
53628
+ const parts = [strategyName, exchangeName];
53629
+ if (frameName)
53630
+ parts.push(frameName);
53631
+ parts.push(isBacktest ? "backtest" : "live");
53632
+ return parts.join(":");
53633
+ };
53634
+ /**
53635
+ * Instance class for firing a function exactly once per interval boundary.
53636
+ *
53637
+ * On the first call within a new interval the wrapped function is invoked and
53638
+ * its result is returned. Every subsequent call within the same interval returns
53639
+ * `null` without invoking the function again.
53640
+ * If the function itself returns `null`, the interval countdown does not start —
53641
+ * the next call will retry the function.
53642
+ *
53643
+ * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
53644
+ *
53645
+ * @example
53646
+ * ```typescript
53647
+ * const instance = new IntervalFnInstance(mySignalFn, "1h");
53648
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called)
53649
+ * await instance.run("BTCUSDT"); // → null (skipped, same interval)
53650
+ * // After 1 hour passes:
53651
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called again)
53652
+ * ```
53653
+ */
53654
+ class IntervalFnInstance {
53655
+ /**
53656
+ * Creates a new IntervalFnInstance.
53657
+ *
53658
+ * @param fn - Signal function to fire once per interval
53659
+ * @param interval - Candle interval that controls the firing boundary
53660
+ */
53661
+ constructor(fn, interval) {
53662
+ this.fn = fn;
53663
+ this.interval = interval;
53664
+ /** Stores the last aligned timestamp per context+symbol key. */
53665
+ this._stateMap = new Map();
53666
+ /**
53667
+ * Execute the signal function with once-per-interval enforcement.
53668
+ *
53669
+ * Algorithm:
53670
+ * 1. Align the current execution context `when` to the interval boundary.
53671
+ * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
53672
+ * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
53673
+ * the signal. If it returns `null`, leave state unchanged so the next call retries.
53674
+ *
53675
+ * Requires active method context and execution context.
53676
+ *
53677
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
53678
+ * @returns The signal returned by `fn` on the first non-null fire, `null` on all subsequent calls
53679
+ * within the same interval or when `fn` itself returned `null`
53680
+ * @throws Error if method context, execution context, or interval is missing
53681
+ */
53682
+ this.run = async (symbol) => {
53683
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
53684
+ const step = INTERVAL_MINUTES[this.interval];
53685
+ {
53686
+ if (!MethodContextService.hasContext()) {
53687
+ throw new Error("IntervalFnInstance run requires method context");
53688
+ }
53689
+ if (!ExecutionContextService.hasContext()) {
53690
+ throw new Error("IntervalFnInstance run requires execution context");
53691
+ }
53692
+ if (!step) {
53693
+ throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
53694
+ }
53695
+ }
53696
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53697
+ const key = `${contextKey}:${symbol}`;
53698
+ const currentWhen = backtest.executionContextService.context.when;
53699
+ const currentAligned = align(currentWhen.getTime(), this.interval);
53700
+ if (this._stateMap.get(key) === currentAligned) {
53701
+ return null;
53702
+ }
53703
+ const result = await this.fn(symbol, currentWhen);
53704
+ if (result !== null) {
53705
+ this._stateMap.set(key, currentAligned);
53706
+ }
53707
+ return result;
53708
+ };
53709
+ /**
53710
+ * Clear fired-interval state for the current execution context.
53711
+ *
53712
+ * Removes all entries for the current strategy/exchange/frame/mode combination
53713
+ * from this instance's state map. The next `run()` call will invoke the function again.
53714
+ *
53715
+ * Requires active method context and execution context.
53716
+ */
53717
+ this.clear = () => {
53718
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53719
+ const prefix = `${contextKey}:`;
53720
+ for (const key of this._stateMap.keys()) {
53721
+ if (key.startsWith(prefix)) {
53722
+ this._stateMap.delete(key);
53723
+ }
53724
+ }
53725
+ };
53726
+ }
53727
+ }
53728
+ /**
53729
+ * Instance class for firing an async function exactly once per interval boundary,
53730
+ * with the fired state persisted to disk via `PersistIntervalAdapter`.
53731
+ *
53732
+ * On the first call within a new interval the wrapped function is invoked.
53733
+ * If it returns a non-null signal, that result is written to disk and returned.
53734
+ * Every subsequent call within the same interval returns `null` (record exists on disk).
53735
+ * If the function returns `null`, nothing is written and the next call retries.
53736
+ *
53737
+ * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
53738
+ *
53739
+ * @template T - Async function type: `(symbol: string, ...args) => Promise<ISignalIntervalDto | null>`
53740
+ *
53741
+ * @example
53742
+ * ```typescript
53743
+ * const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
53744
+ * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called, result written to disk)
53745
+ * await instance.run("BTCUSDT"); // → null (record exists, already fired)
53746
+ * ```
53747
+ */
53748
+ class IntervalFileInstance {
53749
+ /**
53750
+ * Allocates a new unique index. Called once in the constructor to give each
53751
+ * IntervalFileInstance its own namespace in the persistent key space.
53752
+ */
53753
+ static createIndex() {
53754
+ return IntervalFileInstance._indexCounter++;
53755
+ }
53756
+ /**
53757
+ * Resets the index counter to zero.
53758
+ * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
53759
+ */
53760
+ static clearCounter() {
53761
+ IntervalFileInstance._indexCounter = 0;
53762
+ }
53763
+ /**
53764
+ * Creates a new IntervalFileInstance.
53765
+ *
53766
+ * @param fn - Async signal function to fire once per interval
53767
+ * @param interval - Candle interval that controls the firing boundary
53768
+ * @param name - Human-readable bucket name used as the directory prefix
53769
+ */
53770
+ constructor(fn, interval, name) {
53771
+ this.fn = fn;
53772
+ this.interval = interval;
53773
+ this.name = name;
53774
+ /**
53775
+ * Execute the async signal function with persistent once-per-interval enforcement.
53776
+ *
53777
+ * Algorithm:
53778
+ * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
53779
+ * 2. Align execution context `when` to interval boundary → `alignedTs`.
53780
+ * 3. Build entity key = `${symbol}_${alignedTs}`.
53781
+ * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
53782
+ * 5. On hit — return `null` (interval already fired).
53783
+ * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
53784
+ *
53785
+ * Requires active method context and execution context.
53786
+ *
53787
+ * @param args - Arguments forwarded to the wrapped function (first must be `symbol: string`)
53788
+ * @returns The signal on the first non-null fire, `null` if already fired this interval
53789
+ * or if `fn` itself returned `null`
53790
+ * @throws Error if method context, execution context, or interval is missing
53791
+ */
53792
+ this.run = async (...args) => {
53793
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
53794
+ const step = INTERVAL_MINUTES[this.interval];
53795
+ {
53796
+ if (!MethodContextService.hasContext()) {
53797
+ throw new Error("IntervalFileInstance run requires method context");
53798
+ }
53799
+ if (!ExecutionContextService.hasContext()) {
53800
+ throw new Error("IntervalFileInstance run requires execution context");
53801
+ }
53802
+ if (!step) {
53803
+ throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
53804
+ }
53805
+ }
53806
+ const [symbol] = args;
53807
+ const { when } = backtest.executionContextService.context;
53808
+ const alignedTs = align(when.getTime(), this.interval);
53809
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53810
+ const entityKey = `${symbol}_${alignedTs}`;
53811
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
53812
+ if (cached !== null) {
53813
+ return null;
53814
+ }
53815
+ const result = await this.fn.call(null, ...args);
53816
+ if (result !== null) {
53817
+ await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
53818
+ }
53819
+ return result;
53820
+ };
53821
+ /**
53822
+ * Soft-delete all persisted records for this instance's bucket.
53823
+ * After this call the function will fire again on the next `run()`.
53824
+ */
53825
+ this.clear = async () => {
53826
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
53827
+ for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
53828
+ await PersistIntervalAdapter.removeIntervalData(bucket, key);
53829
+ }
53830
+ };
53831
+ this.index = IntervalFileInstance.createIndex();
53832
+ }
53833
+ }
53834
+ /** Global counter — incremented once per IntervalFileInstance construction. */
53835
+ IntervalFileInstance._indexCounter = 0;
53836
+ /**
53837
+ * Utility class for wrapping signal functions with once-per-interval firing.
53838
+ * Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
53839
+ * Exported as singleton instance `Interval` for convenient usage.
53840
+ *
53841
+ * @example
53842
+ * ```typescript
53843
+ * import { Interval } from "./classes/Interval";
53844
+ *
53845
+ * const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
53846
+ * await fireOncePerHour("BTCUSDT", when); // fn called — returns its result
53847
+ * await fireOncePerHour("BTCUSDT", when); // returns null (same interval)
53848
+ * ```
53849
+ */
53850
+ class IntervalUtils {
53851
+ constructor() {
53852
+ /**
53853
+ * Memoized factory to get or create an `IntervalFnInstance` for a function.
53854
+ * Each function reference gets its own isolated instance.
53855
+ */
53856
+ this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
53857
+ /**
53858
+ * Memoized factory to get or create an `IntervalFileInstance` for an async function.
53859
+ * Each function reference gets its own isolated persistent instance.
53860
+ */
53861
+ this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
53862
+ /**
53863
+ * Wrap a signal function with in-memory once-per-interval firing.
53864
+ *
53865
+ * Returns a wrapped version of the function that fires at most once per interval boundary.
53866
+ * If the function returns `null`, the countdown does not start and the next call retries.
53867
+ *
53868
+ * The `run` function reference is used as the memoization key for the underlying
53869
+ * `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
53870
+ *
53871
+ * @param run - Signal function to wrap
53872
+ * @param context.interval - Candle interval that controls the firing boundary
53873
+ * @returns Wrapped function with the same signature as `TIntervalFn`, plus a `clear()` method
53874
+ *
53875
+ * @example
53876
+ * ```typescript
53877
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
53878
+ *
53879
+ * await fireOnce("BTCUSDT", when); // → signal or null (fn called)
53880
+ * await fireOnce("BTCUSDT", when); // → null (same interval, skipped)
53881
+ * ```
53882
+ */
53883
+ this.fn = (run, context) => {
53884
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
53885
+ const wrappedFn = (symbol, _when) => {
53886
+ const instance = this._getInstance(run, context.interval);
53887
+ return instance.run(symbol);
53888
+ };
53889
+ wrappedFn.clear = () => {
53890
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
53891
+ if (!MethodContextService.hasContext()) {
53892
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
53893
+ return;
53894
+ }
53895
+ if (!ExecutionContextService.hasContext()) {
53896
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
53897
+ return;
53898
+ }
53899
+ this._getInstance.get(run)?.clear();
53900
+ };
53901
+ return wrappedFn;
53902
+ };
53903
+ /**
53904
+ * Wrap an async signal function with persistent file-based once-per-interval firing.
53905
+ *
53906
+ * Returns a wrapped version of the function that reads from disk on hit (returns `null`)
53907
+ * and writes the fired signal to disk on the first successful fire.
53908
+ * Fired state survives process restarts.
53909
+ *
53910
+ * The `run` function reference is used as the memoization key for the underlying
53911
+ * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
53912
+ *
53913
+ * @template T - Async function type to wrap
53914
+ * @param run - Async signal function to wrap with persistent once-per-interval firing
53915
+ * @param context.interval - Candle interval that controls the firing boundary
53916
+ * @param context.name - Human-readable bucket name; becomes the directory prefix
53917
+ * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
53918
+ * that deletes persisted records from disk and disposes the memoized instance
53919
+ *
53920
+ * @example
53921
+ * ```typescript
53922
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
53923
+ * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
53924
+ * await fireOnce.clear(); // delete disk records so the function fires again next call
53925
+ * ```
53926
+ */
53927
+ this.file = (run, context) => {
53928
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
53929
+ {
53930
+ this._getFileInstance(run, context.interval, context.name);
53931
+ }
53932
+ const wrappedFn = (...args) => {
53933
+ const instance = this._getFileInstance(run, context.interval, context.name);
53934
+ return instance.run(...args);
53935
+ };
53936
+ wrappedFn.clear = async () => {
53937
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
53938
+ await this._getFileInstance.get(run)?.clear();
53939
+ };
53940
+ return wrappedFn;
53941
+ };
53942
+ /**
53943
+ * Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
53944
+ *
53945
+ * Removes the instance from the internal memoization cache, discarding all in-memory
53946
+ * fired-interval state across all contexts for that function.
53947
+ * The next call to the wrapped function will create a fresh `IntervalFnInstance`.
53948
+ *
53949
+ * @param run - Function whose `IntervalFnInstance` should be disposed
53950
+ *
53951
+ * @example
53952
+ * ```typescript
53953
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
53954
+ * Interval.dispose(mySignalFn);
53955
+ * ```
53956
+ */
53957
+ this.dispose = (run) => {
53958
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
53959
+ this._getInstance.clear(run);
53960
+ };
53961
+ /**
53962
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
53963
+ * resets the `IntervalFileInstance` index counter.
53964
+ * Call this when `process.cwd()` changes between strategy iterations
53965
+ * so new instances are created with the updated base path.
53966
+ */
53967
+ this.clear = () => {
53968
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
53969
+ this._getInstance.clear();
53970
+ this._getFileInstance.clear();
53971
+ IntervalFileInstance.clearCounter();
53972
+ };
53973
+ }
53974
+ }
53975
+ /**
53976
+ * Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
53977
+ *
53978
+ * @example
53979
+ * ```typescript
53980
+ * import { Interval } from "./classes/Interval";
53981
+ *
53982
+ * // In-memory: fires once per hour, resets on process restart
53983
+ * const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
53984
+ *
53985
+ * // Persistent: fired state survives restarts
53986
+ * const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
53987
+ * ```
53988
+ */
53989
+ const Interval = new IntervalUtils();
53990
+
53328
53991
  const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
53329
53992
  const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
53330
53993
  const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
@@ -54451,11 +55114,13 @@ exports.Exchange = Exchange;
54451
55114
  exports.ExecutionContextService = ExecutionContextService;
54452
55115
  exports.Heat = Heat;
54453
55116
  exports.HighestProfit = HighestProfit;
55117
+ exports.Interval = Interval;
54454
55118
  exports.Live = Live;
54455
55119
  exports.Log = Log;
54456
55120
  exports.Markdown = Markdown;
54457
55121
  exports.MarkdownFileBase = MarkdownFileBase;
54458
55122
  exports.MarkdownFolderBase = MarkdownFolderBase;
55123
+ exports.MarkdownWriter = MarkdownWriter;
54459
55124
  exports.MaxDrawdown = MaxDrawdown;
54460
55125
  exports.Memory = Memory;
54461
55126
  exports.MethodContextService = MethodContextService;
@@ -54467,6 +55132,7 @@ exports.Performance = Performance;
54467
55132
  exports.PersistBase = PersistBase;
54468
55133
  exports.PersistBreakevenAdapter = PersistBreakevenAdapter;
54469
55134
  exports.PersistCandleAdapter = PersistCandleAdapter;
55135
+ exports.PersistIntervalAdapter = PersistIntervalAdapter;
54470
55136
  exports.PersistLogAdapter = PersistLogAdapter;
54471
55137
  exports.PersistMeasureAdapter = PersistMeasureAdapter;
54472
55138
  exports.PersistMemoryAdapter = PersistMemoryAdapter;
@@ -54476,9 +55142,11 @@ exports.PersistRiskAdapter = PersistRiskAdapter;
54476
55142
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
54477
55143
  exports.PersistSignalAdapter = PersistSignalAdapter;
54478
55144
  exports.PersistStorageAdapter = PersistStorageAdapter;
55145
+ exports.Position = Position;
54479
55146
  exports.PositionSize = PositionSize;
54480
55147
  exports.Report = Report;
54481
55148
  exports.ReportBase = ReportBase;
55149
+ exports.ReportWriter = ReportWriter;
54482
55150
  exports.Risk = Risk;
54483
55151
  exports.Schedule = Schedule;
54484
55152
  exports.Storage = Storage;