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