backtest-kit 1.4.6 → 1.4.7

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