backtest-kit 1.4.6 → 1.4.8

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
@@ -1216,24 +1216,25 @@ const PersistBase = functoolsKit.makeExtendable(class {
1216
1216
  class PersistSignalUtils {
1217
1217
  constructor() {
1218
1218
  this.PersistSignalFactory = PersistBase;
1219
- this.getSignalStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, (strategyName) => Reflect.construct(this.PersistSignalFactory, [
1220
- strategyName,
1219
+ this.getSignalStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => Reflect.construct(this.PersistSignalFactory, [
1220
+ `${symbol}_${strategyName}`,
1221
1221
  `./dump/data/signal/`,
1222
1222
  ]));
1223
1223
  /**
1224
- * Reads persisted signal data for a strategy and symbol.
1224
+ * Reads persisted signal data for a symbol and strategy.
1225
1225
  *
1226
1226
  * Called by ClientStrategy.waitForInit() to restore state.
1227
1227
  * Returns null if no signal exists.
1228
1228
  *
1229
- * @param strategyName - Strategy identifier
1230
1229
  * @param symbol - Trading pair symbol
1230
+ * @param strategyName - Strategy identifier
1231
1231
  * @returns Promise resolving to signal or null
1232
1232
  */
1233
- this.readSignalData = async (strategyName, symbol) => {
1233
+ this.readSignalData = async (symbol, strategyName) => {
1234
1234
  backtest$1.loggerService.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
1235
- const isInitial = !this.getSignalStorage.has(strategyName);
1236
- const stateStorage = this.getSignalStorage(strategyName);
1235
+ const key = `${symbol}:${strategyName}`;
1236
+ const isInitial = !this.getSignalStorage.has(key);
1237
+ const stateStorage = this.getSignalStorage(symbol, strategyName);
1237
1238
  await stateStorage.waitForInit(isInitial);
1238
1239
  if (await stateStorage.hasValue(symbol)) {
1239
1240
  return await stateStorage.readValue(symbol);
@@ -1247,14 +1248,15 @@ class PersistSignalUtils {
1247
1248
  * Uses atomic writes to prevent corruption on crashes.
1248
1249
  *
1249
1250
  * @param signalRow - Signal data (null to clear)
1250
- * @param strategyName - Strategy identifier
1251
1251
  * @param symbol - Trading pair symbol
1252
+ * @param strategyName - Strategy identifier
1252
1253
  * @returns Promise that resolves when write is complete
1253
1254
  */
1254
- this.writeSignalData = async (signalRow, strategyName, symbol) => {
1255
+ this.writeSignalData = async (signalRow, symbol, strategyName) => {
1255
1256
  backtest$1.loggerService.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
1256
- const isInitial = !this.getSignalStorage.has(strategyName);
1257
- const stateStorage = this.getSignalStorage(strategyName);
1257
+ const key = `${symbol}:${strategyName}`;
1258
+ const isInitial = !this.getSignalStorage.has(key);
1259
+ const stateStorage = this.getSignalStorage(symbol, strategyName);
1258
1260
  await stateStorage.waitForInit(isInitial);
1259
1261
  await stateStorage.writeValue(symbol, signalRow);
1260
1262
  };
@@ -1405,24 +1407,25 @@ const PersistRiskAdapter = new PersistRiskUtils();
1405
1407
  class PersistScheduleUtils {
1406
1408
  constructor() {
1407
1409
  this.PersistScheduleFactory = PersistBase;
1408
- this.getScheduleStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, (strategyName) => Reflect.construct(this.PersistScheduleFactory, [
1409
- strategyName,
1410
+ this.getScheduleStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => Reflect.construct(this.PersistScheduleFactory, [
1411
+ `${symbol}_${strategyName}`,
1410
1412
  `./dump/data/schedule/`,
1411
1413
  ]));
1412
1414
  /**
1413
- * Reads persisted scheduled signal data for a strategy and symbol.
1415
+ * Reads persisted scheduled signal data for a symbol and strategy.
1414
1416
  *
1415
1417
  * Called by ClientStrategy.waitForInit() to restore scheduled signal state.
1416
1418
  * Returns null if no scheduled signal exists.
1417
1419
  *
1418
- * @param strategyName - Strategy identifier
1419
1420
  * @param symbol - Trading pair symbol
1421
+ * @param strategyName - Strategy identifier
1420
1422
  * @returns Promise resolving to scheduled signal or null
1421
1423
  */
1422
- this.readScheduleData = async (strategyName, symbol) => {
1424
+ this.readScheduleData = async (symbol, strategyName) => {
1423
1425
  backtest$1.loggerService.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
1424
- const isInitial = !this.getScheduleStorage.has(strategyName);
1425
- const stateStorage = this.getScheduleStorage(strategyName);
1426
+ const key = `${symbol}:${strategyName}`;
1427
+ const isInitial = !this.getScheduleStorage.has(key);
1428
+ const stateStorage = this.getScheduleStorage(symbol, strategyName);
1426
1429
  await stateStorage.waitForInit(isInitial);
1427
1430
  if (await stateStorage.hasValue(symbol)) {
1428
1431
  return await stateStorage.readValue(symbol);
@@ -1436,14 +1439,15 @@ class PersistScheduleUtils {
1436
1439
  * Uses atomic writes to prevent corruption on crashes.
1437
1440
  *
1438
1441
  * @param scheduledSignalRow - Scheduled signal data (null to clear)
1439
- * @param strategyName - Strategy identifier
1440
1442
  * @param symbol - Trading pair symbol
1443
+ * @param strategyName - Strategy identifier
1441
1444
  * @returns Promise that resolves when write is complete
1442
1445
  */
1443
- this.writeScheduleData = async (scheduledSignalRow, strategyName, symbol) => {
1446
+ this.writeScheduleData = async (scheduledSignalRow, symbol, strategyName) => {
1444
1447
  backtest$1.loggerService.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
1445
- const isInitial = !this.getScheduleStorage.has(strategyName);
1446
- const stateStorage = this.getScheduleStorage(strategyName);
1448
+ const key = `${symbol}:${strategyName}`;
1449
+ const isInitial = !this.getScheduleStorage.has(key);
1450
+ const stateStorage = this.getScheduleStorage(symbol, strategyName);
1447
1451
  await stateStorage.waitForInit(isInitial);
1448
1452
  await stateStorage.writeValue(symbol, scheduledSignalRow);
1449
1453
  };
@@ -1827,6 +1831,9 @@ const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
1827
1831
  if (signal.minuteEstimatedTime <= 0) {
1828
1832
  errors.push(`minuteEstimatedTime must be positive, got ${signal.minuteEstimatedTime}`);
1829
1833
  }
1834
+ if (!Number.isInteger(signal.minuteEstimatedTime)) {
1835
+ errors.push(`minuteEstimatedTime must be an integer (whole number), got ${signal.minuteEstimatedTime}`);
1836
+ }
1830
1837
  // ЗАЩИТА ОТ ВЕЧНЫХ СИГНАЛОВ: ограничиваем максимальное время жизни сигнала
1831
1838
  if (GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES && GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) {
1832
1839
  if (signal.minuteEstimatedTime > GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) {
@@ -1969,7 +1976,7 @@ const WAIT_FOR_INIT_FN$2 = async (self) => {
1969
1976
  return;
1970
1977
  }
1971
1978
  // Restore pending signal
1972
- const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.strategyName, self.params.execution.context.symbol);
1979
+ const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.execution.context.symbol, self.params.strategyName);
1973
1980
  if (pendingSignal) {
1974
1981
  if (pendingSignal.exchangeName !== self.params.method.context.exchangeName) {
1975
1982
  return;
@@ -1985,7 +1992,7 @@ const WAIT_FOR_INIT_FN$2 = async (self) => {
1985
1992
  }
1986
1993
  }
1987
1994
  // Restore scheduled signal
1988
- const scheduledSignal = await PersistScheduleAdapter.readScheduleData(self.params.strategyName, self.params.execution.context.symbol);
1995
+ const scheduledSignal = await PersistScheduleAdapter.readScheduleData(self.params.execution.context.symbol, self.params.strategyName);
1989
1996
  if (scheduledSignal) {
1990
1997
  if (scheduledSignal.exchangeName !== self.params.method.context.exchangeName) {
1991
1998
  return;
@@ -2679,7 +2686,7 @@ class ClientStrategy {
2679
2686
  if (this.params.execution.context.backtest) {
2680
2687
  return;
2681
2688
  }
2682
- await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.strategyName, this.params.execution.context.symbol);
2689
+ await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName);
2683
2690
  }
2684
2691
  /**
2685
2692
  * Updates scheduled signal and persists to disk in live mode.
@@ -2698,14 +2705,18 @@ class ClientStrategy {
2698
2705
  if (this.params.execution.context.backtest) {
2699
2706
  return;
2700
2707
  }
2701
- await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, this.params.strategyName, this.params.execution.context.symbol);
2708
+ await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, this.params.execution.context.symbol, this.params.strategyName);
2702
2709
  }
2703
2710
  /**
2704
2711
  * Retrieves the current pending signal.
2705
2712
  * If no signal is pending, returns null.
2706
2713
  * @returns Promise resolving to the pending signal or null.
2707
2714
  */
2708
- async getPendingSignal() {
2715
+ async getPendingSignal(symbol, strategyName) {
2716
+ this.params.logger.debug("ClientStrategy getPendingSignal", {
2717
+ symbol,
2718
+ strategyName,
2719
+ });
2709
2720
  return this._pendingSignal;
2710
2721
  }
2711
2722
  /**
@@ -2738,8 +2749,11 @@ class ClientStrategy {
2738
2749
  * }
2739
2750
  * ```
2740
2751
  */
2741
- async tick() {
2742
- this.params.logger.debug("ClientStrategy tick");
2752
+ async tick(symbol, strategyName) {
2753
+ this.params.logger.debug("ClientStrategy tick", {
2754
+ symbol,
2755
+ strategyName,
2756
+ });
2743
2757
  // Получаем текущее время в начале tick для консистентности
2744
2758
  const currentTime = this.params.execution.context.when.getTime();
2745
2759
  // Early return if strategy was stopped
@@ -2824,9 +2838,11 @@ class ClientStrategy {
2824
2838
  * console.log(result.closeReason); // "take_profit" | "stop_loss" | "time_expired" | "cancelled"
2825
2839
  * ```
2826
2840
  */
2827
- async backtest(candles) {
2841
+ async backtest(symbol, strategyName, candles) {
2828
2842
  this.params.logger.debug("ClientStrategy backtest", {
2829
- symbol: this.params.execution.context.symbol,
2843
+ symbol,
2844
+ strategyName,
2845
+ contextSymbol: this.params.execution.context.symbol,
2830
2846
  candlesCount: candles.length,
2831
2847
  hasScheduled: !!this._scheduledSignal,
2832
2848
  hasPending: !!this._pendingSignal,
@@ -2942,8 +2958,10 @@ class ClientStrategy {
2942
2958
  * // Existing signal will continue until natural close
2943
2959
  * ```
2944
2960
  */
2945
- async stop() {
2961
+ async stop(symbol, strategyName) {
2946
2962
  this.params.logger.debug("ClientStrategy stop", {
2963
+ symbol,
2964
+ strategyName,
2947
2965
  hasPendingSignal: this._pendingSignal !== null,
2948
2966
  hasScheduledSignal: this._scheduledSignal !== null,
2949
2967
  });
@@ -2964,21 +2982,20 @@ const NOOP_RISK = {
2964
2982
  * Connection service routing strategy operations to correct ClientStrategy instance.
2965
2983
  *
2966
2984
  * Routes all IStrategy method calls to the appropriate strategy implementation
2967
- * based on methodContextService.context.strategyName. Uses memoization to cache
2985
+ * based on symbol-strategy pairs. Uses memoization to cache
2968
2986
  * ClientStrategy instances for performance.
2969
2987
  *
2970
2988
  * Key features:
2971
- * - Automatic strategy routing via method context
2972
- * - Memoized ClientStrategy instances by strategyName
2973
- * - Implements IStrategy interface
2989
+ * - Automatic strategy routing via symbol-strategy pairs
2990
+ * - Memoized ClientStrategy instances by symbol:strategyName
2974
2991
  * - Ensures initialization with waitForInit() before operations
2975
2992
  * - Handles both tick() (live) and backtest() operations
2976
2993
  *
2977
2994
  * @example
2978
2995
  * ```typescript
2979
2996
  * // Used internally by framework
2980
- * const result = await strategyConnectionService.tick();
2981
- * // Automatically routes to correct strategy based on methodContext
2997
+ * const result = await strategyConnectionService.tick(symbol, strategyName);
2998
+ * // Routes to correct strategy instance for symbol-strategy pair
2982
2999
  * ```
2983
3000
  */
2984
3001
  class StrategyConnectionService {
@@ -2991,17 +3008,19 @@ class StrategyConnectionService {
2991
3008
  this.methodContextService = inject(TYPES.methodContextService);
2992
3009
  this.partialConnectionService = inject(TYPES.partialConnectionService);
2993
3010
  /**
2994
- * Retrieves memoized ClientStrategy instance for given strategy name.
3011
+ * Retrieves memoized ClientStrategy instance for given symbol-strategy pair.
2995
3012
  *
2996
3013
  * Creates ClientStrategy on first call, returns cached instance on subsequent calls.
2997
- * Cache key is strategyName string.
3014
+ * Cache key is symbol:strategyName string.
2998
3015
  *
3016
+ * @param symbol - Trading pair symbol
2999
3017
  * @param strategyName - Name of registered strategy schema
3000
3018
  * @returns Configured ClientStrategy instance
3001
3019
  */
3002
- this.getStrategy = functoolsKit.memoize(([strategyName]) => `${strategyName}`, (strategyName) => {
3020
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => {
3003
3021
  const { riskName, getSignal, interval, callbacks } = this.strategySchemaService.get(strategyName);
3004
3022
  return new ClientStrategy({
3023
+ symbol,
3005
3024
  interval,
3006
3025
  execution: this.executionContextService,
3007
3026
  method: this.methodContextService,
@@ -3020,14 +3039,18 @@ class StrategyConnectionService {
3020
3039
  * If no active signal exists, returns null.
3021
3040
  * Used internally for monitoring TP/SL and time expiration.
3022
3041
  *
3042
+ * @param symbol - Trading pair symbol
3023
3043
  * @param strategyName - Name of strategy to get pending signal for
3024
3044
  *
3025
3045
  * @returns Promise resolving to pending signal or null
3026
3046
  */
3027
- this.getPendingSignal = async (strategyName) => {
3028
- this.loggerService.log("strategyConnectionService getPendingSignal");
3029
- const strategy = await this.getStrategy(strategyName);
3030
- return await strategy.getPendingSignal();
3047
+ this.getPendingSignal = async (symbol, strategyName) => {
3048
+ this.loggerService.log("strategyConnectionService getPendingSignal", {
3049
+ symbol,
3050
+ strategyName,
3051
+ });
3052
+ const strategy = this.getStrategy(symbol, strategyName);
3053
+ return await strategy.getPendingSignal(symbol, strategyName);
3031
3054
  };
3032
3055
  /**
3033
3056
  * Executes live trading tick for current strategy.
@@ -3035,13 +3058,18 @@ class StrategyConnectionService {
3035
3058
  * Waits for strategy initialization before processing tick.
3036
3059
  * Evaluates current market conditions and returns signal state.
3037
3060
  *
3061
+ * @param symbol - Trading pair symbol
3062
+ * @param strategyName - Name of strategy to tick
3038
3063
  * @returns Promise resolving to tick result (idle, opened, active, closed)
3039
3064
  */
3040
- this.tick = async () => {
3041
- this.loggerService.log("strategyConnectionService tick");
3042
- const strategy = await this.getStrategy(this.methodContextService.context.strategyName);
3065
+ this.tick = async (symbol, strategyName) => {
3066
+ this.loggerService.log("strategyConnectionService tick", {
3067
+ symbol,
3068
+ strategyName,
3069
+ });
3070
+ const strategy = this.getStrategy(symbol, strategyName);
3043
3071
  await strategy.waitForInit();
3044
- const tick = await strategy.tick();
3072
+ const tick = await strategy.tick(symbol, strategyName);
3045
3073
  {
3046
3074
  if (this.executionContextService.context.backtest) {
3047
3075
  await signalBacktestEmitter.next(tick);
@@ -3059,16 +3087,20 @@ class StrategyConnectionService {
3059
3087
  * Waits for strategy initialization before processing candles.
3060
3088
  * Evaluates strategy signals against historical data.
3061
3089
  *
3090
+ * @param symbol - Trading pair symbol
3091
+ * @param strategyName - Name of strategy to backtest
3062
3092
  * @param candles - Array of historical candle data to backtest
3063
3093
  * @returns Promise resolving to backtest result (signal or idle)
3064
3094
  */
3065
- this.backtest = async (candles) => {
3095
+ this.backtest = async (symbol, strategyName, candles) => {
3066
3096
  this.loggerService.log("strategyConnectionService backtest", {
3097
+ symbol,
3098
+ strategyName,
3067
3099
  candleCount: candles.length,
3068
3100
  });
3069
- const strategy = await this.getStrategy(this.methodContextService.context.strategyName);
3101
+ const strategy = this.getStrategy(symbol, strategyName);
3070
3102
  await strategy.waitForInit();
3071
- const tick = await strategy.backtest(candles);
3103
+ const tick = await strategy.backtest(symbol, strategyName, candles);
3072
3104
  {
3073
3105
  if (this.executionContextService.context.backtest) {
3074
3106
  await signalBacktestEmitter.next(tick);
@@ -3083,15 +3115,17 @@ class StrategyConnectionService {
3083
3115
  * Delegates to ClientStrategy.stop() which sets internal flag to prevent
3084
3116
  * getSignal from being called on subsequent ticks.
3085
3117
  *
3118
+ * @param symbol - Trading pair symbol
3086
3119
  * @param strategyName - Name of strategy to stop
3087
3120
  * @returns Promise that resolves when stop flag is set
3088
3121
  */
3089
- this.stop = async (strategyName) => {
3122
+ this.stop = async (symbol, strategyName) => {
3090
3123
  this.loggerService.log("strategyConnectionService stop", {
3124
+ symbol,
3091
3125
  strategyName,
3092
3126
  });
3093
- const strategy = this.getStrategy(strategyName);
3094
- await strategy.stop();
3127
+ const strategy = this.getStrategy(symbol, strategyName);
3128
+ await strategy.stop(symbol, strategyName);
3095
3129
  };
3096
3130
  /**
3097
3131
  * Clears the memoized ClientStrategy instance from cache.
@@ -3099,13 +3133,19 @@ class StrategyConnectionService {
3099
3133
  * Forces re-initialization of strategy on next getStrategy call.
3100
3134
  * Useful for resetting strategy state or releasing resources.
3101
3135
  *
3102
- * @param strategyName - Name of strategy to clear from cache
3136
+ * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
3103
3137
  */
3104
- this.clear = async (strategyName) => {
3138
+ this.clear = async (ctx) => {
3105
3139
  this.loggerService.log("strategyConnectionService clear", {
3106
- strategyName,
3140
+ ctx,
3107
3141
  });
3108
- this.getStrategy.clear(strategyName);
3142
+ if (ctx) {
3143
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
3144
+ this.getStrategy.clear(key);
3145
+ }
3146
+ else {
3147
+ this.getStrategy.clear();
3148
+ }
3109
3149
  };
3110
3150
  }
3111
3151
  }
@@ -3916,13 +3956,15 @@ class StrategyGlobalService {
3916
3956
  /**
3917
3957
  * Validates strategy and associated risk configuration.
3918
3958
  *
3919
- * Memoized to avoid redundant validations for the same strategy.
3959
+ * Memoized to avoid redundant validations for the same symbol-strategy pair.
3920
3960
  * Logs validation activity.
3961
+ * @param symbol - Trading pair symbol
3921
3962
  * @param strategyName - Name of the strategy to validate
3922
3963
  * @returns Promise that resolves when validation is complete
3923
3964
  */
3924
- this.validate = functoolsKit.memoize(([strategyName]) => `${strategyName}`, async (strategyName) => {
3965
+ this.validate = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, async (symbol, strategyName) => {
3925
3966
  this.loggerService.log(METHOD_NAME_VALIDATE, {
3967
+ symbol,
3926
3968
  strategyName,
3927
3969
  });
3928
3970
  const strategySchema = this.strategySchemaService.get(strategyName);
@@ -3937,16 +3979,16 @@ class StrategyGlobalService {
3937
3979
  * Used internally for monitoring TP/SL and time expiration.
3938
3980
  *
3939
3981
  * @param symbol - Trading pair symbol
3940
- * @param when - Timestamp for tick evaluation
3941
- * @param backtest - Whether running in backtest mode
3982
+ * @param strategyName - Name of the strategy
3942
3983
  * @returns Promise resolving to pending signal or null
3943
3984
  */
3944
- this.getPendingSignal = async (strategyName) => {
3985
+ this.getPendingSignal = async (symbol, strategyName) => {
3945
3986
  this.loggerService.log("strategyGlobalService getPendingSignal", {
3987
+ symbol,
3946
3988
  strategyName,
3947
3989
  });
3948
- await this.validate(this.methodContextService.context.strategyName);
3949
- return await this.strategyConnectionService.getPendingSignal(strategyName);
3990
+ await this.validate(symbol, strategyName);
3991
+ return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
3950
3992
  };
3951
3993
  /**
3952
3994
  * Checks signal status at a specific timestamp.
@@ -3965,9 +4007,10 @@ class StrategyGlobalService {
3965
4007
  when,
3966
4008
  backtest,
3967
4009
  });
3968
- await this.validate(this.methodContextService.context.strategyName);
4010
+ const strategyName = this.methodContextService.context.strategyName;
4011
+ await this.validate(symbol, strategyName);
3969
4012
  return await ExecutionContextService.runInContext(async () => {
3970
- return await this.strategyConnectionService.tick();
4013
+ return await this.strategyConnectionService.tick(symbol, strategyName);
3971
4014
  }, {
3972
4015
  symbol,
3973
4016
  when,
@@ -3993,9 +4036,10 @@ class StrategyGlobalService {
3993
4036
  when,
3994
4037
  backtest,
3995
4038
  });
3996
- await this.validate(this.methodContextService.context.strategyName);
4039
+ const strategyName = this.methodContextService.context.strategyName;
4040
+ await this.validate(symbol, strategyName);
3997
4041
  return await ExecutionContextService.runInContext(async () => {
3998
- return await this.strategyConnectionService.backtest(candles);
4042
+ return await this.strategyConnectionService.backtest(symbol, strategyName, candles);
3999
4043
  }, {
4000
4044
  symbol,
4001
4045
  when,
@@ -4008,15 +4052,17 @@ class StrategyGlobalService {
4008
4052
  * Delegates to StrategyConnectionService.stop() to set internal flag.
4009
4053
  * Does not require execution context.
4010
4054
  *
4055
+ * @param symbol - Trading pair symbol
4011
4056
  * @param strategyName - Name of strategy to stop
4012
4057
  * @returns Promise that resolves when stop flag is set
4013
4058
  */
4014
- this.stop = async (strategyName) => {
4059
+ this.stop = async (symbol, strategyName) => {
4015
4060
  this.loggerService.log("strategyGlobalService stop", {
4061
+ symbol,
4016
4062
  strategyName,
4017
4063
  });
4018
- await this.validate(strategyName);
4019
- return await this.strategyConnectionService.stop(strategyName);
4064
+ await this.validate(symbol, strategyName);
4065
+ return await this.strategyConnectionService.stop(symbol, strategyName);
4020
4066
  };
4021
4067
  /**
4022
4068
  * Clears the memoized ClientStrategy instance from cache.
@@ -4024,16 +4070,16 @@ class StrategyGlobalService {
4024
4070
  * Delegates to StrategyConnectionService.clear() to remove strategy from cache.
4025
4071
  * Forces re-initialization of strategy on next operation.
4026
4072
  *
4027
- * @param strategyName - Name of strategy to clear from cache
4073
+ * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4028
4074
  */
4029
- this.clear = async (strategyName) => {
4075
+ this.clear = async (ctx) => {
4030
4076
  this.loggerService.log("strategyGlobalService clear", {
4031
- strategyName,
4077
+ ctx,
4032
4078
  });
4033
- if (strategyName) {
4034
- await this.validate(strategyName);
4079
+ if (ctx) {
4080
+ await this.validate(ctx.symbol, ctx.strategyName);
4035
4081
  }
4036
- return await this.strategyConnectionService.clear(strategyName);
4082
+ return await this.strategyConnectionService.clear(ctx);
4037
4083
  };
4038
4084
  }
4039
4085
  }
@@ -5008,8 +5054,14 @@ class WalkerLogicPrivateService {
5008
5054
  let strategiesTested = 0;
5009
5055
  let bestMetric = null;
5010
5056
  let bestStrategy = null;
5057
+ let pendingStrategy;
5011
5058
  const listenStop = walkerStopSubject
5012
- .filter((walkerName) => walkerName === context.walkerName)
5059
+ .filter((data) => {
5060
+ let isOk = true;
5061
+ isOk = isOk && data.symbol === symbol;
5062
+ isOk = isOk && data.strategyName === pendingStrategy;
5063
+ return isOk;
5064
+ })
5013
5065
  .map(() => CANCEL_SYMBOL)
5014
5066
  .toPromise();
5015
5067
  // Run backtest for each strategy
@@ -5027,6 +5079,7 @@ class WalkerLogicPrivateService {
5027
5079
  exchangeName: context.exchangeName,
5028
5080
  frameName: context.frameName,
5029
5081
  });
5082
+ pendingStrategy = strategyName;
5030
5083
  const result = await Promise.race([
5031
5084
  await functoolsKit.resolveDocuments(iterator),
5032
5085
  listenStop,
@@ -5042,7 +5095,7 @@ class WalkerLogicPrivateService {
5042
5095
  symbol,
5043
5096
  });
5044
5097
  // Get statistics from BacktestMarkdownService
5045
- const stats = await this.backtestMarkdownService.getData(strategyName);
5098
+ const stats = await this.backtestMarkdownService.getData(symbol, strategyName);
5046
5099
  // Extract metric value
5047
5100
  const value = stats[metric];
5048
5101
  const metricValue = value !== null &&
@@ -5101,7 +5154,7 @@ class WalkerLogicPrivateService {
5101
5154
  bestStrategy,
5102
5155
  bestMetric,
5103
5156
  bestStats: bestStrategy !== null
5104
- ? await this.backtestMarkdownService.getData(bestStrategy)
5157
+ ? await this.backtestMarkdownService.getData(symbol, bestStrategy)
5105
5158
  : null,
5106
5159
  };
5107
5160
  // Call onComplete callback if provided with final best results
@@ -5657,10 +5710,10 @@ class BacktestMarkdownService {
5657
5710
  /** Logger service for debug output */
5658
5711
  this.loggerService = inject(TYPES.loggerService);
5659
5712
  /**
5660
- * Memoized function to get or create ReportStorage for a strategy.
5661
- * Each strategy gets its own isolated storage instance.
5713
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
5714
+ * Each symbol-strategy combination gets its own isolated storage instance.
5662
5715
  */
5663
- this.getStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, () => new ReportStorage$4());
5716
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$4());
5664
5717
  /**
5665
5718
  * Processes tick events and accumulates closed signals.
5666
5719
  * Should be called from IStrategyCallbacks.onTick.
@@ -5687,56 +5740,61 @@ class BacktestMarkdownService {
5687
5740
  if (data.action !== "closed") {
5688
5741
  return;
5689
5742
  }
5690
- const storage = this.getStorage(data.strategyName);
5743
+ const storage = this.getStorage(data.symbol, data.strategyName);
5691
5744
  storage.addSignal(data);
5692
5745
  };
5693
5746
  /**
5694
- * Gets statistical data from all closed signals for a strategy.
5747
+ * Gets statistical data from all closed signals for a symbol-strategy pair.
5695
5748
  * Delegates to ReportStorage.getData().
5696
5749
  *
5750
+ * @param symbol - Trading pair symbol
5697
5751
  * @param strategyName - Strategy name to get data for
5698
5752
  * @returns Statistical data object with all metrics
5699
5753
  *
5700
5754
  * @example
5701
5755
  * ```typescript
5702
5756
  * const service = new BacktestMarkdownService();
5703
- * const stats = await service.getData("my-strategy");
5757
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
5704
5758
  * console.log(stats.sharpeRatio, stats.winRate);
5705
5759
  * ```
5706
5760
  */
5707
- this.getData = async (strategyName) => {
5761
+ this.getData = async (symbol, strategyName) => {
5708
5762
  this.loggerService.log("backtestMarkdownService getData", {
5763
+ symbol,
5709
5764
  strategyName,
5710
5765
  });
5711
- const storage = this.getStorage(strategyName);
5766
+ const storage = this.getStorage(symbol, strategyName);
5712
5767
  return storage.getData();
5713
5768
  };
5714
5769
  /**
5715
- * Generates markdown report with all closed signals for a strategy.
5770
+ * Generates markdown report with all closed signals for a symbol-strategy pair.
5716
5771
  * Delegates to ReportStorage.generateReport().
5717
5772
  *
5773
+ * @param symbol - Trading pair symbol
5718
5774
  * @param strategyName - Strategy name to generate report for
5719
5775
  * @returns Markdown formatted report string with table of all closed signals
5720
5776
  *
5721
5777
  * @example
5722
5778
  * ```typescript
5723
5779
  * const service = new BacktestMarkdownService();
5724
- * const markdown = await service.getReport("my-strategy");
5780
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
5725
5781
  * console.log(markdown);
5726
5782
  * ```
5727
5783
  */
5728
- this.getReport = async (strategyName) => {
5784
+ this.getReport = async (symbol, strategyName) => {
5729
5785
  this.loggerService.log("backtestMarkdownService getReport", {
5786
+ symbol,
5730
5787
  strategyName,
5731
5788
  });
5732
- const storage = this.getStorage(strategyName);
5789
+ const storage = this.getStorage(symbol, strategyName);
5733
5790
  return storage.getReport(strategyName);
5734
5791
  };
5735
5792
  /**
5736
- * Saves strategy report to disk.
5793
+ * Saves symbol-strategy report to disk.
5737
5794
  * Creates directory if it doesn't exist.
5738
5795
  * Delegates to ReportStorage.dump().
5739
5796
  *
5797
+ * @param symbol - Trading pair symbol
5740
5798
  * @param strategyName - Strategy name to save report for
5741
5799
  * @param path - Directory path to save report (default: "./dump/backtest")
5742
5800
  *
@@ -5745,43 +5803,50 @@ class BacktestMarkdownService {
5745
5803
  * const service = new BacktestMarkdownService();
5746
5804
  *
5747
5805
  * // Save to default path: ./dump/backtest/my-strategy.md
5748
- * await service.dump("my-strategy");
5806
+ * await service.dump("BTCUSDT", "my-strategy");
5749
5807
  *
5750
5808
  * // Save to custom path: ./custom/path/my-strategy.md
5751
- * await service.dump("my-strategy", "./custom/path");
5809
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
5752
5810
  * ```
5753
5811
  */
5754
- this.dump = async (strategyName, path = "./dump/backtest") => {
5812
+ this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
5755
5813
  this.loggerService.log("backtestMarkdownService dump", {
5814
+ symbol,
5756
5815
  strategyName,
5757
5816
  path,
5758
5817
  });
5759
- const storage = this.getStorage(strategyName);
5818
+ const storage = this.getStorage(symbol, strategyName);
5760
5819
  await storage.dump(strategyName, path);
5761
5820
  };
5762
5821
  /**
5763
5822
  * Clears accumulated signal data from storage.
5764
- * If strategyName is provided, clears only that strategy's data.
5765
- * If strategyName is omitted, clears all strategies' data.
5823
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
5824
+ * If nothing is provided, clears all data.
5766
5825
  *
5767
- * @param strategyName - Optional strategy name to clear specific strategy data
5826
+ * @param ctx - Optional context with symbol and strategyName
5768
5827
  *
5769
5828
  * @example
5770
5829
  * ```typescript
5771
5830
  * const service = new BacktestMarkdownService();
5772
5831
  *
5773
- * // Clear specific strategy data
5774
- * await service.clear("my-strategy");
5832
+ * // Clear specific symbol-strategy pair
5833
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
5775
5834
  *
5776
- * // Clear all strategies' data
5835
+ * // Clear all data
5777
5836
  * await service.clear();
5778
5837
  * ```
5779
5838
  */
5780
- this.clear = async (strategyName) => {
5839
+ this.clear = async (ctx) => {
5781
5840
  this.loggerService.log("backtestMarkdownService clear", {
5782
- strategyName,
5841
+ ctx,
5783
5842
  });
5784
- this.getStorage.clear(strategyName);
5843
+ if (ctx) {
5844
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
5845
+ this.getStorage.clear(key);
5846
+ }
5847
+ else {
5848
+ this.getStorage.clear();
5849
+ }
5785
5850
  };
5786
5851
  /**
5787
5852
  * Initializes the service by subscribing to backtest signal events.
@@ -6180,10 +6245,10 @@ class LiveMarkdownService {
6180
6245
  /** Logger service for debug output */
6181
6246
  this.loggerService = inject(TYPES.loggerService);
6182
6247
  /**
6183
- * Memoized function to get or create ReportStorage for a strategy.
6184
- * Each strategy gets its own isolated storage instance.
6248
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6249
+ * Each symbol-strategy combination gets its own isolated storage instance.
6185
6250
  */
6186
- this.getStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, () => new ReportStorage$3());
6251
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$3());
6187
6252
  /**
6188
6253
  * Processes tick events and accumulates all event types.
6189
6254
  * Should be called from IStrategyCallbacks.onTick.
@@ -6209,7 +6274,7 @@ class LiveMarkdownService {
6209
6274
  this.loggerService.log("liveMarkdownService tick", {
6210
6275
  data,
6211
6276
  });
6212
- const storage = this.getStorage(data.strategyName);
6277
+ const storage = this.getStorage(data.symbol, data.strategyName);
6213
6278
  if (data.action === "idle") {
6214
6279
  storage.addIdleEvent(data.currentPrice);
6215
6280
  }
@@ -6224,52 +6289,57 @@ class LiveMarkdownService {
6224
6289
  }
6225
6290
  };
6226
6291
  /**
6227
- * Gets statistical data from all live trading events for a strategy.
6292
+ * Gets statistical data from all live trading events for a symbol-strategy pair.
6228
6293
  * Delegates to ReportStorage.getData().
6229
6294
  *
6295
+ * @param symbol - Trading pair symbol
6230
6296
  * @param strategyName - Strategy name to get data for
6231
6297
  * @returns Statistical data object with all metrics
6232
6298
  *
6233
6299
  * @example
6234
6300
  * ```typescript
6235
6301
  * const service = new LiveMarkdownService();
6236
- * const stats = await service.getData("my-strategy");
6302
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
6237
6303
  * console.log(stats.sharpeRatio, stats.winRate);
6238
6304
  * ```
6239
6305
  */
6240
- this.getData = async (strategyName) => {
6306
+ this.getData = async (symbol, strategyName) => {
6241
6307
  this.loggerService.log("liveMarkdownService getData", {
6308
+ symbol,
6242
6309
  strategyName,
6243
6310
  });
6244
- const storage = this.getStorage(strategyName);
6311
+ const storage = this.getStorage(symbol, strategyName);
6245
6312
  return storage.getData();
6246
6313
  };
6247
6314
  /**
6248
- * Generates markdown report with all events for a strategy.
6315
+ * Generates markdown report with all events for a symbol-strategy pair.
6249
6316
  * Delegates to ReportStorage.getReport().
6250
6317
  *
6318
+ * @param symbol - Trading pair symbol
6251
6319
  * @param strategyName - Strategy name to generate report for
6252
6320
  * @returns Markdown formatted report string with table of all events
6253
6321
  *
6254
6322
  * @example
6255
6323
  * ```typescript
6256
6324
  * const service = new LiveMarkdownService();
6257
- * const markdown = await service.getReport("my-strategy");
6325
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
6258
6326
  * console.log(markdown);
6259
6327
  * ```
6260
6328
  */
6261
- this.getReport = async (strategyName) => {
6329
+ this.getReport = async (symbol, strategyName) => {
6262
6330
  this.loggerService.log("liveMarkdownService getReport", {
6331
+ symbol,
6263
6332
  strategyName,
6264
6333
  });
6265
- const storage = this.getStorage(strategyName);
6334
+ const storage = this.getStorage(symbol, strategyName);
6266
6335
  return storage.getReport(strategyName);
6267
6336
  };
6268
6337
  /**
6269
- * Saves strategy report to disk.
6338
+ * Saves symbol-strategy report to disk.
6270
6339
  * Creates directory if it doesn't exist.
6271
6340
  * Delegates to ReportStorage.dump().
6272
6341
  *
6342
+ * @param symbol - Trading pair symbol
6273
6343
  * @param strategyName - Strategy name to save report for
6274
6344
  * @param path - Directory path to save report (default: "./dump/live")
6275
6345
  *
@@ -6278,43 +6348,50 @@ class LiveMarkdownService {
6278
6348
  * const service = new LiveMarkdownService();
6279
6349
  *
6280
6350
  * // Save to default path: ./dump/live/my-strategy.md
6281
- * await service.dump("my-strategy");
6351
+ * await service.dump("BTCUSDT", "my-strategy");
6282
6352
  *
6283
6353
  * // Save to custom path: ./custom/path/my-strategy.md
6284
- * await service.dump("my-strategy", "./custom/path");
6354
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
6285
6355
  * ```
6286
6356
  */
6287
- this.dump = async (strategyName, path = "./dump/live") => {
6357
+ this.dump = async (symbol, strategyName, path = "./dump/live") => {
6288
6358
  this.loggerService.log("liveMarkdownService dump", {
6359
+ symbol,
6289
6360
  strategyName,
6290
6361
  path,
6291
6362
  });
6292
- const storage = this.getStorage(strategyName);
6363
+ const storage = this.getStorage(symbol, strategyName);
6293
6364
  await storage.dump(strategyName, path);
6294
6365
  };
6295
6366
  /**
6296
6367
  * Clears accumulated event data from storage.
6297
- * If strategyName is provided, clears only that strategy's data.
6298
- * If strategyName is omitted, clears all strategies' data.
6368
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
6369
+ * If nothing is provided, clears all data.
6299
6370
  *
6300
- * @param strategyName - Optional strategy name to clear specific strategy data
6371
+ * @param ctx - Optional context with symbol and strategyName
6301
6372
  *
6302
6373
  * @example
6303
6374
  * ```typescript
6304
6375
  * const service = new LiveMarkdownService();
6305
6376
  *
6306
- * // Clear specific strategy data
6307
- * await service.clear("my-strategy");
6377
+ * // Clear specific symbol-strategy pair
6378
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
6308
6379
  *
6309
- * // Clear all strategies' data
6380
+ * // Clear all data
6310
6381
  * await service.clear();
6311
6382
  * ```
6312
6383
  */
6313
- this.clear = async (strategyName) => {
6384
+ this.clear = async (ctx) => {
6314
6385
  this.loggerService.log("liveMarkdownService clear", {
6315
- strategyName,
6386
+ ctx,
6316
6387
  });
6317
- this.getStorage.clear(strategyName);
6388
+ if (ctx) {
6389
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
6390
+ this.getStorage.clear(key);
6391
+ }
6392
+ else {
6393
+ this.getStorage.clear();
6394
+ }
6318
6395
  };
6319
6396
  /**
6320
6397
  * Initializes the service by subscribing to live signal events.
@@ -6563,10 +6640,10 @@ class ScheduleMarkdownService {
6563
6640
  /** Logger service for debug output */
6564
6641
  this.loggerService = inject(TYPES.loggerService);
6565
6642
  /**
6566
- * Memoized function to get or create ReportStorage for a strategy.
6567
- * Each strategy gets its own isolated storage instance.
6643
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6644
+ * Each symbol-strategy combination gets its own isolated storage instance.
6568
6645
  */
6569
- this.getStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, () => new ReportStorage$2());
6646
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$2());
6570
6647
  /**
6571
6648
  * Processes tick events and accumulates scheduled/cancelled events.
6572
6649
  * Should be called from signalLiveEmitter subscription.
@@ -6585,7 +6662,7 @@ class ScheduleMarkdownService {
6585
6662
  this.loggerService.log("scheduleMarkdownService tick", {
6586
6663
  data,
6587
6664
  });
6588
- const storage = this.getStorage(data.strategyName);
6665
+ const storage = this.getStorage(data.symbol, data.strategyName);
6589
6666
  if (data.action === "scheduled") {
6590
6667
  storage.addScheduledEvent(data);
6591
6668
  }
@@ -6594,52 +6671,57 @@ class ScheduleMarkdownService {
6594
6671
  }
6595
6672
  };
6596
6673
  /**
6597
- * Gets statistical data from all scheduled signal events for a strategy.
6674
+ * Gets statistical data from all scheduled signal events for a symbol-strategy pair.
6598
6675
  * Delegates to ReportStorage.getData().
6599
6676
  *
6677
+ * @param symbol - Trading pair symbol
6600
6678
  * @param strategyName - Strategy name to get data for
6601
6679
  * @returns Statistical data object with all metrics
6602
6680
  *
6603
6681
  * @example
6604
6682
  * ```typescript
6605
6683
  * const service = new ScheduleMarkdownService();
6606
- * const stats = await service.getData("my-strategy");
6684
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
6607
6685
  * console.log(stats.cancellationRate, stats.avgWaitTime);
6608
6686
  * ```
6609
6687
  */
6610
- this.getData = async (strategyName) => {
6688
+ this.getData = async (symbol, strategyName) => {
6611
6689
  this.loggerService.log("scheduleMarkdownService getData", {
6690
+ symbol,
6612
6691
  strategyName,
6613
6692
  });
6614
- const storage = this.getStorage(strategyName);
6693
+ const storage = this.getStorage(symbol, strategyName);
6615
6694
  return storage.getData();
6616
6695
  };
6617
6696
  /**
6618
- * Generates markdown report with all scheduled events for a strategy.
6697
+ * Generates markdown report with all scheduled events for a symbol-strategy pair.
6619
6698
  * Delegates to ReportStorage.getReport().
6620
6699
  *
6700
+ * @param symbol - Trading pair symbol
6621
6701
  * @param strategyName - Strategy name to generate report for
6622
6702
  * @returns Markdown formatted report string with table of all events
6623
6703
  *
6624
6704
  * @example
6625
6705
  * ```typescript
6626
6706
  * const service = new ScheduleMarkdownService();
6627
- * const markdown = await service.getReport("my-strategy");
6707
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
6628
6708
  * console.log(markdown);
6629
6709
  * ```
6630
6710
  */
6631
- this.getReport = async (strategyName) => {
6711
+ this.getReport = async (symbol, strategyName) => {
6632
6712
  this.loggerService.log("scheduleMarkdownService getReport", {
6713
+ symbol,
6633
6714
  strategyName,
6634
6715
  });
6635
- const storage = this.getStorage(strategyName);
6716
+ const storage = this.getStorage(symbol, strategyName);
6636
6717
  return storage.getReport(strategyName);
6637
6718
  };
6638
6719
  /**
6639
- * Saves strategy report to disk.
6720
+ * Saves symbol-strategy report to disk.
6640
6721
  * Creates directory if it doesn't exist.
6641
6722
  * Delegates to ReportStorage.dump().
6642
6723
  *
6724
+ * @param symbol - Trading pair symbol
6643
6725
  * @param strategyName - Strategy name to save report for
6644
6726
  * @param path - Directory path to save report (default: "./dump/schedule")
6645
6727
  *
@@ -6648,43 +6730,50 @@ class ScheduleMarkdownService {
6648
6730
  * const service = new ScheduleMarkdownService();
6649
6731
  *
6650
6732
  * // Save to default path: ./dump/schedule/my-strategy.md
6651
- * await service.dump("my-strategy");
6733
+ * await service.dump("BTCUSDT", "my-strategy");
6652
6734
  *
6653
6735
  * // Save to custom path: ./custom/path/my-strategy.md
6654
- * await service.dump("my-strategy", "./custom/path");
6736
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
6655
6737
  * ```
6656
6738
  */
6657
- this.dump = async (strategyName, path = "./dump/schedule") => {
6739
+ this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
6658
6740
  this.loggerService.log("scheduleMarkdownService dump", {
6741
+ symbol,
6659
6742
  strategyName,
6660
6743
  path,
6661
6744
  });
6662
- const storage = this.getStorage(strategyName);
6745
+ const storage = this.getStorage(symbol, strategyName);
6663
6746
  await storage.dump(strategyName, path);
6664
6747
  };
6665
6748
  /**
6666
6749
  * Clears accumulated event data from storage.
6667
- * If strategyName is provided, clears only that strategy's data.
6668
- * If strategyName is omitted, clears all strategies' data.
6750
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
6751
+ * If nothing is provided, clears all data.
6669
6752
  *
6670
- * @param strategyName - Optional strategy name to clear specific strategy data
6753
+ * @param ctx - Optional context with symbol and strategyName
6671
6754
  *
6672
6755
  * @example
6673
6756
  * ```typescript
6674
6757
  * const service = new ScheduleMarkdownService();
6675
6758
  *
6676
- * // Clear specific strategy data
6677
- * await service.clear("my-strategy");
6759
+ * // Clear specific symbol-strategy pair
6760
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
6678
6761
  *
6679
- * // Clear all strategies' data
6762
+ * // Clear all data
6680
6763
  * await service.clear();
6681
6764
  * ```
6682
6765
  */
6683
- this.clear = async (strategyName) => {
6766
+ this.clear = async (ctx) => {
6684
6767
  this.loggerService.log("scheduleMarkdownService clear", {
6685
- strategyName,
6768
+ ctx,
6686
6769
  });
6687
- this.getStorage.clear(strategyName);
6770
+ if (ctx) {
6771
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
6772
+ this.getStorage.clear(key);
6773
+ }
6774
+ else {
6775
+ this.getStorage.clear();
6776
+ }
6688
6777
  };
6689
6778
  /**
6690
6779
  * Initializes the service by subscribing to live signal events.
@@ -6918,10 +7007,10 @@ class PerformanceMarkdownService {
6918
7007
  /** Logger service for debug output */
6919
7008
  this.loggerService = inject(TYPES.loggerService);
6920
7009
  /**
6921
- * Memoized function to get or create PerformanceStorage for a strategy.
6922
- * Each strategy gets its own isolated storage instance.
7010
+ * Memoized function to get or create PerformanceStorage for a symbol-strategy pair.
7011
+ * Each symbol-strategy combination gets its own isolated storage instance.
6923
7012
  */
6924
- this.getStorage = functoolsKit.memoize(([strategyName]) => `${strategyName}`, () => new PerformanceStorage());
7013
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new PerformanceStorage());
6925
7014
  /**
6926
7015
  * Processes performance events and accumulates metrics.
6927
7016
  * Should be called from performance tracking code.
@@ -6932,83 +7021,97 @@ class PerformanceMarkdownService {
6932
7021
  this.loggerService.log("performanceMarkdownService track", {
6933
7022
  event,
6934
7023
  });
7024
+ const symbol = event.symbol || "global";
6935
7025
  const strategyName = event.strategyName || "global";
6936
- const storage = this.getStorage(strategyName);
7026
+ const storage = this.getStorage(symbol, strategyName);
6937
7027
  storage.addEvent(event);
6938
7028
  };
6939
7029
  /**
6940
- * Gets aggregated performance statistics for a strategy.
7030
+ * Gets aggregated performance statistics for a symbol-strategy pair.
6941
7031
  *
7032
+ * @param symbol - Trading pair symbol
6942
7033
  * @param strategyName - Strategy name to get data for
6943
7034
  * @returns Performance statistics with aggregated metrics
6944
7035
  *
6945
7036
  * @example
6946
7037
  * ```typescript
6947
- * const stats = await performanceService.getData("my-strategy");
7038
+ * const stats = await performanceService.getData("BTCUSDT", "my-strategy");
6948
7039
  * console.log("Total time:", stats.totalDuration);
6949
7040
  * console.log("Slowest operation:", Object.values(stats.metricStats)
6950
7041
  * .sort((a, b) => b.avgDuration - a.avgDuration)[0]);
6951
7042
  * ```
6952
7043
  */
6953
- this.getData = async (strategyName) => {
7044
+ this.getData = async (symbol, strategyName) => {
6954
7045
  this.loggerService.log("performanceMarkdownService getData", {
7046
+ symbol,
6955
7047
  strategyName,
6956
7048
  });
6957
- const storage = this.getStorage(strategyName);
7049
+ const storage = this.getStorage(symbol, strategyName);
6958
7050
  return storage.getData(strategyName);
6959
7051
  };
6960
7052
  /**
6961
7053
  * Generates markdown report with performance analysis.
6962
7054
  *
7055
+ * @param symbol - Trading pair symbol
6963
7056
  * @param strategyName - Strategy name to generate report for
6964
7057
  * @returns Markdown formatted report string
6965
7058
  *
6966
7059
  * @example
6967
7060
  * ```typescript
6968
- * const markdown = await performanceService.getReport("my-strategy");
7061
+ * const markdown = await performanceService.getReport("BTCUSDT", "my-strategy");
6969
7062
  * console.log(markdown);
6970
7063
  * ```
6971
7064
  */
6972
- this.getReport = async (strategyName) => {
7065
+ this.getReport = async (symbol, strategyName) => {
6973
7066
  this.loggerService.log("performanceMarkdownService getReport", {
7067
+ symbol,
6974
7068
  strategyName,
6975
7069
  });
6976
- const storage = this.getStorage(strategyName);
7070
+ const storage = this.getStorage(symbol, strategyName);
6977
7071
  return storage.getReport(strategyName);
6978
7072
  };
6979
7073
  /**
6980
7074
  * Saves performance report to disk.
6981
7075
  *
7076
+ * @param symbol - Trading pair symbol
6982
7077
  * @param strategyName - Strategy name to save report for
6983
7078
  * @param path - Directory path to save report
6984
7079
  *
6985
7080
  * @example
6986
7081
  * ```typescript
6987
7082
  * // Save to default path: ./dump/performance/my-strategy.md
6988
- * await performanceService.dump("my-strategy");
7083
+ * await performanceService.dump("BTCUSDT", "my-strategy");
6989
7084
  *
6990
7085
  * // Save to custom path
6991
- * await performanceService.dump("my-strategy", "./custom/path");
7086
+ * await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
6992
7087
  * ```
6993
7088
  */
6994
- this.dump = async (strategyName, path = "./dump/performance") => {
7089
+ this.dump = async (symbol, strategyName, path = "./dump/performance") => {
6995
7090
  this.loggerService.log("performanceMarkdownService dump", {
7091
+ symbol,
6996
7092
  strategyName,
6997
7093
  path,
6998
7094
  });
6999
- const storage = this.getStorage(strategyName);
7095
+ const storage = this.getStorage(symbol, strategyName);
7000
7096
  await storage.dump(strategyName, path);
7001
7097
  };
7002
7098
  /**
7003
7099
  * Clears accumulated performance data from storage.
7004
7100
  *
7005
- * @param strategyName - Optional strategy name to clear specific strategy data
7101
+ * @param symbol - Optional trading pair symbol
7102
+ * @param strategyName - Optional strategy name
7006
7103
  */
7007
- this.clear = async (strategyName) => {
7104
+ this.clear = async (ctx) => {
7008
7105
  this.loggerService.log("performanceMarkdownService clear", {
7009
- strategyName,
7106
+ ctx,
7010
7107
  });
7011
- this.getStorage.clear(strategyName);
7108
+ if (ctx) {
7109
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
7110
+ this.getStorage.clear(key);
7111
+ }
7112
+ {
7113
+ this.getStorage.clear();
7114
+ }
7012
7115
  };
7013
7116
  /**
7014
7117
  * Initializes the service by subscribing to performance events.
@@ -8702,7 +8805,7 @@ class OptimizerTemplateService {
8702
8805
  };
8703
8806
  /**
8704
8807
  * Generates text() helper for LLM text generation.
8705
- * Uses Ollama gpt-oss:20b model for market analysis.
8808
+ * Uses Ollama deepseek-v3.1:671b model for market analysis.
8706
8809
  *
8707
8810
  * @param symbol - Trading pair symbol (used in prompt)
8708
8811
  * @returns Generated async text() function
@@ -8727,7 +8830,7 @@ class OptimizerTemplateService {
8727
8830
  ` });`,
8728
8831
  ``,
8729
8832
  ` const response = await ollama.chat({`,
8730
- ` model: "gpt-oss:20b",`,
8833
+ ` model: "deepseek-v3.1:671b",`,
8731
8834
  ` messages: [`,
8732
8835
  ` {`,
8733
8836
  ` role: "system",`,
@@ -8792,7 +8895,7 @@ class OptimizerTemplateService {
8792
8895
  ` });`,
8793
8896
  ``,
8794
8897
  ` const response = await ollama.chat({`,
8795
- ` model: "gpt-oss:20b",`,
8898
+ ` model: "deepseek-v3.1:671b",`,
8796
8899
  ` messages: [`,
8797
8900
  ` {`,
8798
8901
  ` role: "system",`,
@@ -12303,11 +12406,11 @@ class BacktestUtils {
12303
12406
  context,
12304
12407
  });
12305
12408
  {
12306
- backtest$1.backtestMarkdownService.clear(context.strategyName);
12307
- backtest$1.scheduleMarkdownService.clear(context.strategyName);
12409
+ backtest$1.backtestMarkdownService.clear({ symbol, strategyName: context.strategyName });
12410
+ backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
12308
12411
  }
12309
12412
  {
12310
- backtest$1.strategyGlobalService.clear(context.strategyName);
12413
+ backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
12311
12414
  }
12312
12415
  {
12313
12416
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -12361,9 +12464,9 @@ class BacktestUtils {
12361
12464
  };
12362
12465
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
12363
12466
  return () => {
12364
- backtest$1.strategyGlobalService.stop(context.strategyName);
12467
+ backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
12365
12468
  backtest$1.strategyGlobalService
12366
- .getPendingSignal(context.strategyName)
12469
+ .getPendingSignal(symbol, context.strategyName)
12367
12470
  .then(async (pendingSignal) => {
12368
12471
  if (pendingSignal) {
12369
12472
  return;
@@ -12382,40 +12485,44 @@ class BacktestUtils {
12382
12485
  };
12383
12486
  };
12384
12487
  /**
12385
- * Gets statistical data from all closed signals for a strategy.
12488
+ * Gets statistical data from all closed signals for a symbol-strategy pair.
12386
12489
  *
12490
+ * @param symbol - Trading pair symbol
12387
12491
  * @param strategyName - Strategy name to get data for
12388
12492
  * @returns Promise resolving to statistical data object
12389
12493
  *
12390
12494
  * @example
12391
12495
  * ```typescript
12392
- * const stats = await Backtest.getData("my-strategy");
12496
+ * const stats = await Backtest.getData("BTCUSDT", "my-strategy");
12393
12497
  * console.log(stats.sharpeRatio, stats.winRate);
12394
12498
  * ```
12395
12499
  */
12396
- this.getData = async (strategyName) => {
12500
+ this.getData = async (symbol, strategyName) => {
12397
12501
  backtest$1.loggerService.info("BacktestUtils.getData", {
12502
+ symbol,
12398
12503
  strategyName,
12399
12504
  });
12400
- return await backtest$1.backtestMarkdownService.getData(strategyName);
12505
+ return await backtest$1.backtestMarkdownService.getData(symbol, strategyName);
12401
12506
  };
12402
12507
  /**
12403
- * Generates markdown report with all closed signals for a strategy.
12508
+ * Generates markdown report with all closed signals for a symbol-strategy pair.
12404
12509
  *
12510
+ * @param symbol - Trading pair symbol
12405
12511
  * @param strategyName - Strategy name to generate report for
12406
12512
  * @returns Promise resolving to markdown formatted report string
12407
12513
  *
12408
12514
  * @example
12409
12515
  * ```typescript
12410
- * const markdown = await Backtest.getReport("my-strategy");
12516
+ * const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
12411
12517
  * console.log(markdown);
12412
12518
  * ```
12413
12519
  */
12414
- this.getReport = async (strategyName) => {
12520
+ this.getReport = async (symbol, strategyName) => {
12415
12521
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
12522
+ symbol,
12416
12523
  strategyName,
12417
12524
  });
12418
- return await backtest$1.backtestMarkdownService.getReport(strategyName);
12525
+ return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
12419
12526
  };
12420
12527
  /**
12421
12528
  * Saves strategy report to disk.
@@ -12512,11 +12619,11 @@ class LiveUtils {
12512
12619
  context,
12513
12620
  });
12514
12621
  {
12515
- backtest$1.liveMarkdownService.clear(context.strategyName);
12516
- backtest$1.scheduleMarkdownService.clear(context.strategyName);
12622
+ backtest$1.liveMarkdownService.clear({ symbol, strategyName: context.strategyName });
12623
+ backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
12517
12624
  }
12518
12625
  {
12519
- backtest$1.strategyGlobalService.clear(context.strategyName);
12626
+ backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
12520
12627
  }
12521
12628
  {
12522
12629
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -12570,9 +12677,9 @@ class LiveUtils {
12570
12677
  };
12571
12678
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
12572
12679
  return () => {
12573
- backtest$1.strategyGlobalService.stop(context.strategyName);
12680
+ backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
12574
12681
  backtest$1.strategyGlobalService
12575
- .getPendingSignal(context.strategyName)
12682
+ .getPendingSignal(symbol, context.strategyName)
12576
12683
  .then(async (pendingSignal) => {
12577
12684
  if (pendingSignal) {
12578
12685
  return;
@@ -12591,40 +12698,44 @@ class LiveUtils {
12591
12698
  };
12592
12699
  };
12593
12700
  /**
12594
- * Gets statistical data from all live trading events for a strategy.
12701
+ * Gets statistical data from all live trading events for a symbol-strategy pair.
12595
12702
  *
12703
+ * @param symbol - Trading pair symbol
12596
12704
  * @param strategyName - Strategy name to get data for
12597
12705
  * @returns Promise resolving to statistical data object
12598
12706
  *
12599
12707
  * @example
12600
12708
  * ```typescript
12601
- * const stats = await Live.getData("my-strategy");
12709
+ * const stats = await Live.getData("BTCUSDT", "my-strategy");
12602
12710
  * console.log(stats.sharpeRatio, stats.winRate);
12603
12711
  * ```
12604
12712
  */
12605
- this.getData = async (strategyName) => {
12713
+ this.getData = async (symbol, strategyName) => {
12606
12714
  backtest$1.loggerService.info("LiveUtils.getData", {
12715
+ symbol,
12607
12716
  strategyName,
12608
12717
  });
12609
- return await backtest$1.liveMarkdownService.getData(strategyName);
12718
+ return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
12610
12719
  };
12611
12720
  /**
12612
- * Generates markdown report with all events for a strategy.
12721
+ * Generates markdown report with all events for a symbol-strategy pair.
12613
12722
  *
12723
+ * @param symbol - Trading pair symbol
12614
12724
  * @param strategyName - Strategy name to generate report for
12615
12725
  * @returns Promise resolving to markdown formatted report string
12616
12726
  *
12617
12727
  * @example
12618
12728
  * ```typescript
12619
- * const markdown = await Live.getReport("my-strategy");
12729
+ * const markdown = await Live.getReport("BTCUSDT", "my-strategy");
12620
12730
  * console.log(markdown);
12621
12731
  * ```
12622
12732
  */
12623
- this.getReport = async (strategyName) => {
12733
+ this.getReport = async (symbol, strategyName) => {
12624
12734
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
12735
+ symbol,
12625
12736
  strategyName,
12626
12737
  });
12627
- return await backtest$1.liveMarkdownService.getReport(strategyName);
12738
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
12628
12739
  };
12629
12740
  /**
12630
12741
  * Saves strategy report to disk.
@@ -12698,40 +12809,44 @@ const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
12698
12809
  class ScheduleUtils {
12699
12810
  constructor() {
12700
12811
  /**
12701
- * Gets statistical data from all scheduled signal events for a strategy.
12812
+ * Gets statistical data from all scheduled signal events for a symbol-strategy pair.
12702
12813
  *
12814
+ * @param symbol - Trading pair symbol
12703
12815
  * @param strategyName - Strategy name to get data for
12704
12816
  * @returns Promise resolving to statistical data object
12705
12817
  *
12706
12818
  * @example
12707
12819
  * ```typescript
12708
- * const stats = await Schedule.getData("my-strategy");
12820
+ * const stats = await Schedule.getData("BTCUSDT", "my-strategy");
12709
12821
  * console.log(stats.cancellationRate, stats.avgWaitTime);
12710
12822
  * ```
12711
12823
  */
12712
- this.getData = async (strategyName) => {
12824
+ this.getData = async (symbol, strategyName) => {
12713
12825
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_DATA, {
12826
+ symbol,
12714
12827
  strategyName,
12715
12828
  });
12716
- return await backtest$1.scheduleMarkdownService.getData(strategyName);
12829
+ return await backtest$1.scheduleMarkdownService.getData(symbol, strategyName);
12717
12830
  };
12718
12831
  /**
12719
- * Generates markdown report with all scheduled events for a strategy.
12832
+ * Generates markdown report with all scheduled events for a symbol-strategy pair.
12720
12833
  *
12834
+ * @param symbol - Trading pair symbol
12721
12835
  * @param strategyName - Strategy name to generate report for
12722
12836
  * @returns Promise resolving to markdown formatted report string
12723
12837
  *
12724
12838
  * @example
12725
12839
  * ```typescript
12726
- * const markdown = await Schedule.getReport("my-strategy");
12840
+ * const markdown = await Schedule.getReport("BTCUSDT", "my-strategy");
12727
12841
  * console.log(markdown);
12728
12842
  * ```
12729
12843
  */
12730
- this.getReport = async (strategyName) => {
12844
+ this.getReport = async (symbol, strategyName) => {
12731
12845
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
12846
+ symbol,
12732
12847
  strategyName,
12733
12848
  });
12734
- return await backtest$1.scheduleMarkdownService.getReport(strategyName);
12849
+ return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
12735
12850
  };
12736
12851
  /**
12737
12852
  * Saves strategy report to disk.
@@ -12803,19 +12918,20 @@ const Schedule = new ScheduleUtils();
12803
12918
  */
12804
12919
  class Performance {
12805
12920
  /**
12806
- * Gets aggregated performance statistics for a strategy.
12921
+ * Gets aggregated performance statistics for a symbol-strategy pair.
12807
12922
  *
12808
12923
  * Returns detailed metrics grouped by operation type:
12809
12924
  * - Count, total duration, average, min, max
12810
12925
  * - Standard deviation for volatility
12811
12926
  * - Percentiles (median, P95, P99) for outlier detection
12812
12927
  *
12928
+ * @param symbol - Trading pair symbol
12813
12929
  * @param strategyName - Strategy name to analyze
12814
12930
  * @returns Performance statistics with aggregated metrics
12815
12931
  *
12816
12932
  * @example
12817
12933
  * ```typescript
12818
- * const stats = await Performance.getData("my-strategy");
12934
+ * const stats = await Performance.getData("BTCUSDT", "my-strategy");
12819
12935
  *
12820
12936
  * // Find slowest operation type
12821
12937
  * const slowest = Object.values(stats.metricStats)
@@ -12830,8 +12946,8 @@ class Performance {
12830
12946
  * }
12831
12947
  * ```
12832
12948
  */
12833
- static async getData(strategyName) {
12834
- return backtest$1.performanceMarkdownService.getData(strategyName);
12949
+ static async getData(symbol, strategyName) {
12950
+ return backtest$1.performanceMarkdownService.getData(symbol, strategyName);
12835
12951
  }
12836
12952
  /**
12837
12953
  * Generates markdown report with performance analysis.
@@ -12841,12 +12957,13 @@ class Performance {
12841
12957
  * - Detailed metrics table with statistics
12842
12958
  * - Percentile analysis for bottleneck detection
12843
12959
  *
12960
+ * @param symbol - Trading pair symbol
12844
12961
  * @param strategyName - Strategy name to generate report for
12845
12962
  * @returns Markdown formatted report string
12846
12963
  *
12847
12964
  * @example
12848
12965
  * ```typescript
12849
- * const markdown = await Performance.getReport("my-strategy");
12966
+ * const markdown = await Performance.getReport("BTCUSDT", "my-strategy");
12850
12967
  * console.log(markdown);
12851
12968
  *
12852
12969
  * // Or save to file
@@ -12854,8 +12971,8 @@ class Performance {
12854
12971
  * await fs.writeFile("performance-report.md", markdown);
12855
12972
  * ```
12856
12973
  */
12857
- static async getReport(strategyName) {
12858
- return backtest$1.performanceMarkdownService.getReport(strategyName);
12974
+ static async getReport(symbol, strategyName) {
12975
+ return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
12859
12976
  }
12860
12977
  /**
12861
12978
  * Saves performance report to disk.
@@ -12878,23 +12995,6 @@ class Performance {
12878
12995
  static async dump(strategyName, path = "./dump/performance") {
12879
12996
  return backtest$1.performanceMarkdownService.dump(strategyName, path);
12880
12997
  }
12881
- /**
12882
- * Clears accumulated performance metrics from memory.
12883
- *
12884
- * @param strategyName - Optional strategy name to clear specific strategy's metrics
12885
- *
12886
- * @example
12887
- * ```typescript
12888
- * // Clear specific strategy metrics
12889
- * await Performance.clear("my-strategy");
12890
- *
12891
- * // Clear all metrics for all strategies
12892
- * await Performance.clear();
12893
- * ```
12894
- */
12895
- static async clear(strategyName) {
12896
- return backtest$1.performanceMarkdownService.clear(strategyName);
12897
- }
12898
12998
  }
12899
12999
 
12900
13000
  const WALKER_METHOD_NAME_RUN = "WalkerUtils.run";
@@ -12946,11 +13046,11 @@ class WalkerUtils {
12946
13046
  // Clear backtest data for all strategies
12947
13047
  for (const strategyName of walkerSchema.strategies) {
12948
13048
  {
12949
- backtest$1.backtestMarkdownService.clear(strategyName);
12950
- backtest$1.scheduleMarkdownService.clear(strategyName);
13049
+ backtest$1.backtestMarkdownService.clear({ symbol, strategyName });
13050
+ backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
12951
13051
  }
12952
13052
  {
12953
- backtest$1.strategyGlobalService.clear(strategyName);
13053
+ backtest$1.strategyGlobalService.clear({ symbol, strategyName });
12954
13054
  }
12955
13055
  {
12956
13056
  const { riskName } = backtest$1.strategySchemaService.get(strategyName);
@@ -13009,9 +13109,9 @@ class WalkerUtils {
13009
13109
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13010
13110
  return () => {
13011
13111
  for (const strategyName of walkerSchema.strategies) {
13012
- backtest$1.strategyGlobalService.stop(strategyName);
13112
+ backtest$1.strategyGlobalService.stop(symbol, strategyName);
13113
+ walkerStopSubject.next({ symbol, strategyName });
13013
13114
  }
13014
- walkerStopSubject.next(context.walkerName);
13015
13115
  if (!isDone) {
13016
13116
  doneWalkerSubject.next({
13017
13117
  exchangeName: walkerSchema.exchangeName,