backtest-kit 2.2.1 → 2.2.5

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.
Files changed (5) hide show
  1. package/README.md +32 -10
  2. package/build/index.cjs +1997 -178
  3. package/build/index.mjs +1996 -180
  4. package/package.json +2 -1
  5. package/types.d.ts +2039 -963
package/build/index.cjs CHANGED
@@ -121,6 +121,7 @@ const markdownServices$1 = {
121
121
  breakevenMarkdownService: Symbol('breakevenMarkdownService'),
122
122
  outlineMarkdownService: Symbol('outlineMarkdownService'),
123
123
  riskMarkdownService: Symbol('riskMarkdownService'),
124
+ strategyMarkdownService: Symbol('strategyMarkdownService'),
124
125
  };
125
126
  const reportServices$1 = {
126
127
  backtestReportService: Symbol('backtestReportService'),
@@ -132,6 +133,7 @@ const reportServices$1 = {
132
133
  partialReportService: Symbol('partialReportService'),
133
134
  breakevenReportService: Symbol('breakevenReportService'),
134
135
  riskReportService: Symbol('riskReportService'),
136
+ strategyReportService: Symbol('strategyReportService'),
135
137
  };
136
138
  const validationServices$1 = {
137
139
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -540,6 +542,20 @@ const schedulePingSubject = new functoolsKit.Subject();
540
542
  * Allows users to track active signal lifecycle and implement custom dynamic management logic.
541
543
  */
542
544
  const activePingSubject = new functoolsKit.Subject();
545
+ /**
546
+ * Strategy management signal emitter.
547
+ * Emits when strategy management actions are executed:
548
+ * - cancel-scheduled: Scheduled signal cancelled
549
+ * - close-pending: Pending signal closed
550
+ * - partial-profit: Partial close at profit level
551
+ * - partial-loss: Partial close at loss level
552
+ * - trailing-stop: Stop-loss adjusted
553
+ * - trailing-take: Take-profit adjusted
554
+ * - breakeven: Stop-loss moved to entry price
555
+ *
556
+ * Used by StrategyReportService and StrategyMarkdownService for event logging and reporting.
557
+ */
558
+ const strategyCommitSubject = new functoolsKit.Subject();
543
559
 
544
560
  var emitters = /*#__PURE__*/Object.freeze({
545
561
  __proto__: null,
@@ -560,6 +576,7 @@ var emitters = /*#__PURE__*/Object.freeze({
560
576
  signalBacktestEmitter: signalBacktestEmitter,
561
577
  signalEmitter: signalEmitter,
562
578
  signalLiveEmitter: signalLiveEmitter,
579
+ strategyCommitSubject: strategyCommitSubject,
563
580
  validationSubject: validationSubject,
564
581
  walkerCompleteSubject: walkerCompleteSubject,
565
582
  walkerEmitter: walkerEmitter,
@@ -1565,8 +1582,15 @@ class PersistCandleUtils {
1565
1582
  const candle = await stateStorage.readValue(timestamp);
1566
1583
  cachedCandles.push(candle);
1567
1584
  }
1568
- catch {
1569
- // Skip invalid candles
1585
+ catch (error) {
1586
+ const message = `PersistCandleUtils.readCandlesData found invalid candle symbol=${symbol} interval=${interval} timestamp=${timestamp}`;
1587
+ const payload = {
1588
+ error: functoolsKit.errorData(error),
1589
+ message: functoolsKit.getErrorMessage(error),
1590
+ };
1591
+ bt.loggerService.warn(message, payload);
1592
+ console.warn(message, payload);
1593
+ errorEmitter.next(error);
1570
1594
  continue;
1571
1595
  }
1572
1596
  }
@@ -3678,6 +3702,7 @@ const CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN = async (self, scheduled, currentPrice)
3678
3702
  symbol: self.params.execution.context.symbol,
3679
3703
  backtest: self.params.execution.context.backtest,
3680
3704
  reason: "timeout",
3705
+ createdAt: currentTime,
3681
3706
  };
3682
3707
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
3683
3708
  return result;
@@ -3733,6 +3758,7 @@ const CANCEL_SCHEDULED_SIGNAL_BY_STOPLOSS_FN = async (self, scheduled, currentPr
3733
3758
  symbol: self.params.execution.context.symbol,
3734
3759
  backtest: self.params.execution.context.backtest,
3735
3760
  reason: "price_reject",
3761
+ createdAt: currentTime,
3736
3762
  };
3737
3763
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
3738
3764
  return result;
@@ -3788,6 +3814,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
3788
3814
  symbol: self.params.execution.context.symbol,
3789
3815
  currentPrice: self._pendingSignal.priceOpen,
3790
3816
  backtest: self.params.execution.context.backtest,
3817
+ createdAt: activationTime,
3791
3818
  };
3792
3819
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
3793
3820
  return result;
@@ -4220,6 +4247,7 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
4220
4247
  percentSl: 0,
4221
4248
  pnl,
4222
4249
  backtest: self.params.execution.context.backtest,
4250
+ createdAt: currentTime,
4223
4251
  };
4224
4252
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4225
4253
  return result;
@@ -4244,6 +4272,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
4244
4272
  symbol: self.params.execution.context.symbol,
4245
4273
  currentPrice: currentPrice,
4246
4274
  backtest: self.params.execution.context.backtest,
4275
+ createdAt: currentTime,
4247
4276
  };
4248
4277
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4249
4278
  return result;
@@ -4264,6 +4293,7 @@ const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
4264
4293
  symbol: self.params.execution.context.symbol,
4265
4294
  currentPrice: signal.priceOpen,
4266
4295
  backtest: self.params.execution.context.backtest,
4296
+ createdAt: currentTime,
4267
4297
  };
4268
4298
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4269
4299
  return result;
@@ -4328,6 +4358,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
4328
4358
  frameName: self.params.method.context.frameName,
4329
4359
  symbol: self.params.execution.context.symbol,
4330
4360
  backtest: self.params.execution.context.backtest,
4361
+ createdAt: currentTime,
4331
4362
  };
4332
4363
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4333
4364
  return result;
@@ -4401,6 +4432,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4401
4432
  percentSl,
4402
4433
  pnl,
4403
4434
  backtest: self.params.execution.context.backtest,
4435
+ createdAt: currentTime,
4404
4436
  };
4405
4437
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4406
4438
  return result;
@@ -4417,6 +4449,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
4417
4449
  symbol: self.params.execution.context.symbol,
4418
4450
  currentPrice: currentPrice,
4419
4451
  backtest: self.params.execution.context.backtest,
4452
+ createdAt: currentTime,
4420
4453
  };
4421
4454
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4422
4455
  return result;
@@ -4443,6 +4476,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
4443
4476
  symbol: self.params.execution.context.symbol,
4444
4477
  backtest: self.params.execution.context.backtest,
4445
4478
  reason,
4479
+ createdAt: closeTimestamp,
4446
4480
  };
4447
4481
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
4448
4482
  return result;
@@ -4523,6 +4557,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
4523
4557
  frameName: self.params.method.context.frameName,
4524
4558
  symbol: self.params.execution.context.symbol,
4525
4559
  backtest: self.params.execution.context.backtest,
4560
+ createdAt: closeTimestamp,
4526
4561
  };
4527
4562
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
4528
4563
  return result;
@@ -5039,6 +5074,7 @@ class ClientStrategy {
5039
5074
  backtest: this.params.execution.context.backtest,
5040
5075
  reason: "user",
5041
5076
  cancelId: cancelledSignal.cancelId,
5077
+ createdAt: currentTime,
5042
5078
  };
5043
5079
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5044
5080
  return result;
@@ -5073,6 +5109,7 @@ class ClientStrategy {
5073
5109
  symbol: this.params.execution.context.symbol,
5074
5110
  backtest: this.params.execution.context.backtest,
5075
5111
  closeId: closedSignal.closeId,
5112
+ createdAt: currentTime,
5076
5113
  };
5077
5114
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5078
5115
  return result;
@@ -5187,6 +5224,7 @@ class ClientStrategy {
5187
5224
  backtest: true,
5188
5225
  reason: "user",
5189
5226
  cancelId: cancelledSignal.cancelId,
5227
+ createdAt: closeTimestamp,
5190
5228
  };
5191
5229
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledResult, closeTimestamp, this.params.execution.context.backtest);
5192
5230
  return cancelledResult;
@@ -5218,6 +5256,7 @@ class ClientStrategy {
5218
5256
  symbol: this.params.execution.context.symbol,
5219
5257
  backtest: true,
5220
5258
  closeId: closedSignal.closeId,
5259
+ createdAt: closeTimestamp,
5221
5260
  };
5222
5261
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, closedResult, closeTimestamp, this.params.execution.context.backtest);
5223
5262
  return closedResult;
@@ -6533,7 +6572,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
6533
6572
  * @param backtest - Whether running in backtest mode
6534
6573
  * @returns Unique string key for memoization
6535
6574
  */
6536
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
6575
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
6537
6576
  const parts = [symbol, strategyName, exchangeName];
6538
6577
  if (frameName)
6539
6578
  parts.push(frameName);
@@ -6698,7 +6737,7 @@ class StrategyConnectionService {
6698
6737
  * @param backtest - Whether running in backtest mode
6699
6738
  * @returns Configured ClientStrategy instance
6700
6739
  */
6701
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6740
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6702
6741
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
6703
6742
  return new ClientStrategy({
6704
6743
  symbol,
@@ -6953,7 +6992,7 @@ class StrategyConnectionService {
6953
6992
  }
6954
6993
  return;
6955
6994
  }
6956
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6995
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6957
6996
  if (!this.getStrategy.has(key)) {
6958
6997
  return;
6959
6998
  }
@@ -7922,7 +7961,7 @@ class ClientRisk {
7922
7961
  * @param backtest - Whether running in backtest mode
7923
7962
  * @returns Unique string key for memoization
7924
7963
  */
7925
- const CREATE_KEY_FN$j = (riskName, exchangeName, frameName, backtest) => {
7964
+ const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
7926
7965
  const parts = [riskName, exchangeName];
7927
7966
  if (frameName)
7928
7967
  parts.push(frameName);
@@ -8021,7 +8060,7 @@ class RiskConnectionService {
8021
8060
  * @param backtest - True if backtest mode, false if live mode
8022
8061
  * @returns Configured ClientRisk instance
8023
8062
  */
8024
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8063
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8025
8064
  const schema = this.riskSchemaService.get(riskName);
8026
8065
  return new ClientRisk({
8027
8066
  ...schema,
@@ -8089,7 +8128,7 @@ class RiskConnectionService {
8089
8128
  payload,
8090
8129
  });
8091
8130
  if (payload) {
8092
- const key = CREATE_KEY_FN$j(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8131
+ const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8093
8132
  this.getRisk.clear(key);
8094
8133
  }
8095
8134
  else {
@@ -9512,7 +9551,7 @@ class ClientAction {
9512
9551
  * @param backtest - Whether running in backtest mode
9513
9552
  * @returns Unique string key for memoization
9514
9553
  */
9515
- const CREATE_KEY_FN$i = (actionName, strategyName, exchangeName, frameName, backtest) => {
9554
+ const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
9516
9555
  const parts = [actionName, strategyName, exchangeName];
9517
9556
  if (frameName)
9518
9557
  parts.push(frameName);
@@ -9563,7 +9602,7 @@ class ActionConnectionService {
9563
9602
  * @param backtest - True if backtest mode, false if live mode
9564
9603
  * @returns Configured ClientAction instance
9565
9604
  */
9566
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9605
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9567
9606
  const schema = this.actionSchemaService.get(actionName);
9568
9607
  return new ClientAction({
9569
9608
  ...schema,
@@ -9756,7 +9795,7 @@ class ActionConnectionService {
9756
9795
  await Promise.all(actions.map(async (action) => await action.dispose()));
9757
9796
  return;
9758
9797
  }
9759
- const key = CREATE_KEY_FN$i(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9798
+ const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9760
9799
  if (!this.getAction.has(key)) {
9761
9800
  return;
9762
9801
  }
@@ -9774,7 +9813,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
9774
9813
  * @param exchangeName - Exchange name
9775
9814
  * @returns Unique string key for memoization
9776
9815
  */
9777
- const CREATE_KEY_FN$h = (exchangeName) => {
9816
+ const CREATE_KEY_FN$i = (exchangeName) => {
9778
9817
  return exchangeName;
9779
9818
  };
9780
9819
  /**
@@ -9798,7 +9837,7 @@ class ExchangeCoreService {
9798
9837
  * @param exchangeName - Name of the exchange to validate
9799
9838
  * @returns Promise that resolves when validation is complete
9800
9839
  */
9801
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$h(exchangeName), async (exchangeName) => {
9840
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
9802
9841
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
9803
9842
  exchangeName,
9804
9843
  });
@@ -10022,12 +10061,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
10022
10061
  * @param context - Execution context with strategyName, exchangeName, frameName
10023
10062
  * @returns Unique string key for memoization
10024
10063
  */
10025
- const CREATE_KEY_FN$g = (context) => {
10064
+ const CREATE_KEY_FN$h = (context) => {
10026
10065
  const parts = [context.strategyName, context.exchangeName];
10027
10066
  if (context.frameName)
10028
10067
  parts.push(context.frameName);
10029
10068
  return parts.join(":");
10030
10069
  };
10070
+ /**
10071
+ * Broadcasts StrategyCommitContract event to strategyCommitSubject.
10072
+ *
10073
+ * @param event - The signal commit event to broadcast
10074
+ */
10075
+ const CALL_STRATEGY_COMMIT_FN = functoolsKit.trycatch(async (event) => {
10076
+ await strategyCommitSubject.next(event);
10077
+ }, {
10078
+ fallback: (error) => {
10079
+ const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
10080
+ const payload = {
10081
+ error: functoolsKit.errorData(error),
10082
+ message: functoolsKit.getErrorMessage(error),
10083
+ };
10084
+ bt.loggerService.warn(message, payload);
10085
+ console.warn(message, payload);
10086
+ errorEmitter.next(error);
10087
+ },
10088
+ defaultValue: null,
10089
+ });
10031
10090
  /**
10032
10091
  * Global service for strategy operations with execution context injection.
10033
10092
  *
@@ -10054,7 +10113,7 @@ class StrategyCoreService {
10054
10113
  * @param context - Execution context with strategyName, exchangeName, frameName
10055
10114
  * @returns Promise that resolves when validation is complete
10056
10115
  */
10057
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10116
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
10058
10117
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
10059
10118
  context,
10060
10119
  });
@@ -10260,7 +10319,19 @@ class StrategyCoreService {
10260
10319
  cancelId,
10261
10320
  });
10262
10321
  await this.validate(context);
10263
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10322
+ const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10323
+ {
10324
+ await CALL_STRATEGY_COMMIT_FN({
10325
+ action: "cancel-scheduled",
10326
+ symbol,
10327
+ strategyName: context.strategyName,
10328
+ exchangeName: context.exchangeName,
10329
+ frameName: context.frameName,
10330
+ backtest,
10331
+ cancelId,
10332
+ });
10333
+ }
10334
+ return result;
10264
10335
  };
10265
10336
  /**
10266
10337
  * Closes the pending signal without stopping the strategy.
@@ -10287,7 +10358,19 @@ class StrategyCoreService {
10287
10358
  closeId,
10288
10359
  });
10289
10360
  await this.validate(context);
10290
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10361
+ const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10362
+ {
10363
+ await CALL_STRATEGY_COMMIT_FN({
10364
+ action: "close-pending",
10365
+ symbol,
10366
+ strategyName: context.strategyName,
10367
+ exchangeName: context.exchangeName,
10368
+ frameName: context.frameName,
10369
+ backtest,
10370
+ closeId,
10371
+ });
10372
+ }
10373
+ return result;
10291
10374
  };
10292
10375
  /**
10293
10376
  * Disposes the ClientStrategy instance for the given context.
@@ -10368,7 +10451,20 @@ class StrategyCoreService {
10368
10451
  backtest,
10369
10452
  });
10370
10453
  await this.validate(context);
10371
- return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10454
+ const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10455
+ if (result) {
10456
+ await CALL_STRATEGY_COMMIT_FN({
10457
+ action: "partial-profit",
10458
+ symbol,
10459
+ strategyName: context.strategyName,
10460
+ exchangeName: context.exchangeName,
10461
+ frameName: context.frameName,
10462
+ backtest,
10463
+ percentToClose,
10464
+ currentPrice,
10465
+ });
10466
+ }
10467
+ return result;
10372
10468
  };
10373
10469
  /**
10374
10470
  * Executes partial close at loss level (moving toward SL).
@@ -10409,7 +10505,20 @@ class StrategyCoreService {
10409
10505
  backtest,
10410
10506
  });
10411
10507
  await this.validate(context);
10412
- return await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10508
+ const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10509
+ if (result) {
10510
+ await CALL_STRATEGY_COMMIT_FN({
10511
+ action: "partial-loss",
10512
+ symbol,
10513
+ strategyName: context.strategyName,
10514
+ exchangeName: context.exchangeName,
10515
+ frameName: context.frameName,
10516
+ backtest,
10517
+ percentToClose,
10518
+ currentPrice,
10519
+ });
10520
+ }
10521
+ return result;
10413
10522
  };
10414
10523
  /**
10415
10524
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -10448,7 +10557,20 @@ class StrategyCoreService {
10448
10557
  backtest,
10449
10558
  });
10450
10559
  await this.validate(context);
10451
- return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10560
+ const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10561
+ if (result) {
10562
+ await CALL_STRATEGY_COMMIT_FN({
10563
+ action: "trailing-stop",
10564
+ symbol,
10565
+ strategyName: context.strategyName,
10566
+ exchangeName: context.exchangeName,
10567
+ frameName: context.frameName,
10568
+ backtest,
10569
+ percentShift,
10570
+ currentPrice,
10571
+ });
10572
+ }
10573
+ return result;
10452
10574
  };
10453
10575
  /**
10454
10576
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -10483,7 +10605,20 @@ class StrategyCoreService {
10483
10605
  backtest,
10484
10606
  });
10485
10607
  await this.validate(context);
10486
- return await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10608
+ const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10609
+ if (result) {
10610
+ await CALL_STRATEGY_COMMIT_FN({
10611
+ action: "trailing-take",
10612
+ symbol,
10613
+ strategyName: context.strategyName,
10614
+ exchangeName: context.exchangeName,
10615
+ frameName: context.frameName,
10616
+ backtest,
10617
+ percentShift,
10618
+ currentPrice,
10619
+ });
10620
+ }
10621
+ return result;
10487
10622
  };
10488
10623
  /**
10489
10624
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -10513,7 +10648,19 @@ class StrategyCoreService {
10513
10648
  backtest,
10514
10649
  });
10515
10650
  await this.validate(context);
10516
- return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10651
+ const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10652
+ if (result) {
10653
+ await CALL_STRATEGY_COMMIT_FN({
10654
+ action: "breakeven",
10655
+ symbol,
10656
+ strategyName: context.strategyName,
10657
+ exchangeName: context.exchangeName,
10658
+ frameName: context.frameName,
10659
+ backtest,
10660
+ currentPrice,
10661
+ });
10662
+ }
10663
+ return result;
10517
10664
  };
10518
10665
  }
10519
10666
  }
@@ -10587,7 +10734,7 @@ class SizingGlobalService {
10587
10734
  * @param context - Context with riskName, exchangeName, frameName
10588
10735
  * @returns Unique string key for memoization
10589
10736
  */
10590
- const CREATE_KEY_FN$f = (context) => {
10737
+ const CREATE_KEY_FN$g = (context) => {
10591
10738
  const parts = [context.riskName, context.exchangeName];
10592
10739
  if (context.frameName)
10593
10740
  parts.push(context.frameName);
@@ -10613,7 +10760,7 @@ class RiskGlobalService {
10613
10760
  * @param payload - Payload with riskName, exchangeName and frameName
10614
10761
  * @returns Promise that resolves when validation is complete
10615
10762
  */
10616
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10763
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10617
10764
  this.loggerService.log("riskGlobalService validate", {
10618
10765
  context,
10619
10766
  });
@@ -10691,7 +10838,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
10691
10838
  * @param context - Execution context with strategyName, exchangeName, frameName
10692
10839
  * @returns Unique string key for memoization
10693
10840
  */
10694
- const CREATE_KEY_FN$e = (context) => {
10841
+ const CREATE_KEY_FN$f = (context) => {
10695
10842
  const parts = [context.strategyName, context.exchangeName];
10696
10843
  if (context.frameName)
10697
10844
  parts.push(context.frameName);
@@ -10735,7 +10882,7 @@ class ActionCoreService {
10735
10882
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
10736
10883
  * @returns Promise that resolves when all validations complete
10737
10884
  */
10738
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), async (context) => {
10885
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10739
10886
  this.loggerService.log(METHOD_NAME_VALIDATE, {
10740
10887
  context,
10741
10888
  });
@@ -13985,6 +14132,116 @@ const schedule_columns = [
13985
14132
  },
13986
14133
  ];
13987
14134
 
14135
+ /**
14136
+ * Column configuration for strategy markdown reports.
14137
+ *
14138
+ * Defines the table structure for displaying strategy management events in trading reports.
14139
+ * Each column specifies how to format and display strategy actions like partial profit/loss,
14140
+ * trailing stop/take, breakeven, cancel scheduled, and close pending.
14141
+ *
14142
+ * Used by {@link StrategyMarkdownService} to generate markdown tables showing:
14143
+ * - Signal identification (symbol, strategy name, signal ID)
14144
+ * - Action information (action type, percent values, prices)
14145
+ * - Timing information (timestamp, mode: backtest or live)
14146
+ *
14147
+ * @remarks
14148
+ * This configuration tracks all strategy management events - when signals are
14149
+ * modified, cancelled, or closed during their lifecycle.
14150
+ *
14151
+ * @example
14152
+ * ```typescript
14153
+ * import { strategy_columns } from "./assets/strategy.columns";
14154
+ *
14155
+ * // Use with StrategyMarkdownService
14156
+ * const service = new StrategyMarkdownService();
14157
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
14158
+ *
14159
+ * // Or customize to show only key fields
14160
+ * const customColumns = strategy_columns.filter(col =>
14161
+ * ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
14162
+ * );
14163
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
14164
+ * ```
14165
+ *
14166
+ * @see {@link StrategyMarkdownService} for usage in report generation
14167
+ * @see {@link ColumnModel} for column interface definition
14168
+ * @see {@link StrategyEvent} for data structure
14169
+ */
14170
+ const strategy_columns = [
14171
+ {
14172
+ key: "symbol",
14173
+ label: "Symbol",
14174
+ format: (data) => data.symbol,
14175
+ isVisible: () => true,
14176
+ },
14177
+ {
14178
+ key: "strategyName",
14179
+ label: "Strategy",
14180
+ format: (data) => data.strategyName,
14181
+ isVisible: () => true,
14182
+ },
14183
+ {
14184
+ key: "signalId",
14185
+ label: "Signal ID",
14186
+ format: (data) => data.signalId,
14187
+ isVisible: () => true,
14188
+ },
14189
+ {
14190
+ key: "action",
14191
+ label: "Action",
14192
+ format: (data) => data.action,
14193
+ isVisible: () => true,
14194
+ },
14195
+ {
14196
+ key: "currentPrice",
14197
+ label: "Price",
14198
+ format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
14199
+ isVisible: () => true,
14200
+ },
14201
+ {
14202
+ key: "percentToClose",
14203
+ label: "% To Close",
14204
+ format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
14205
+ isVisible: () => true,
14206
+ },
14207
+ {
14208
+ key: "percentShift",
14209
+ label: "% Shift",
14210
+ format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
14211
+ isVisible: () => true,
14212
+ },
14213
+ {
14214
+ key: "cancelId",
14215
+ label: "Cancel ID",
14216
+ format: (data) => data.cancelId || "N/A",
14217
+ isVisible: () => true,
14218
+ },
14219
+ {
14220
+ key: "closeId",
14221
+ label: "Close ID",
14222
+ format: (data) => data.closeId || "N/A",
14223
+ isVisible: () => true,
14224
+ },
14225
+ {
14226
+ key: "createdAt",
14227
+ label: "Created At",
14228
+ format: (data) => data.createdAt || "N/A",
14229
+ isVisible: () => true,
14230
+ },
14231
+ {
14232
+ key: "timestamp",
14233
+ label: "Timestamp",
14234
+ format: (data) => new Date(data.timestamp).toISOString(),
14235
+ isVisible: () => true,
14236
+ },
14237
+ {
14238
+ key: "mode",
14239
+ label: "Mode",
14240
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
14241
+ isVisible: () => true,
14242
+ },
14243
+ ];
14244
+
13988
14245
  /**
13989
14246
  * Column configuration for walker strategy comparison table in markdown reports.
13990
14247
  *
@@ -14198,6 +14455,8 @@ const COLUMN_CONFIG = {
14198
14455
  risk_columns,
14199
14456
  /** Columns for scheduled report output */
14200
14457
  schedule_columns,
14458
+ /** Columns for strategy management events */
14459
+ strategy_columns,
14201
14460
  /** Walker: PnL summary columns */
14202
14461
  walker_pnl_columns,
14203
14462
  /** Walker: strategy-level summary columns */
@@ -14234,6 +14493,7 @@ const WILDCARD_TARGET$1 = {
14234
14493
  partial: true,
14235
14494
  performance: true,
14236
14495
  risk: true,
14496
+ strategy: true,
14237
14497
  schedule: true,
14238
14498
  walker: true,
14239
14499
  };
@@ -14463,7 +14723,7 @@ class MarkdownUtils {
14463
14723
  *
14464
14724
  * @returns Cleanup function that unsubscribes from all enabled services
14465
14725
  */
14466
- this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
14726
+ this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
14467
14727
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
14468
14728
  backtest: bt$1,
14469
14729
  breakeven,
@@ -14472,6 +14732,7 @@ class MarkdownUtils {
14472
14732
  partial,
14473
14733
  performance,
14474
14734
  risk,
14735
+ strategy,
14475
14736
  schedule,
14476
14737
  walker,
14477
14738
  });
@@ -14497,6 +14758,9 @@ class MarkdownUtils {
14497
14758
  if (risk) {
14498
14759
  unList.push(bt.riskMarkdownService.subscribe());
14499
14760
  }
14761
+ if (strategy) {
14762
+ unList.push(bt.strategyMarkdownService.subscribe());
14763
+ }
14500
14764
  if (schedule) {
14501
14765
  unList.push(bt.scheduleMarkdownService.subscribe());
14502
14766
  }
@@ -14542,7 +14806,7 @@ class MarkdownUtils {
14542
14806
  * Markdown.disable();
14543
14807
  * ```
14544
14808
  */
14545
- this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
14809
+ this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
14546
14810
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
14547
14811
  backtest: bt$1,
14548
14812
  breakeven,
@@ -14551,6 +14815,7 @@ class MarkdownUtils {
14551
14815
  partial,
14552
14816
  performance,
14553
14817
  risk,
14818
+ strategy,
14554
14819
  schedule,
14555
14820
  walker,
14556
14821
  });
@@ -14575,6 +14840,9 @@ class MarkdownUtils {
14575
14840
  if (risk) {
14576
14841
  bt.riskMarkdownService.unsubscribe();
14577
14842
  }
14843
+ if (strategy) {
14844
+ bt.strategyMarkdownService.unsubscribe();
14845
+ }
14578
14846
  if (schedule) {
14579
14847
  bt.scheduleMarkdownService.unsubscribe();
14580
14848
  }
@@ -14687,7 +14955,7 @@ const Markdown = new MarkdownAdapter();
14687
14955
  * @param backtest - Whether running in backtest mode
14688
14956
  * @returns Unique string key for memoization
14689
14957
  */
14690
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
14958
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
14691
14959
  const parts = [symbol, strategyName, exchangeName];
14692
14960
  if (frameName)
14693
14961
  parts.push(frameName);
@@ -14704,7 +14972,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
14704
14972
  * @param timestamp - Unix timestamp in milliseconds
14705
14973
  * @returns Filename string
14706
14974
  */
14707
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14975
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14708
14976
  const parts = [symbol, strategyName, exchangeName];
14709
14977
  if (frameName) {
14710
14978
  parts.push(frameName);
@@ -14733,12 +15001,12 @@ function isUnsafe$3(value) {
14733
15001
  return false;
14734
15002
  }
14735
15003
  /** Maximum number of signals to store in backtest reports */
14736
- const MAX_EVENTS$7 = 250;
15004
+ const MAX_EVENTS$8 = 250;
14737
15005
  /**
14738
15006
  * Storage class for accumulating closed signals per strategy.
14739
15007
  * Maintains a list of all closed signals and provides methods to generate reports.
14740
15008
  */
14741
- let ReportStorage$6 = class ReportStorage {
15009
+ let ReportStorage$7 = class ReportStorage {
14742
15010
  constructor(symbol, strategyName, exchangeName, frameName) {
14743
15011
  this.symbol = symbol;
14744
15012
  this.strategyName = strategyName;
@@ -14755,7 +15023,7 @@ let ReportStorage$6 = class ReportStorage {
14755
15023
  addSignal(data) {
14756
15024
  this._signalList.unshift(data);
14757
15025
  // Trim queue if exceeded MAX_EVENTS
14758
- if (this._signalList.length > MAX_EVENTS$7) {
15026
+ if (this._signalList.length > MAX_EVENTS$8) {
14759
15027
  this._signalList.pop();
14760
15028
  }
14761
15029
  }
@@ -14879,7 +15147,7 @@ let ReportStorage$6 = class ReportStorage {
14879
15147
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
14880
15148
  const markdown = await this.getReport(strategyName, columns);
14881
15149
  const timestamp = Date.now();
14882
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15150
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
14883
15151
  await Markdown.writeData("backtest", markdown, {
14884
15152
  path,
14885
15153
  file: filename,
@@ -14926,7 +15194,7 @@ class BacktestMarkdownService {
14926
15194
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
14927
15195
  * Each combination gets its own isolated storage instance.
14928
15196
  */
14929
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
15197
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
14930
15198
  /**
14931
15199
  * Processes tick events and accumulates closed signals.
14932
15200
  * Should be called from IStrategyCallbacks.onTick.
@@ -15083,7 +15351,7 @@ class BacktestMarkdownService {
15083
15351
  payload,
15084
15352
  });
15085
15353
  if (payload) {
15086
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15354
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15087
15355
  this.getStorage.clear(key);
15088
15356
  }
15089
15357
  else {
@@ -15145,7 +15413,7 @@ class BacktestMarkdownService {
15145
15413
  * @param backtest - Whether running in backtest mode
15146
15414
  * @returns Unique string key for memoization
15147
15415
  */
15148
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15416
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
15149
15417
  const parts = [symbol, strategyName, exchangeName];
15150
15418
  if (frameName)
15151
15419
  parts.push(frameName);
@@ -15162,7 +15430,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
15162
15430
  * @param timestamp - Unix timestamp in milliseconds
15163
15431
  * @returns Filename string
15164
15432
  */
15165
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15433
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15166
15434
  const parts = [symbol, strategyName, exchangeName];
15167
15435
  if (frameName) {
15168
15436
  parts.push(frameName);
@@ -15191,12 +15459,12 @@ function isUnsafe$2(value) {
15191
15459
  return false;
15192
15460
  }
15193
15461
  /** Maximum number of events to store in live trading reports */
15194
- const MAX_EVENTS$6 = 250;
15462
+ const MAX_EVENTS$7 = 250;
15195
15463
  /**
15196
15464
  * Storage class for accumulating all tick events per strategy.
15197
15465
  * Maintains a chronological list of all events (idle, opened, active, closed).
15198
15466
  */
15199
- let ReportStorage$5 = class ReportStorage {
15467
+ let ReportStorage$6 = class ReportStorage {
15200
15468
  constructor(symbol, strategyName, exchangeName, frameName) {
15201
15469
  this.symbol = symbol;
15202
15470
  this.strategyName = strategyName;
@@ -15228,7 +15496,7 @@ let ReportStorage$5 = class ReportStorage {
15228
15496
  }
15229
15497
  {
15230
15498
  this._eventList.unshift(newEvent);
15231
- if (this._eventList.length > MAX_EVENTS$6) {
15499
+ if (this._eventList.length > MAX_EVENTS$7) {
15232
15500
  this._eventList.pop();
15233
15501
  }
15234
15502
  }
@@ -15255,7 +15523,7 @@ let ReportStorage$5 = class ReportStorage {
15255
15523
  totalExecuted: data.signal.totalExecuted,
15256
15524
  });
15257
15525
  // Trim queue if exceeded MAX_EVENTS
15258
- if (this._eventList.length > MAX_EVENTS$6) {
15526
+ if (this._eventList.length > MAX_EVENTS$7) {
15259
15527
  this._eventList.pop();
15260
15528
  }
15261
15529
  }
@@ -15294,7 +15562,7 @@ let ReportStorage$5 = class ReportStorage {
15294
15562
  // If no previous active event found, add new event
15295
15563
  this._eventList.unshift(newEvent);
15296
15564
  // Trim queue if exceeded MAX_EVENTS
15297
- if (this._eventList.length > MAX_EVENTS$6) {
15565
+ if (this._eventList.length > MAX_EVENTS$7) {
15298
15566
  this._eventList.pop();
15299
15567
  }
15300
15568
  }
@@ -15326,7 +15594,7 @@ let ReportStorage$5 = class ReportStorage {
15326
15594
  };
15327
15595
  this._eventList.unshift(newEvent);
15328
15596
  // Trim queue if exceeded MAX_EVENTS
15329
- if (this._eventList.length > MAX_EVENTS$6) {
15597
+ if (this._eventList.length > MAX_EVENTS$7) {
15330
15598
  this._eventList.pop();
15331
15599
  }
15332
15600
  }
@@ -15352,7 +15620,7 @@ let ReportStorage$5 = class ReportStorage {
15352
15620
  totalExecuted: data.signal.totalExecuted,
15353
15621
  });
15354
15622
  // Trim queue if exceeded MAX_EVENTS
15355
- if (this._eventList.length > MAX_EVENTS$6) {
15623
+ if (this._eventList.length > MAX_EVENTS$7) {
15356
15624
  this._eventList.pop();
15357
15625
  }
15358
15626
  }
@@ -15391,7 +15659,7 @@ let ReportStorage$5 = class ReportStorage {
15391
15659
  // If no previous waiting event found, add new event
15392
15660
  this._eventList.unshift(newEvent);
15393
15661
  // Trim queue if exceeded MAX_EVENTS
15394
- if (this._eventList.length > MAX_EVENTS$6) {
15662
+ if (this._eventList.length > MAX_EVENTS$7) {
15395
15663
  this._eventList.pop();
15396
15664
  }
15397
15665
  }
@@ -15418,7 +15686,7 @@ let ReportStorage$5 = class ReportStorage {
15418
15686
  cancelReason: data.reason,
15419
15687
  });
15420
15688
  // Trim queue if exceeded MAX_EVENTS
15421
- if (this._eventList.length > MAX_EVENTS$6) {
15689
+ if (this._eventList.length > MAX_EVENTS$7) {
15422
15690
  this._eventList.pop();
15423
15691
  }
15424
15692
  }
@@ -15557,7 +15825,7 @@ let ReportStorage$5 = class ReportStorage {
15557
15825
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
15558
15826
  const markdown = await this.getReport(strategyName, columns);
15559
15827
  const timestamp = Date.now();
15560
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15828
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15561
15829
  await Markdown.writeData("live", markdown, {
15562
15830
  path,
15563
15831
  signalId: "",
@@ -15607,7 +15875,7 @@ class LiveMarkdownService {
15607
15875
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
15608
15876
  * Each combination gets its own isolated storage instance.
15609
15877
  */
15610
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
15878
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
15611
15879
  /**
15612
15880
  * Subscribes to live signal emitter to receive tick events.
15613
15881
  * Protected against multiple subscriptions.
@@ -15825,7 +16093,7 @@ class LiveMarkdownService {
15825
16093
  payload,
15826
16094
  });
15827
16095
  if (payload) {
15828
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16096
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15829
16097
  this.getStorage.clear(key);
15830
16098
  }
15831
16099
  else {
@@ -15845,7 +16113,7 @@ class LiveMarkdownService {
15845
16113
  * @param backtest - Whether running in backtest mode
15846
16114
  * @returns Unique string key for memoization
15847
16115
  */
15848
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16116
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15849
16117
  const parts = [symbol, strategyName, exchangeName];
15850
16118
  if (frameName)
15851
16119
  parts.push(frameName);
@@ -15862,7 +16130,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
15862
16130
  * @param timestamp - Unix timestamp in milliseconds
15863
16131
  * @returns Filename string
15864
16132
  */
15865
- const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16133
+ const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15866
16134
  const parts = [symbol, strategyName, exchangeName];
15867
16135
  if (frameName) {
15868
16136
  parts.push(frameName);
@@ -15873,12 +16141,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
15873
16141
  return `${parts.join("_")}-${timestamp}.md`;
15874
16142
  };
15875
16143
  /** Maximum number of events to store in schedule reports */
15876
- const MAX_EVENTS$5 = 250;
16144
+ const MAX_EVENTS$6 = 250;
15877
16145
  /**
15878
16146
  * Storage class for accumulating scheduled signal events per strategy.
15879
16147
  * Maintains a chronological list of scheduled and cancelled events.
15880
16148
  */
15881
- let ReportStorage$4 = class ReportStorage {
16149
+ let ReportStorage$5 = class ReportStorage {
15882
16150
  constructor(symbol, strategyName, exchangeName, frameName) {
15883
16151
  this.symbol = symbol;
15884
16152
  this.strategyName = strategyName;
@@ -15909,7 +16177,7 @@ let ReportStorage$4 = class ReportStorage {
15909
16177
  totalExecuted: data.signal.totalExecuted,
15910
16178
  });
15911
16179
  // Trim queue if exceeded MAX_EVENTS
15912
- if (this._eventList.length > MAX_EVENTS$5) {
16180
+ if (this._eventList.length > MAX_EVENTS$6) {
15913
16181
  this._eventList.pop();
15914
16182
  }
15915
16183
  }
@@ -15939,7 +16207,7 @@ let ReportStorage$4 = class ReportStorage {
15939
16207
  };
15940
16208
  this._eventList.unshift(newEvent);
15941
16209
  // Trim queue if exceeded MAX_EVENTS
15942
- if (this._eventList.length > MAX_EVENTS$5) {
16210
+ if (this._eventList.length > MAX_EVENTS$6) {
15943
16211
  this._eventList.pop();
15944
16212
  }
15945
16213
  }
@@ -15972,7 +16240,7 @@ let ReportStorage$4 = class ReportStorage {
15972
16240
  };
15973
16241
  this._eventList.unshift(newEvent);
15974
16242
  // Trim queue if exceeded MAX_EVENTS
15975
- if (this._eventList.length > MAX_EVENTS$5) {
16243
+ if (this._eventList.length > MAX_EVENTS$6) {
15976
16244
  this._eventList.pop();
15977
16245
  }
15978
16246
  }
@@ -16079,7 +16347,7 @@ let ReportStorage$4 = class ReportStorage {
16079
16347
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
16080
16348
  const markdown = await this.getReport(strategyName, columns);
16081
16349
  const timestamp = Date.now();
16082
- const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16350
+ const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16083
16351
  await Markdown.writeData("schedule", markdown, {
16084
16352
  path,
16085
16353
  file: filename,
@@ -16120,7 +16388,7 @@ class ScheduleMarkdownService {
16120
16388
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
16121
16389
  * Each combination gets its own isolated storage instance.
16122
16390
  */
16123
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
16391
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
16124
16392
  /**
16125
16393
  * Subscribes to signal emitter to receive scheduled signal events.
16126
16394
  * Protected against multiple subscriptions.
@@ -16323,7 +16591,7 @@ class ScheduleMarkdownService {
16323
16591
  payload,
16324
16592
  });
16325
16593
  if (payload) {
16326
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16594
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16327
16595
  this.getStorage.clear(key);
16328
16596
  }
16329
16597
  else {
@@ -16343,7 +16611,7 @@ class ScheduleMarkdownService {
16343
16611
  * @param backtest - Whether running in backtest mode
16344
16612
  * @returns Unique string key for memoization
16345
16613
  */
16346
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
16614
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16347
16615
  const parts = [symbol, strategyName, exchangeName];
16348
16616
  if (frameName)
16349
16617
  parts.push(frameName);
@@ -16360,7 +16628,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
16360
16628
  * @param timestamp - Unix timestamp in milliseconds
16361
16629
  * @returns Filename string
16362
16630
  */
16363
- const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16631
+ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16364
16632
  const parts = [symbol, strategyName, exchangeName];
16365
16633
  if (frameName) {
16366
16634
  parts.push(frameName);
@@ -16380,7 +16648,7 @@ function percentile(sortedArray, p) {
16380
16648
  return sortedArray[Math.max(0, index)];
16381
16649
  }
16382
16650
  /** Maximum number of performance events to store per strategy */
16383
- const MAX_EVENTS$4 = 10000;
16651
+ const MAX_EVENTS$5 = 10000;
16384
16652
  /**
16385
16653
  * Storage class for accumulating performance metrics per strategy.
16386
16654
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -16402,7 +16670,7 @@ class PerformanceStorage {
16402
16670
  addEvent(event) {
16403
16671
  this._events.unshift(event);
16404
16672
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
16405
- if (this._events.length > MAX_EVENTS$4) {
16673
+ if (this._events.length > MAX_EVENTS$5) {
16406
16674
  this._events.pop();
16407
16675
  }
16408
16676
  }
@@ -16544,7 +16812,7 @@ class PerformanceStorage {
16544
16812
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
16545
16813
  const markdown = await this.getReport(strategyName, columns);
16546
16814
  const timestamp = Date.now();
16547
- const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16815
+ const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16548
16816
  await Markdown.writeData("performance", markdown, {
16549
16817
  path,
16550
16818
  file: filename,
@@ -16591,7 +16859,7 @@ class PerformanceMarkdownService {
16591
16859
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
16592
16860
  * Each combination gets its own isolated storage instance.
16593
16861
  */
16594
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
16862
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
16595
16863
  /**
16596
16864
  * Subscribes to performance emitter to receive performance events.
16597
16865
  * Protected against multiple subscriptions.
@@ -16758,7 +17026,7 @@ class PerformanceMarkdownService {
16758
17026
  payload,
16759
17027
  });
16760
17028
  if (payload) {
16761
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
17029
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16762
17030
  this.getStorage.clear(key);
16763
17031
  }
16764
17032
  else {
@@ -16772,7 +17040,7 @@ class PerformanceMarkdownService {
16772
17040
  * Creates a filename for markdown report based on walker name.
16773
17041
  * Filename format: "walkerName-timestamp.md"
16774
17042
  */
16775
- const CREATE_FILE_NAME_FN$4 = (walkerName, timestamp) => {
17043
+ const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
16776
17044
  return `${walkerName}-${timestamp}.md`;
16777
17045
  };
16778
17046
  /**
@@ -16807,7 +17075,7 @@ function formatMetric(value) {
16807
17075
  * Storage class for accumulating walker results.
16808
17076
  * Maintains a list of all strategy results and provides methods to generate reports.
16809
17077
  */
16810
- let ReportStorage$3 = class ReportStorage {
17078
+ let ReportStorage$4 = class ReportStorage {
16811
17079
  constructor(walkerName) {
16812
17080
  this.walkerName = walkerName;
16813
17081
  /** Walker metadata (set from first addResult call) */
@@ -16995,7 +17263,7 @@ let ReportStorage$3 = class ReportStorage {
16995
17263
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
16996
17264
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
16997
17265
  const timestamp = Date.now();
16998
- const filename = CREATE_FILE_NAME_FN$4(this.walkerName, timestamp);
17266
+ const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
16999
17267
  await Markdown.writeData("walker", markdown, {
17000
17268
  path,
17001
17269
  file: filename,
@@ -17031,7 +17299,7 @@ class WalkerMarkdownService {
17031
17299
  * Memoized function to get or create ReportStorage for a walker.
17032
17300
  * Each walker gets its own isolated storage instance.
17033
17301
  */
17034
- this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$3(walkerName));
17302
+ this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
17035
17303
  /**
17036
17304
  * Subscribes to walker emitter to receive walker progress events.
17037
17305
  * Protected against multiple subscriptions.
@@ -17228,7 +17496,7 @@ class WalkerMarkdownService {
17228
17496
  * @param backtest - Whether running in backtest mode
17229
17497
  * @returns Unique string key for memoization
17230
17498
  */
17231
- const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17499
+ const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
17232
17500
  const parts = [exchangeName];
17233
17501
  if (frameName)
17234
17502
  parts.push(frameName);
@@ -17239,7 +17507,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17239
17507
  * Creates a filename for markdown report based on memoization key components.
17240
17508
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
17241
17509
  */
17242
- const CREATE_FILE_NAME_FN$3 = (strategyName, exchangeName, frameName, timestamp) => {
17510
+ const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
17243
17511
  const parts = [strategyName, exchangeName];
17244
17512
  if (frameName) {
17245
17513
  parts.push(frameName);
@@ -17272,7 +17540,7 @@ function isUnsafe(value) {
17272
17540
  return false;
17273
17541
  }
17274
17542
  /** Maximum number of signals to store per symbol in heatmap reports */
17275
- const MAX_EVENTS$3 = 250;
17543
+ const MAX_EVENTS$4 = 250;
17276
17544
  /**
17277
17545
  * Storage class for accumulating closed signals per strategy and generating heatmap.
17278
17546
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -17298,7 +17566,7 @@ class HeatmapStorage {
17298
17566
  const signals = this.symbolData.get(symbol);
17299
17567
  signals.unshift(data);
17300
17568
  // Trim queue if exceeded MAX_EVENTS per symbol
17301
- if (signals.length > MAX_EVENTS$3) {
17569
+ if (signals.length > MAX_EVENTS$4) {
17302
17570
  signals.pop();
17303
17571
  }
17304
17572
  }
@@ -17549,7 +17817,7 @@ class HeatmapStorage {
17549
17817
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
17550
17818
  const markdown = await this.getReport(strategyName, columns);
17551
17819
  const timestamp = Date.now();
17552
- const filename = CREATE_FILE_NAME_FN$3(strategyName, this.exchangeName, this.frameName, timestamp);
17820
+ const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
17553
17821
  await Markdown.writeData("heat", markdown, {
17554
17822
  path,
17555
17823
  file: filename,
@@ -17595,7 +17863,7 @@ class HeatMarkdownService {
17595
17863
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
17596
17864
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
17597
17865
  */
17598
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17866
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17599
17867
  /**
17600
17868
  * Subscribes to signal emitter to receive tick events.
17601
17869
  * Protected against multiple subscriptions.
@@ -17790,7 +18058,7 @@ class HeatMarkdownService {
17790
18058
  payload,
17791
18059
  });
17792
18060
  if (payload) {
17793
- const key = CREATE_KEY_FN$9(payload.exchangeName, payload.frameName, payload.backtest);
18061
+ const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
17794
18062
  this.getStorage.clear(key);
17795
18063
  }
17796
18064
  else {
@@ -18821,7 +19089,7 @@ class ClientPartial {
18821
19089
  * @param backtest - Whether running in backtest mode
18822
19090
  * @returns Unique string key for memoization
18823
19091
  */
18824
- const CREATE_KEY_FN$8 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
19092
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
18825
19093
  /**
18826
19094
  * Creates a callback function for emitting profit events to partialProfitSubject.
18827
19095
  *
@@ -18943,7 +19211,7 @@ class PartialConnectionService {
18943
19211
  * Key format: "signalId:backtest" or "signalId:live"
18944
19212
  * Value: ClientPartial instance with logger and event emitters
18945
19213
  */
18946
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$8(signalId, backtest), (signalId, backtest) => {
19214
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
18947
19215
  return new ClientPartial({
18948
19216
  signalId,
18949
19217
  logger: this.loggerService,
@@ -19033,7 +19301,7 @@ class PartialConnectionService {
19033
19301
  const partial = this.getPartial(data.id, backtest);
19034
19302
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
19035
19303
  await partial.clear(symbol, data, priceClose, backtest);
19036
- const key = CREATE_KEY_FN$8(data.id, backtest);
19304
+ const key = CREATE_KEY_FN$9(data.id, backtest);
19037
19305
  this.getPartial.clear(key);
19038
19306
  };
19039
19307
  }
@@ -19049,7 +19317,7 @@ class PartialConnectionService {
19049
19317
  * @param backtest - Whether running in backtest mode
19050
19318
  * @returns Unique string key for memoization
19051
19319
  */
19052
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19320
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19053
19321
  const parts = [symbol, strategyName, exchangeName];
19054
19322
  if (frameName)
19055
19323
  parts.push(frameName);
@@ -19060,7 +19328,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
19060
19328
  * Creates a filename for markdown report based on memoization key components.
19061
19329
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
19062
19330
  */
19063
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19331
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19064
19332
  const parts = [symbol, strategyName, exchangeName];
19065
19333
  if (frameName) {
19066
19334
  parts.push(frameName);
@@ -19071,12 +19339,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
19071
19339
  return `${parts.join("_")}-${timestamp}.md`;
19072
19340
  };
19073
19341
  /** Maximum number of events to store in partial reports */
19074
- const MAX_EVENTS$2 = 250;
19342
+ const MAX_EVENTS$3 = 250;
19075
19343
  /**
19076
19344
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
19077
19345
  * Maintains a chronological list of profit and loss level events.
19078
19346
  */
19079
- let ReportStorage$2 = class ReportStorage {
19347
+ let ReportStorage$3 = class ReportStorage {
19080
19348
  constructor(symbol, strategyName, exchangeName, frameName) {
19081
19349
  this.symbol = symbol;
19082
19350
  this.strategyName = strategyName;
@@ -19113,7 +19381,7 @@ let ReportStorage$2 = class ReportStorage {
19113
19381
  backtest,
19114
19382
  });
19115
19383
  // Trim queue if exceeded MAX_EVENTS
19116
- if (this._eventList.length > MAX_EVENTS$2) {
19384
+ if (this._eventList.length > MAX_EVENTS$3) {
19117
19385
  this._eventList.pop();
19118
19386
  }
19119
19387
  }
@@ -19145,7 +19413,7 @@ let ReportStorage$2 = class ReportStorage {
19145
19413
  backtest,
19146
19414
  });
19147
19415
  // Trim queue if exceeded MAX_EVENTS
19148
- if (this._eventList.length > MAX_EVENTS$2) {
19416
+ if (this._eventList.length > MAX_EVENTS$3) {
19149
19417
  this._eventList.pop();
19150
19418
  }
19151
19419
  }
@@ -19221,7 +19489,7 @@ let ReportStorage$2 = class ReportStorage {
19221
19489
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
19222
19490
  const markdown = await this.getReport(symbol, strategyName, columns);
19223
19491
  const timestamp = Date.now();
19224
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19492
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19225
19493
  await Markdown.writeData("partial", markdown, {
19226
19494
  path,
19227
19495
  file: filename,
@@ -19262,7 +19530,7 @@ class PartialMarkdownService {
19262
19530
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19263
19531
  * Each combination gets its own isolated storage instance.
19264
19532
  */
19265
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
19533
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
19266
19534
  /**
19267
19535
  * Subscribes to partial profit/loss signal emitters to receive events.
19268
19536
  * Protected against multiple subscriptions.
@@ -19472,7 +19740,7 @@ class PartialMarkdownService {
19472
19740
  payload,
19473
19741
  });
19474
19742
  if (payload) {
19475
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19743
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19476
19744
  this.getStorage.clear(key);
19477
19745
  }
19478
19746
  else {
@@ -19488,7 +19756,7 @@ class PartialMarkdownService {
19488
19756
  * @param context - Context with strategyName, exchangeName, frameName
19489
19757
  * @returns Unique string key for memoization
19490
19758
  */
19491
- const CREATE_KEY_FN$6 = (context) => {
19759
+ const CREATE_KEY_FN$7 = (context) => {
19492
19760
  const parts = [context.strategyName, context.exchangeName];
19493
19761
  if (context.frameName)
19494
19762
  parts.push(context.frameName);
@@ -19562,7 +19830,7 @@ class PartialGlobalService {
19562
19830
  * @param context - Context with strategyName, exchangeName and frameName
19563
19831
  * @param methodName - Name of the calling method for error tracking
19564
19832
  */
19565
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), (context, methodName) => {
19833
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
19566
19834
  this.loggerService.log("partialGlobalService validate", {
19567
19835
  context,
19568
19836
  methodName,
@@ -20017,7 +20285,7 @@ class ClientBreakeven {
20017
20285
  * @param backtest - Whether running in backtest mode
20018
20286
  * @returns Unique string key for memoization
20019
20287
  */
20020
- const CREATE_KEY_FN$5 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20288
+ const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20021
20289
  /**
20022
20290
  * Creates a callback function for emitting breakeven events to breakevenSubject.
20023
20291
  *
@@ -20103,7 +20371,7 @@ class BreakevenConnectionService {
20103
20371
  * Key format: "signalId:backtest" or "signalId:live"
20104
20372
  * Value: ClientBreakeven instance with logger and event emitter
20105
20373
  */
20106
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$5(signalId, backtest), (signalId, backtest) => {
20374
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
20107
20375
  return new ClientBreakeven({
20108
20376
  signalId,
20109
20377
  logger: this.loggerService,
@@ -20164,7 +20432,7 @@ class BreakevenConnectionService {
20164
20432
  const breakeven = this.getBreakeven(data.id, backtest);
20165
20433
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
20166
20434
  await breakeven.clear(symbol, data, priceClose, backtest);
20167
- const key = CREATE_KEY_FN$5(data.id, backtest);
20435
+ const key = CREATE_KEY_FN$6(data.id, backtest);
20168
20436
  this.getBreakeven.clear(key);
20169
20437
  };
20170
20438
  }
@@ -20180,7 +20448,7 @@ class BreakevenConnectionService {
20180
20448
  * @param backtest - Whether running in backtest mode
20181
20449
  * @returns Unique string key for memoization
20182
20450
  */
20183
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20451
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20184
20452
  const parts = [symbol, strategyName, exchangeName];
20185
20453
  if (frameName)
20186
20454
  parts.push(frameName);
@@ -20191,7 +20459,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
20191
20459
  * Creates a filename for markdown report based on memoization key components.
20192
20460
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20193
20461
  */
20194
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20462
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20195
20463
  const parts = [symbol, strategyName, exchangeName];
20196
20464
  if (frameName) {
20197
20465
  parts.push(frameName);
@@ -20202,12 +20470,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
20202
20470
  return `${parts.join("_")}-${timestamp}.md`;
20203
20471
  };
20204
20472
  /** Maximum number of events to store in breakeven reports */
20205
- const MAX_EVENTS$1 = 250;
20473
+ const MAX_EVENTS$2 = 250;
20206
20474
  /**
20207
20475
  * Storage class for accumulating breakeven events per symbol-strategy pair.
20208
20476
  * Maintains a chronological list of breakeven events.
20209
20477
  */
20210
- let ReportStorage$1 = class ReportStorage {
20478
+ let ReportStorage$2 = class ReportStorage {
20211
20479
  constructor(symbol, strategyName, exchangeName, frameName) {
20212
20480
  this.symbol = symbol;
20213
20481
  this.strategyName = strategyName;
@@ -20242,7 +20510,7 @@ let ReportStorage$1 = class ReportStorage {
20242
20510
  backtest,
20243
20511
  });
20244
20512
  // Trim queue if exceeded MAX_EVENTS
20245
- if (this._eventList.length > MAX_EVENTS$1) {
20513
+ if (this._eventList.length > MAX_EVENTS$2) {
20246
20514
  this._eventList.pop();
20247
20515
  }
20248
20516
  }
@@ -20310,7 +20578,7 @@ let ReportStorage$1 = class ReportStorage {
20310
20578
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
20311
20579
  const markdown = await this.getReport(symbol, strategyName, columns);
20312
20580
  const timestamp = Date.now();
20313
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20581
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20314
20582
  await Markdown.writeData("breakeven", markdown, {
20315
20583
  path,
20316
20584
  file: filename,
@@ -20351,7 +20619,7 @@ class BreakevenMarkdownService {
20351
20619
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20352
20620
  * Each combination gets its own isolated storage instance.
20353
20621
  */
20354
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
20622
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
20355
20623
  /**
20356
20624
  * Subscribes to breakeven signal emitter to receive events.
20357
20625
  * Protected against multiple subscriptions.
@@ -20540,7 +20808,7 @@ class BreakevenMarkdownService {
20540
20808
  payload,
20541
20809
  });
20542
20810
  if (payload) {
20543
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20811
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20544
20812
  this.getStorage.clear(key);
20545
20813
  }
20546
20814
  else {
@@ -20556,7 +20824,7 @@ class BreakevenMarkdownService {
20556
20824
  * @param context - Context with strategyName, exchangeName, frameName
20557
20825
  * @returns Unique string key for memoization
20558
20826
  */
20559
- const CREATE_KEY_FN$3 = (context) => {
20827
+ const CREATE_KEY_FN$4 = (context) => {
20560
20828
  const parts = [context.strategyName, context.exchangeName];
20561
20829
  if (context.frameName)
20562
20830
  parts.push(context.frameName);
@@ -20630,7 +20898,7 @@ class BreakevenGlobalService {
20630
20898
  * @param context - Context with strategyName, exchangeName and frameName
20631
20899
  * @param methodName - Name of the calling method for error tracking
20632
20900
  */
20633
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$3(context), (context, methodName) => {
20901
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
20634
20902
  this.loggerService.log("breakevenGlobalService validate", {
20635
20903
  context,
20636
20904
  methodName,
@@ -20843,7 +21111,7 @@ class ConfigValidationService {
20843
21111
  * @param backtest - Whether running in backtest mode
20844
21112
  * @returns Unique string key for memoization
20845
21113
  */
20846
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
21114
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20847
21115
  const parts = [symbol, strategyName, exchangeName];
20848
21116
  if (frameName)
20849
21117
  parts.push(frameName);
@@ -20854,7 +21122,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
20854
21122
  * Creates a filename for markdown report based on memoization key components.
20855
21123
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20856
21124
  */
20857
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
21125
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20858
21126
  const parts = [symbol, strategyName, exchangeName];
20859
21127
  if (frameName) {
20860
21128
  parts.push(frameName);
@@ -20865,12 +21133,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
20865
21133
  return `${parts.join("_")}-${timestamp}.md`;
20866
21134
  };
20867
21135
  /** Maximum number of events to store in risk reports */
20868
- const MAX_EVENTS = 250;
21136
+ const MAX_EVENTS$1 = 250;
20869
21137
  /**
20870
21138
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
20871
21139
  * Maintains a chronological list of rejected signals due to risk limits.
20872
21140
  */
20873
- class ReportStorage {
21141
+ let ReportStorage$1 = class ReportStorage {
20874
21142
  constructor(symbol, strategyName, exchangeName, frameName) {
20875
21143
  this.symbol = symbol;
20876
21144
  this.strategyName = strategyName;
@@ -20887,7 +21155,7 @@ class ReportStorage {
20887
21155
  addRejectionEvent(event) {
20888
21156
  this._eventList.unshift(event);
20889
21157
  // Trim queue if exceeded MAX_EVENTS
20890
- if (this._eventList.length > MAX_EVENTS) {
21158
+ if (this._eventList.length > MAX_EVENTS$1) {
20891
21159
  this._eventList.pop();
20892
21160
  }
20893
21161
  }
@@ -20971,7 +21239,7 @@ class ReportStorage {
20971
21239
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
20972
21240
  const markdown = await this.getReport(symbol, strategyName, columns);
20973
21241
  const timestamp = Date.now();
20974
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21242
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20975
21243
  await Markdown.writeData("risk", markdown, {
20976
21244
  path,
20977
21245
  file: filename,
@@ -20982,7 +21250,7 @@ class ReportStorage {
20982
21250
  frameName: this.frameName
20983
21251
  });
20984
21252
  }
20985
- }
21253
+ };
20986
21254
  /**
20987
21255
  * Service for generating and saving risk rejection markdown reports.
20988
21256
  *
@@ -21012,7 +21280,7 @@ class RiskMarkdownService {
21012
21280
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21013
21281
  * Each combination gets its own isolated storage instance.
21014
21282
  */
21015
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
21283
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
21016
21284
  /**
21017
21285
  * Subscribes to risk rejection emitter to receive rejection events.
21018
21286
  * Protected against multiple subscriptions.
@@ -21201,7 +21469,7 @@ class RiskMarkdownService {
21201
21469
  payload,
21202
21470
  });
21203
21471
  if (payload) {
21204
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21472
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21205
21473
  this.getStorage.clear(key);
21206
21474
  }
21207
21475
  else {
@@ -21485,6 +21753,7 @@ class ReportDummy {
21485
21753
  */
21486
21754
  const WILDCARD_TARGET = {
21487
21755
  backtest: true,
21756
+ strategy: true,
21488
21757
  breakeven: true,
21489
21758
  heat: true,
21490
21759
  live: true,
@@ -21530,7 +21799,7 @@ class ReportUtils {
21530
21799
  *
21531
21800
  * @returns Cleanup function that unsubscribes from all enabled services
21532
21801
  */
21533
- this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
21802
+ this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, } = WILDCARD_TARGET) => {
21534
21803
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
21535
21804
  backtest: bt$1,
21536
21805
  breakeven,
@@ -21541,6 +21810,7 @@ class ReportUtils {
21541
21810
  risk,
21542
21811
  schedule,
21543
21812
  walker,
21813
+ strategy,
21544
21814
  });
21545
21815
  const unList = [];
21546
21816
  if (bt$1) {
@@ -21570,6 +21840,9 @@ class ReportUtils {
21570
21840
  if (walker) {
21571
21841
  unList.push(bt.walkerReportService.subscribe());
21572
21842
  }
21843
+ if (strategy) {
21844
+ unList.push(bt.scheduleReportService.subscribe());
21845
+ }
21573
21846
  return functoolsKit.compose(...unList.map((un) => () => void un()));
21574
21847
  };
21575
21848
  /**
@@ -21608,7 +21881,7 @@ class ReportUtils {
21608
21881
  * Report.disable();
21609
21882
  * ```
21610
21883
  */
21611
- this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
21884
+ this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, } = WILDCARD_TARGET) => {
21612
21885
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
21613
21886
  backtest: bt$1,
21614
21887
  breakeven,
@@ -21619,6 +21892,7 @@ class ReportUtils {
21619
21892
  risk,
21620
21893
  schedule,
21621
21894
  walker,
21895
+ strategy,
21622
21896
  });
21623
21897
  if (bt$1) {
21624
21898
  bt.backtestReportService.unsubscribe();
@@ -21647,6 +21921,9 @@ class ReportUtils {
21647
21921
  if (walker) {
21648
21922
  bt.walkerReportService.unsubscribe();
21649
21923
  }
21924
+ if (strategy) {
21925
+ bt.strategyReportService.unsubscribe();
21926
+ }
21650
21927
  };
21651
21928
  }
21652
21929
  }
@@ -23082,22 +23359,1243 @@ class RiskReportService {
23082
23359
  }
23083
23360
  }
23084
23361
 
23085
- {
23086
- provide(TYPES.loggerService, () => new LoggerService());
23087
- }
23088
- {
23089
- provide(TYPES.executionContextService, () => new ExecutionContextService());
23090
- provide(TYPES.methodContextService, () => new MethodContextService());
23091
- }
23092
- {
23093
- provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
23094
- provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
23095
- provide(TYPES.frameConnectionService, () => new FrameConnectionService());
23096
- provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
23097
- provide(TYPES.riskConnectionService, () => new RiskConnectionService());
23098
- provide(TYPES.actionConnectionService, () => new ActionConnectionService());
23099
- provide(TYPES.partialConnectionService, () => new PartialConnectionService());
23100
- provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23362
+ /**
23363
+ * Extracts execution context timestamp for strategy event logging.
23364
+ *
23365
+ * @param self - The StrategyReportService instance to extract context from
23366
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23367
+ * @internal
23368
+ */
23369
+ const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
23370
+ if (ExecutionContextService.hasContext()) {
23371
+ const { when } = self.executionContextService.context;
23372
+ return { when: when.toISOString() };
23373
+ }
23374
+ return {
23375
+ when: "",
23376
+ };
23377
+ };
23378
+ /**
23379
+ * Service for persisting strategy management events to JSON report files.
23380
+ *
23381
+ * Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
23382
+ * partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
23383
+ * the Report class. Each event is written as a separate JSON record.
23384
+ *
23385
+ * Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
23386
+ * this service writes each event immediately to disk for audit trail purposes.
23387
+ *
23388
+ * Lifecycle:
23389
+ * - Call subscribe() to enable event logging
23390
+ * - Events are written via Report.writeData() with "strategy" category
23391
+ * - Call unsubscribe() to disable event logging
23392
+ *
23393
+ * @example
23394
+ * ```typescript
23395
+ * // Service is typically used internally by strategy management classes
23396
+ * strategyReportService.subscribe();
23397
+ *
23398
+ * // Events are logged automatically when strategy actions occur
23399
+ * await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
23400
+ * strategyName: "my-strategy",
23401
+ * exchangeName: "binance",
23402
+ * frameName: "1h"
23403
+ * });
23404
+ *
23405
+ * strategyReportService.unsubscribe();
23406
+ * ```
23407
+ *
23408
+ * @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
23409
+ * @see Report for the underlying persistence mechanism
23410
+ */
23411
+ class StrategyReportService {
23412
+ constructor() {
23413
+ this.loggerService = inject(TYPES.loggerService);
23414
+ this.executionContextService = inject(TYPES.executionContextService);
23415
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
23416
+ /**
23417
+ * Logs a cancel-scheduled event when a scheduled signal is cancelled.
23418
+ *
23419
+ * Retrieves the scheduled signal from StrategyCoreService and writes
23420
+ * the cancellation event to the report file.
23421
+ *
23422
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23423
+ * @param isBacktest - Whether this is a backtest or live trading event
23424
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23425
+ * @param cancelId - Optional identifier for the cancellation reason
23426
+ */
23427
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
23428
+ this.loggerService.log("strategyReportService cancelScheduled", {
23429
+ symbol,
23430
+ isBacktest,
23431
+ cancelId,
23432
+ });
23433
+ if (!this.subscribe.hasValue()) {
23434
+ return;
23435
+ }
23436
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23437
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
23438
+ exchangeName: context.exchangeName,
23439
+ strategyName: context.strategyName,
23440
+ frameName: context.frameName,
23441
+ });
23442
+ if (!scheduledRow) {
23443
+ return;
23444
+ }
23445
+ await Report.writeData("strategy", {
23446
+ action: "cancel-scheduled",
23447
+ cancelId,
23448
+ symbol,
23449
+ createdAt,
23450
+ }, {
23451
+ signalId: scheduledRow.id,
23452
+ exchangeName: context.exchangeName,
23453
+ frameName: context.frameName,
23454
+ strategyName: context.strategyName,
23455
+ symbol,
23456
+ walkerName: "",
23457
+ });
23458
+ };
23459
+ /**
23460
+ * Logs a close-pending event when a pending signal is closed.
23461
+ *
23462
+ * Retrieves the pending signal from StrategyCoreService and writes
23463
+ * the close event to the report file.
23464
+ *
23465
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23466
+ * @param isBacktest - Whether this is a backtest or live trading event
23467
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23468
+ * @param closeId - Optional identifier for the close reason
23469
+ */
23470
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
23471
+ this.loggerService.log("strategyReportService closePending", {
23472
+ symbol,
23473
+ isBacktest,
23474
+ closeId,
23475
+ });
23476
+ if (!this.subscribe.hasValue()) {
23477
+ return;
23478
+ }
23479
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23480
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23481
+ exchangeName: context.exchangeName,
23482
+ strategyName: context.strategyName,
23483
+ frameName: context.frameName,
23484
+ });
23485
+ if (!pendingRow) {
23486
+ return;
23487
+ }
23488
+ await Report.writeData("strategy", {
23489
+ action: "close-pending",
23490
+ closeId,
23491
+ symbol,
23492
+ createdAt,
23493
+ }, {
23494
+ signalId: pendingRow.id,
23495
+ exchangeName: context.exchangeName,
23496
+ frameName: context.frameName,
23497
+ strategyName: context.strategyName,
23498
+ symbol,
23499
+ walkerName: "",
23500
+ });
23501
+ };
23502
+ /**
23503
+ * Logs a partial-profit event when a portion of the position is closed at profit.
23504
+ *
23505
+ * Records the percentage closed and current price when partial profit-taking occurs.
23506
+ *
23507
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23508
+ * @param percentToClose - Percentage of position to close (0-100)
23509
+ * @param currentPrice - Current market price at time of partial close
23510
+ * @param isBacktest - Whether this is a backtest or live trading event
23511
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23512
+ */
23513
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23514
+ this.loggerService.log("strategyReportService partialProfit", {
23515
+ symbol,
23516
+ percentToClose,
23517
+ currentPrice,
23518
+ isBacktest,
23519
+ });
23520
+ if (!this.subscribe.hasValue()) {
23521
+ return;
23522
+ }
23523
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23524
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23525
+ exchangeName: context.exchangeName,
23526
+ strategyName: context.strategyName,
23527
+ frameName: context.frameName,
23528
+ });
23529
+ if (!pendingRow) {
23530
+ return;
23531
+ }
23532
+ await Report.writeData("strategy", {
23533
+ action: "partial-profit",
23534
+ percentToClose,
23535
+ currentPrice,
23536
+ symbol,
23537
+ createdAt,
23538
+ }, {
23539
+ signalId: pendingRow.id,
23540
+ exchangeName: context.exchangeName,
23541
+ frameName: context.frameName,
23542
+ strategyName: context.strategyName,
23543
+ symbol,
23544
+ walkerName: "",
23545
+ });
23546
+ };
23547
+ /**
23548
+ * Logs a partial-loss event when a portion of the position is closed at loss.
23549
+ *
23550
+ * Records the percentage closed and current price when partial loss-cutting occurs.
23551
+ *
23552
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23553
+ * @param percentToClose - Percentage of position to close (0-100)
23554
+ * @param currentPrice - Current market price at time of partial close
23555
+ * @param isBacktest - Whether this is a backtest or live trading event
23556
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23557
+ */
23558
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23559
+ this.loggerService.log("strategyReportService partialLoss", {
23560
+ symbol,
23561
+ percentToClose,
23562
+ currentPrice,
23563
+ isBacktest,
23564
+ });
23565
+ if (!this.subscribe.hasValue()) {
23566
+ return;
23567
+ }
23568
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23569
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23570
+ exchangeName: context.exchangeName,
23571
+ strategyName: context.strategyName,
23572
+ frameName: context.frameName,
23573
+ });
23574
+ if (!pendingRow) {
23575
+ return;
23576
+ }
23577
+ await Report.writeData("strategy", {
23578
+ action: "partial-loss",
23579
+ percentToClose,
23580
+ currentPrice,
23581
+ symbol,
23582
+ createdAt,
23583
+ }, {
23584
+ signalId: pendingRow.id,
23585
+ exchangeName: context.exchangeName,
23586
+ frameName: context.frameName,
23587
+ strategyName: context.strategyName,
23588
+ symbol,
23589
+ walkerName: "",
23590
+ });
23591
+ };
23592
+ /**
23593
+ * Logs a trailing-stop event when the stop-loss is adjusted.
23594
+ *
23595
+ * Records the percentage shift and current price when trailing stop moves.
23596
+ *
23597
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23598
+ * @param percentShift - Percentage the stop-loss was shifted
23599
+ * @param currentPrice - Current market price at time of adjustment
23600
+ * @param isBacktest - Whether this is a backtest or live trading event
23601
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23602
+ */
23603
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23604
+ this.loggerService.log("strategyReportService trailingStop", {
23605
+ symbol,
23606
+ percentShift,
23607
+ currentPrice,
23608
+ isBacktest,
23609
+ });
23610
+ if (!this.subscribe.hasValue()) {
23611
+ return;
23612
+ }
23613
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23614
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23615
+ exchangeName: context.exchangeName,
23616
+ strategyName: context.strategyName,
23617
+ frameName: context.frameName,
23618
+ });
23619
+ if (!pendingRow) {
23620
+ return;
23621
+ }
23622
+ await Report.writeData("strategy", {
23623
+ action: "trailing-stop",
23624
+ percentShift,
23625
+ currentPrice,
23626
+ symbol,
23627
+ createdAt,
23628
+ }, {
23629
+ signalId: pendingRow.id,
23630
+ exchangeName: context.exchangeName,
23631
+ frameName: context.frameName,
23632
+ strategyName: context.strategyName,
23633
+ symbol,
23634
+ walkerName: "",
23635
+ });
23636
+ };
23637
+ /**
23638
+ * Logs a trailing-take event when the take-profit is adjusted.
23639
+ *
23640
+ * Records the percentage shift and current price when trailing take-profit moves.
23641
+ *
23642
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23643
+ * @param percentShift - Percentage the take-profit was shifted
23644
+ * @param currentPrice - Current market price at time of adjustment
23645
+ * @param isBacktest - Whether this is a backtest or live trading event
23646
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23647
+ */
23648
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23649
+ this.loggerService.log("strategyReportService trailingTake", {
23650
+ symbol,
23651
+ percentShift,
23652
+ currentPrice,
23653
+ isBacktest,
23654
+ });
23655
+ if (!this.subscribe.hasValue()) {
23656
+ return;
23657
+ }
23658
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23659
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23660
+ exchangeName: context.exchangeName,
23661
+ strategyName: context.strategyName,
23662
+ frameName: context.frameName,
23663
+ });
23664
+ if (!pendingRow) {
23665
+ return;
23666
+ }
23667
+ await Report.writeData("strategy", {
23668
+ action: "trailing-take",
23669
+ percentShift,
23670
+ currentPrice,
23671
+ symbol,
23672
+ createdAt,
23673
+ }, {
23674
+ signalId: pendingRow.id,
23675
+ exchangeName: context.exchangeName,
23676
+ frameName: context.frameName,
23677
+ strategyName: context.strategyName,
23678
+ symbol,
23679
+ walkerName: "",
23680
+ });
23681
+ };
23682
+ /**
23683
+ * Logs a breakeven event when the stop-loss is moved to entry price.
23684
+ *
23685
+ * Records the current price when breakeven protection is activated.
23686
+ *
23687
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23688
+ * @param currentPrice - Current market price at time of breakeven activation
23689
+ * @param isBacktest - Whether this is a backtest or live trading event
23690
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23691
+ */
23692
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
23693
+ this.loggerService.log("strategyReportService breakeven", {
23694
+ symbol,
23695
+ currentPrice,
23696
+ isBacktest,
23697
+ });
23698
+ if (!this.subscribe.hasValue()) {
23699
+ return;
23700
+ }
23701
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23702
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23703
+ exchangeName: context.exchangeName,
23704
+ strategyName: context.strategyName,
23705
+ frameName: context.frameName,
23706
+ });
23707
+ if (!pendingRow) {
23708
+ return;
23709
+ }
23710
+ await Report.writeData("strategy", {
23711
+ action: "breakeven",
23712
+ currentPrice,
23713
+ symbol,
23714
+ createdAt,
23715
+ }, {
23716
+ signalId: pendingRow.id,
23717
+ exchangeName: context.exchangeName,
23718
+ frameName: context.frameName,
23719
+ strategyName: context.strategyName,
23720
+ symbol,
23721
+ walkerName: "",
23722
+ });
23723
+ };
23724
+ /**
23725
+ * Initializes the service for event logging.
23726
+ *
23727
+ * Must be called before any events can be logged. Uses singleshot pattern
23728
+ * to ensure only one subscription exists at a time.
23729
+ *
23730
+ * @returns Cleanup function that clears the subscription when called
23731
+ */
23732
+ this.subscribe = functoolsKit.singleshot(() => {
23733
+ this.loggerService.log("strategyReportService subscribe");
23734
+ const unCancelSchedule = strategyCommitSubject
23735
+ .filter(({ action }) => action === "cancel-scheduled")
23736
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
23737
+ exchangeName: event.exchangeName,
23738
+ frameName: event.frameName,
23739
+ strategyName: event.strategyName,
23740
+ }, event.cancelId));
23741
+ const unClosePending = strategyCommitSubject
23742
+ .filter(({ action }) => action === "close-pending")
23743
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
23744
+ exchangeName: event.exchangeName,
23745
+ frameName: event.frameName,
23746
+ strategyName: event.strategyName,
23747
+ }, event.closeId));
23748
+ const unPartialProfit = strategyCommitSubject
23749
+ .filter(({ action }) => action === "partial-profit")
23750
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23751
+ exchangeName: event.exchangeName,
23752
+ frameName: event.frameName,
23753
+ strategyName: event.strategyName,
23754
+ }));
23755
+ const unPartialLoss = strategyCommitSubject
23756
+ .filter(({ action }) => action === "partial-loss")
23757
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23758
+ exchangeName: event.exchangeName,
23759
+ frameName: event.frameName,
23760
+ strategyName: event.strategyName,
23761
+ }));
23762
+ const unTrailingStop = strategyCommitSubject
23763
+ .filter(({ action }) => action === "trailing-stop")
23764
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23765
+ exchangeName: event.exchangeName,
23766
+ frameName: event.frameName,
23767
+ strategyName: event.strategyName,
23768
+ }));
23769
+ const unTrailingTake = strategyCommitSubject
23770
+ .filter(({ action }) => action === "trailing-take")
23771
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23772
+ exchangeName: event.exchangeName,
23773
+ frameName: event.frameName,
23774
+ strategyName: event.strategyName,
23775
+ }));
23776
+ const unBreakeven = strategyCommitSubject
23777
+ .filter(({ action }) => action === "breakeven")
23778
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
23779
+ exchangeName: event.exchangeName,
23780
+ frameName: event.frameName,
23781
+ strategyName: event.strategyName,
23782
+ }));
23783
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
23784
+ return () => {
23785
+ disposeFn();
23786
+ this.subscribe.clear();
23787
+ };
23788
+ });
23789
+ /**
23790
+ * Stops event logging and cleans up the subscription.
23791
+ *
23792
+ * Safe to call multiple times - only clears if subscription exists.
23793
+ */
23794
+ this.unsubscribe = async () => {
23795
+ this.loggerService.log("strategyReportService unsubscribe");
23796
+ if (this.subscribe.hasValue()) {
23797
+ const lastSubscription = this.subscribe();
23798
+ lastSubscription();
23799
+ }
23800
+ };
23801
+ }
23802
+ }
23803
+
23804
+ /**
23805
+ * Extracts execution context timestamp for strategy event logging.
23806
+ *
23807
+ * @param self - The StrategyMarkdownService instance to extract context from
23808
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23809
+ * @internal
23810
+ */
23811
+ const GET_EXECUTION_CONTEXT_FN = (self) => {
23812
+ if (ExecutionContextService.hasContext()) {
23813
+ const { when } = self.executionContextService.context;
23814
+ return { when: when.toISOString() };
23815
+ }
23816
+ return {
23817
+ when: "",
23818
+ };
23819
+ };
23820
+ /**
23821
+ * Creates a unique key for memoizing ReportStorage instances.
23822
+ *
23823
+ * Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
23824
+ *
23825
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23826
+ * @param strategyName - Name of the trading strategy
23827
+ * @param exchangeName - Name of the exchange
23828
+ * @param frameName - Timeframe name (optional, included if present)
23829
+ * @param backtest - Whether this is backtest or live mode
23830
+ * @returns Colon-separated key string for memoization
23831
+ * @internal
23832
+ */
23833
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23834
+ const parts = [symbol, strategyName, exchangeName];
23835
+ if (frameName)
23836
+ parts.push(frameName);
23837
+ parts.push(backtest ? "backtest" : "live");
23838
+ return parts.join(":");
23839
+ };
23840
+ /**
23841
+ * Creates a filename for markdown report output.
23842
+ *
23843
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
23844
+ *
23845
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23846
+ * @param strategyName - Name of the trading strategy
23847
+ * @param exchangeName - Name of the exchange
23848
+ * @param frameName - Timeframe name (indicates backtest mode if present)
23849
+ * @param timestamp - Unix timestamp in milliseconds for uniqueness
23850
+ * @returns Underscore-separated filename with .md extension
23851
+ * @internal
23852
+ */
23853
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23854
+ const parts = [symbol, strategyName, exchangeName];
23855
+ if (frameName) {
23856
+ parts.push(frameName);
23857
+ parts.push("backtest");
23858
+ }
23859
+ else
23860
+ parts.push("live");
23861
+ return `${parts.join("_")}-${timestamp}.md`;
23862
+ };
23863
+ /**
23864
+ * Maximum number of events to store per symbol-strategy pair.
23865
+ * Older events are discarded when this limit is exceeded.
23866
+ * @internal
23867
+ */
23868
+ const MAX_EVENTS = 250;
23869
+ /**
23870
+ * In-memory storage for accumulating strategy events per symbol-strategy pair.
23871
+ *
23872
+ * Maintains a rolling window of the most recent events (up to MAX_EVENTS),
23873
+ * with newer events added to the front of the list. Provides methods to:
23874
+ * - Add new events (FIFO queue with max size)
23875
+ * - Retrieve aggregated statistics
23876
+ * - Generate markdown reports
23877
+ * - Dump reports to disk
23878
+ *
23879
+ * @internal
23880
+ */
23881
+ class ReportStorage {
23882
+ /**
23883
+ * Creates a new ReportStorage instance.
23884
+ *
23885
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23886
+ * @param strategyName - Name of the trading strategy
23887
+ * @param exchangeName - Name of the exchange
23888
+ * @param frameName - Timeframe name for backtest identification
23889
+ */
23890
+ constructor(symbol, strategyName, exchangeName, frameName) {
23891
+ this.symbol = symbol;
23892
+ this.strategyName = strategyName;
23893
+ this.exchangeName = exchangeName;
23894
+ this.frameName = frameName;
23895
+ this._eventList = [];
23896
+ }
23897
+ /**
23898
+ * Adds a new event to the storage.
23899
+ *
23900
+ * Events are added to the front of the list (most recent first).
23901
+ * If the list exceeds MAX_EVENTS, the oldest event is removed.
23902
+ *
23903
+ * @param event - The strategy event to store
23904
+ */
23905
+ addEvent(event) {
23906
+ this._eventList.unshift(event);
23907
+ if (this._eventList.length > MAX_EVENTS) {
23908
+ this._eventList.pop();
23909
+ }
23910
+ }
23911
+ /**
23912
+ * Retrieves aggregated statistics from stored events.
23913
+ *
23914
+ * Calculates counts for each action type from the event list.
23915
+ *
23916
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
23917
+ */
23918
+ async getData() {
23919
+ if (this._eventList.length === 0) {
23920
+ return {
23921
+ eventList: [],
23922
+ totalEvents: 0,
23923
+ cancelScheduledCount: 0,
23924
+ closePendingCount: 0,
23925
+ partialProfitCount: 0,
23926
+ partialLossCount: 0,
23927
+ trailingStopCount: 0,
23928
+ trailingTakeCount: 0,
23929
+ breakevenCount: 0,
23930
+ };
23931
+ }
23932
+ return {
23933
+ eventList: this._eventList,
23934
+ totalEvents: this._eventList.length,
23935
+ cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
23936
+ closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
23937
+ partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
23938
+ partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
23939
+ trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
23940
+ trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
23941
+ breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
23942
+ };
23943
+ }
23944
+ /**
23945
+ * Generates a markdown report from stored events.
23946
+ *
23947
+ * Creates a formatted markdown document containing:
23948
+ * - Header with symbol and strategy name
23949
+ * - Table of all events with configurable columns
23950
+ * - Summary statistics with counts by action type
23951
+ *
23952
+ * @param symbol - Trading pair symbol for report header
23953
+ * @param strategyName - Strategy name for report header
23954
+ * @param columns - Column configuration for the event table
23955
+ * @returns Promise resolving to formatted markdown string
23956
+ */
23957
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
23958
+ const stats = await this.getData();
23959
+ if (stats.totalEvents === 0) {
23960
+ return [
23961
+ `# Strategy Report: ${symbol}:${strategyName}`,
23962
+ "",
23963
+ "No strategy events recorded yet."
23964
+ ].join("\n");
23965
+ }
23966
+ const visibleColumns = [];
23967
+ for (const col of columns) {
23968
+ if (await col.isVisible()) {
23969
+ visibleColumns.push(col);
23970
+ }
23971
+ }
23972
+ const header = visibleColumns.map((col) => col.label);
23973
+ const separator = visibleColumns.map(() => "---");
23974
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
23975
+ const tableData = [header, separator, ...rows];
23976
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
23977
+ return [
23978
+ `# Strategy Report: ${symbol}:${strategyName}`,
23979
+ "",
23980
+ table,
23981
+ "",
23982
+ `**Total events:** ${stats.totalEvents}`,
23983
+ `- Cancel scheduled: ${stats.cancelScheduledCount}`,
23984
+ `- Close pending: ${stats.closePendingCount}`,
23985
+ `- Partial profit: ${stats.partialProfitCount}`,
23986
+ `- Partial loss: ${stats.partialLossCount}`,
23987
+ `- Trailing stop: ${stats.trailingStopCount}`,
23988
+ `- Trailing take: ${stats.trailingTakeCount}`,
23989
+ `- Breakeven: ${stats.breakevenCount}`,
23990
+ ].join("\n");
23991
+ }
23992
+ /**
23993
+ * Generates and saves a markdown report to disk.
23994
+ *
23995
+ * Creates the output directory if it doesn't exist and writes
23996
+ * the report with a timestamped filename.
23997
+ *
23998
+ * @param symbol - Trading pair symbol for report
23999
+ * @param strategyName - Strategy name for report
24000
+ * @param path - Output directory path (default: "./dump/strategy")
24001
+ * @param columns - Column configuration for the event table
24002
+ */
24003
+ async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
24004
+ const markdown = await this.getReport(symbol, strategyName, columns);
24005
+ const timestamp = Date.now();
24006
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24007
+ await Markdown.writeData("strategy", markdown, {
24008
+ path,
24009
+ file: filename,
24010
+ symbol: this.symbol,
24011
+ strategyName: this.strategyName,
24012
+ exchangeName: this.exchangeName,
24013
+ signalId: "",
24014
+ frameName: this.frameName
24015
+ });
24016
+ }
24017
+ }
24018
+ /**
24019
+ * Service for accumulating strategy management events and generating markdown reports.
24020
+ *
24021
+ * Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
24022
+ * partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
24023
+ * methods to retrieve statistics, generate reports, and export to files.
24024
+ *
24025
+ * Unlike StrategyReportService which writes each event to disk immediately,
24026
+ * this service accumulates events in ReportStorage instances (max 250 per
24027
+ * symbol-strategy pair) for batch reporting.
24028
+ *
24029
+ * Features:
24030
+ * - In-memory event accumulation with memoized storage per symbol-strategy pair
24031
+ * - Statistical data extraction (event counts by action type)
24032
+ * - Markdown report generation with configurable columns
24033
+ * - File export with timestamped filenames
24034
+ * - Selective or full cache clearing
24035
+ *
24036
+ * Lifecycle:
24037
+ * - Call subscribe() to enable event collection
24038
+ * - Events are collected automatically via cancelScheduled, closePending, etc.
24039
+ * - Use getData(), getReport(), or dump() to retrieve accumulated data
24040
+ * - Call unsubscribe() to disable collection and clear all data
24041
+ *
24042
+ * @example
24043
+ * ```typescript
24044
+ * strategyMarkdownService.subscribe();
24045
+ *
24046
+ * // Events are collected automatically during strategy execution
24047
+ * // ...
24048
+ *
24049
+ * // Get statistics
24050
+ * const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
24051
+ *
24052
+ * // Generate markdown report
24053
+ * const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
24054
+ *
24055
+ * // Export to file
24056
+ * await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
24057
+ *
24058
+ * strategyMarkdownService.unsubscribe();
24059
+ * ```
24060
+ *
24061
+ * @see StrategyReportService for immediate event persistence to JSON files
24062
+ * @see Strategy for the high-level utility class that wraps this service
24063
+ */
24064
+ class StrategyMarkdownService {
24065
+ constructor() {
24066
+ this.loggerService = inject(TYPES.loggerService);
24067
+ this.executionContextService = inject(TYPES.executionContextService);
24068
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
24069
+ /**
24070
+ * Memoized factory for ReportStorage instances.
24071
+ *
24072
+ * Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
24073
+ * Uses CREATE_KEY_FN for cache key generation.
24074
+ *
24075
+ * @internal
24076
+ */
24077
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
24078
+ /**
24079
+ * Records a cancel-scheduled event when a scheduled signal is cancelled.
24080
+ *
24081
+ * Retrieves the scheduled signal from StrategyCoreService and stores
24082
+ * the cancellation event in the appropriate ReportStorage.
24083
+ *
24084
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24085
+ * @param isBacktest - Whether this is a backtest or live trading event
24086
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24087
+ * @param cancelId - Optional identifier for the cancellation reason
24088
+ */
24089
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
24090
+ this.loggerService.log("strategyMarkdownService cancelScheduled", {
24091
+ symbol,
24092
+ isBacktest,
24093
+ cancelId,
24094
+ });
24095
+ if (!this.subscribe.hasValue()) {
24096
+ return;
24097
+ }
24098
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24099
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24100
+ exchangeName: context.exchangeName,
24101
+ strategyName: context.strategyName,
24102
+ frameName: context.frameName,
24103
+ });
24104
+ if (!scheduledRow) {
24105
+ return;
24106
+ }
24107
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24108
+ storage.addEvent({
24109
+ timestamp: Date.now(),
24110
+ symbol,
24111
+ strategyName: context.strategyName,
24112
+ exchangeName: context.exchangeName,
24113
+ frameName: context.frameName,
24114
+ signalId: scheduledRow.id,
24115
+ action: "cancel-scheduled",
24116
+ cancelId,
24117
+ createdAt,
24118
+ backtest: isBacktest,
24119
+ });
24120
+ };
24121
+ /**
24122
+ * Records a close-pending event when a pending signal is closed.
24123
+ *
24124
+ * Retrieves the pending signal from StrategyCoreService and stores
24125
+ * the close event in the appropriate ReportStorage.
24126
+ *
24127
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24128
+ * @param isBacktest - Whether this is a backtest or live trading event
24129
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24130
+ * @param closeId - Optional identifier for the close reason
24131
+ */
24132
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
24133
+ this.loggerService.log("strategyMarkdownService closePending", {
24134
+ symbol,
24135
+ isBacktest,
24136
+ closeId,
24137
+ });
24138
+ if (!this.subscribe.hasValue()) {
24139
+ return;
24140
+ }
24141
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24142
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24143
+ exchangeName: context.exchangeName,
24144
+ strategyName: context.strategyName,
24145
+ frameName: context.frameName,
24146
+ });
24147
+ if (!pendingRow) {
24148
+ return;
24149
+ }
24150
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24151
+ storage.addEvent({
24152
+ timestamp: Date.now(),
24153
+ symbol,
24154
+ strategyName: context.strategyName,
24155
+ exchangeName: context.exchangeName,
24156
+ frameName: context.frameName,
24157
+ signalId: pendingRow.id,
24158
+ action: "close-pending",
24159
+ closeId,
24160
+ createdAt,
24161
+ backtest: isBacktest,
24162
+ });
24163
+ };
24164
+ /**
24165
+ * Records a partial-profit event when a portion of the position is closed at profit.
24166
+ *
24167
+ * Stores the percentage closed and current price when partial profit-taking occurs.
24168
+ *
24169
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24170
+ * @param percentToClose - Percentage of position to close (0-100)
24171
+ * @param currentPrice - Current market price at time of partial close
24172
+ * @param isBacktest - Whether this is a backtest or live trading event
24173
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24174
+ */
24175
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24176
+ this.loggerService.log("strategyMarkdownService partialProfit", {
24177
+ symbol,
24178
+ percentToClose,
24179
+ currentPrice,
24180
+ isBacktest,
24181
+ });
24182
+ if (!this.subscribe.hasValue()) {
24183
+ return;
24184
+ }
24185
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24186
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24187
+ exchangeName: context.exchangeName,
24188
+ strategyName: context.strategyName,
24189
+ frameName: context.frameName,
24190
+ });
24191
+ if (!pendingRow) {
24192
+ return;
24193
+ }
24194
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24195
+ storage.addEvent({
24196
+ timestamp: Date.now(),
24197
+ symbol,
24198
+ strategyName: context.strategyName,
24199
+ exchangeName: context.exchangeName,
24200
+ frameName: context.frameName,
24201
+ signalId: pendingRow.id,
24202
+ action: "partial-profit",
24203
+ percentToClose,
24204
+ currentPrice,
24205
+ createdAt,
24206
+ backtest: isBacktest,
24207
+ });
24208
+ };
24209
+ /**
24210
+ * Records a partial-loss event when a portion of the position is closed at loss.
24211
+ *
24212
+ * Stores the percentage closed and current price when partial loss-cutting occurs.
24213
+ *
24214
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24215
+ * @param percentToClose - Percentage of position to close (0-100)
24216
+ * @param currentPrice - Current market price at time of partial close
24217
+ * @param isBacktest - Whether this is a backtest or live trading event
24218
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24219
+ */
24220
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24221
+ this.loggerService.log("strategyMarkdownService partialLoss", {
24222
+ symbol,
24223
+ percentToClose,
24224
+ currentPrice,
24225
+ isBacktest,
24226
+ });
24227
+ if (!this.subscribe.hasValue()) {
24228
+ return;
24229
+ }
24230
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24231
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24232
+ exchangeName: context.exchangeName,
24233
+ strategyName: context.strategyName,
24234
+ frameName: context.frameName,
24235
+ });
24236
+ if (!pendingRow) {
24237
+ return;
24238
+ }
24239
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24240
+ storage.addEvent({
24241
+ timestamp: Date.now(),
24242
+ symbol,
24243
+ strategyName: context.strategyName,
24244
+ exchangeName: context.exchangeName,
24245
+ frameName: context.frameName,
24246
+ signalId: pendingRow.id,
24247
+ action: "partial-loss",
24248
+ percentToClose,
24249
+ currentPrice,
24250
+ createdAt,
24251
+ backtest: isBacktest,
24252
+ });
24253
+ };
24254
+ /**
24255
+ * Records a trailing-stop event when the stop-loss is adjusted.
24256
+ *
24257
+ * Stores the percentage shift and current price when trailing stop moves.
24258
+ *
24259
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24260
+ * @param percentShift - Percentage the stop-loss was shifted
24261
+ * @param currentPrice - Current market price at time of adjustment
24262
+ * @param isBacktest - Whether this is a backtest or live trading event
24263
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24264
+ */
24265
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24266
+ this.loggerService.log("strategyMarkdownService trailingStop", {
24267
+ symbol,
24268
+ percentShift,
24269
+ currentPrice,
24270
+ isBacktest,
24271
+ });
24272
+ if (!this.subscribe.hasValue()) {
24273
+ return;
24274
+ }
24275
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24276
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24277
+ exchangeName: context.exchangeName,
24278
+ strategyName: context.strategyName,
24279
+ frameName: context.frameName,
24280
+ });
24281
+ if (!pendingRow) {
24282
+ return;
24283
+ }
24284
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24285
+ storage.addEvent({
24286
+ timestamp: Date.now(),
24287
+ symbol,
24288
+ strategyName: context.strategyName,
24289
+ exchangeName: context.exchangeName,
24290
+ frameName: context.frameName,
24291
+ signalId: pendingRow.id,
24292
+ action: "trailing-stop",
24293
+ percentShift,
24294
+ currentPrice,
24295
+ createdAt,
24296
+ backtest: isBacktest,
24297
+ });
24298
+ };
24299
+ /**
24300
+ * Records a trailing-take event when the take-profit is adjusted.
24301
+ *
24302
+ * Stores the percentage shift and current price when trailing take-profit moves.
24303
+ *
24304
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24305
+ * @param percentShift - Percentage the take-profit was shifted
24306
+ * @param currentPrice - Current market price at time of adjustment
24307
+ * @param isBacktest - Whether this is a backtest or live trading event
24308
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24309
+ */
24310
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24311
+ this.loggerService.log("strategyMarkdownService trailingTake", {
24312
+ symbol,
24313
+ percentShift,
24314
+ currentPrice,
24315
+ isBacktest,
24316
+ });
24317
+ if (!this.subscribe.hasValue()) {
24318
+ return;
24319
+ }
24320
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24321
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24322
+ exchangeName: context.exchangeName,
24323
+ strategyName: context.strategyName,
24324
+ frameName: context.frameName,
24325
+ });
24326
+ if (!pendingRow) {
24327
+ return;
24328
+ }
24329
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24330
+ storage.addEvent({
24331
+ timestamp: Date.now(),
24332
+ symbol,
24333
+ strategyName: context.strategyName,
24334
+ exchangeName: context.exchangeName,
24335
+ frameName: context.frameName,
24336
+ signalId: pendingRow.id,
24337
+ action: "trailing-take",
24338
+ percentShift,
24339
+ currentPrice,
24340
+ createdAt,
24341
+ backtest: isBacktest,
24342
+ });
24343
+ };
24344
+ /**
24345
+ * Records a breakeven event when the stop-loss is moved to entry price.
24346
+ *
24347
+ * Stores the current price when breakeven protection is activated.
24348
+ *
24349
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24350
+ * @param currentPrice - Current market price at time of breakeven activation
24351
+ * @param isBacktest - Whether this is a backtest or live trading event
24352
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24353
+ */
24354
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
24355
+ this.loggerService.log("strategyMarkdownService breakeven", {
24356
+ symbol,
24357
+ currentPrice,
24358
+ isBacktest,
24359
+ });
24360
+ if (!this.subscribe.hasValue()) {
24361
+ return;
24362
+ }
24363
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24364
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24365
+ exchangeName: context.exchangeName,
24366
+ strategyName: context.strategyName,
24367
+ frameName: context.frameName,
24368
+ });
24369
+ if (!pendingRow) {
24370
+ return;
24371
+ }
24372
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24373
+ storage.addEvent({
24374
+ timestamp: Date.now(),
24375
+ symbol,
24376
+ strategyName: context.strategyName,
24377
+ exchangeName: context.exchangeName,
24378
+ frameName: context.frameName,
24379
+ signalId: pendingRow.id,
24380
+ action: "breakeven",
24381
+ currentPrice,
24382
+ createdAt,
24383
+ backtest: isBacktest,
24384
+ });
24385
+ };
24386
+ /**
24387
+ * Retrieves aggregated statistics from accumulated strategy events.
24388
+ *
24389
+ * Returns counts for each action type and the full event list from the
24390
+ * ReportStorage for the specified symbol-strategy pair.
24391
+ *
24392
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24393
+ * @param strategyName - Name of the trading strategy
24394
+ * @param exchangeName - Name of the exchange
24395
+ * @param frameName - Timeframe name for backtest identification
24396
+ * @param backtest - Whether to get backtest or live data
24397
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
24398
+ * @throws Error if service not initialized (subscribe() not called)
24399
+ */
24400
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
24401
+ this.loggerService.log("strategyMarkdownService getData", {
24402
+ symbol,
24403
+ strategyName,
24404
+ exchangeName,
24405
+ frameName,
24406
+ backtest,
24407
+ });
24408
+ if (!this.subscribe.hasValue()) {
24409
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
24410
+ }
24411
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24412
+ return storage.getData();
24413
+ };
24414
+ /**
24415
+ * Generates a markdown report from accumulated strategy events.
24416
+ *
24417
+ * Creates a formatted markdown document containing:
24418
+ * - Header with symbol and strategy name
24419
+ * - Table of all events with configurable columns
24420
+ * - Summary statistics with counts by action type
24421
+ *
24422
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24423
+ * @param strategyName - Name of the trading strategy
24424
+ * @param exchangeName - Name of the exchange
24425
+ * @param frameName - Timeframe name for backtest identification
24426
+ * @param backtest - Whether to get backtest or live data
24427
+ * @param columns - Column configuration for the event table
24428
+ * @returns Promise resolving to formatted markdown string
24429
+ * @throws Error if service not initialized (subscribe() not called)
24430
+ */
24431
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
24432
+ this.loggerService.log("strategyMarkdownService getReport", {
24433
+ symbol,
24434
+ strategyName,
24435
+ exchangeName,
24436
+ frameName,
24437
+ backtest,
24438
+ });
24439
+ if (!this.subscribe.hasValue()) {
24440
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
24441
+ }
24442
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24443
+ return storage.getReport(symbol, strategyName, columns);
24444
+ };
24445
+ /**
24446
+ * Generates and saves a markdown report to disk.
24447
+ *
24448
+ * Creates the output directory if it doesn't exist and writes
24449
+ * the report with a timestamped filename via Markdown.writeData().
24450
+ *
24451
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
24452
+ *
24453
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24454
+ * @param strategyName - Name of the trading strategy
24455
+ * @param exchangeName - Name of the exchange
24456
+ * @param frameName - Timeframe name for backtest identification
24457
+ * @param backtest - Whether to dump backtest or live data
24458
+ * @param path - Output directory path (default: "./dump/strategy")
24459
+ * @param columns - Column configuration for the event table
24460
+ * @returns Promise that resolves when file is written
24461
+ * @throws Error if service not initialized (subscribe() not called)
24462
+ */
24463
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
24464
+ this.loggerService.log("strategyMarkdownService dump", {
24465
+ symbol,
24466
+ strategyName,
24467
+ exchangeName,
24468
+ frameName,
24469
+ backtest,
24470
+ path,
24471
+ });
24472
+ if (!this.subscribe.hasValue()) {
24473
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
24474
+ }
24475
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24476
+ await storage.dump(symbol, strategyName, path, columns);
24477
+ };
24478
+ /**
24479
+ * Clears accumulated events from storage.
24480
+ *
24481
+ * Can clear either a specific symbol-strategy pair or all stored data.
24482
+ *
24483
+ * @param payload - Optional filter to clear specific storage. If omitted, clears all.
24484
+ * @param payload.symbol - Trading pair symbol
24485
+ * @param payload.strategyName - Strategy name
24486
+ * @param payload.exchangeName - Exchange name
24487
+ * @param payload.frameName - Frame name
24488
+ * @param payload.backtest - Backtest mode flag
24489
+ */
24490
+ this.clear = async (payload) => {
24491
+ this.loggerService.log("strategyMarkdownService clear", { payload });
24492
+ if (payload) {
24493
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24494
+ this.getStorage.clear(key);
24495
+ }
24496
+ else {
24497
+ this.getStorage.clear();
24498
+ }
24499
+ };
24500
+ /**
24501
+ * Initializes the service for event collection.
24502
+ *
24503
+ * Must be called before any events can be collected or reports generated.
24504
+ * Uses singleshot pattern to ensure only one subscription exists at a time.
24505
+ *
24506
+ * @returns Cleanup function that clears the subscription and all accumulated data
24507
+ */
24508
+ this.subscribe = functoolsKit.singleshot(() => {
24509
+ this.loggerService.log("strategyMarkdownService subscribe");
24510
+ const unCancelSchedule = strategyCommitSubject
24511
+ .filter(({ action }) => action === "cancel-scheduled")
24512
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
24513
+ exchangeName: event.exchangeName,
24514
+ frameName: event.frameName,
24515
+ strategyName: event.strategyName,
24516
+ }, event.cancelId));
24517
+ const unClosePending = strategyCommitSubject
24518
+ .filter(({ action }) => action === "close-pending")
24519
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
24520
+ exchangeName: event.exchangeName,
24521
+ frameName: event.frameName,
24522
+ strategyName: event.strategyName,
24523
+ }, event.closeId));
24524
+ const unPartialProfit = strategyCommitSubject
24525
+ .filter(({ action }) => action === "partial-profit")
24526
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24527
+ exchangeName: event.exchangeName,
24528
+ frameName: event.frameName,
24529
+ strategyName: event.strategyName,
24530
+ }));
24531
+ const unPartialLoss = strategyCommitSubject
24532
+ .filter(({ action }) => action === "partial-loss")
24533
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24534
+ exchangeName: event.exchangeName,
24535
+ frameName: event.frameName,
24536
+ strategyName: event.strategyName,
24537
+ }));
24538
+ const unTrailingStop = strategyCommitSubject
24539
+ .filter(({ action }) => action === "trailing-stop")
24540
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24541
+ exchangeName: event.exchangeName,
24542
+ frameName: event.frameName,
24543
+ strategyName: event.strategyName,
24544
+ }));
24545
+ const unTrailingTake = strategyCommitSubject
24546
+ .filter(({ action }) => action === "trailing-take")
24547
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24548
+ exchangeName: event.exchangeName,
24549
+ frameName: event.frameName,
24550
+ strategyName: event.strategyName,
24551
+ }));
24552
+ const unBreakeven = strategyCommitSubject
24553
+ .filter(({ action }) => action === "breakeven")
24554
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24555
+ exchangeName: event.exchangeName,
24556
+ frameName: event.frameName,
24557
+ strategyName: event.strategyName,
24558
+ }));
24559
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24560
+ return () => {
24561
+ disposeFn();
24562
+ this.subscribe.clear();
24563
+ this.clear();
24564
+ };
24565
+ });
24566
+ /**
24567
+ * Stops event collection and clears all accumulated data.
24568
+ *
24569
+ * Invokes the cleanup function returned by subscribe(), which clears
24570
+ * both the subscription and all ReportStorage instances.
24571
+ * Safe to call multiple times - only acts if subscription exists.
24572
+ */
24573
+ this.unsubscribe = async () => {
24574
+ this.loggerService.log("strategyMarkdownService unsubscribe");
24575
+ if (this.subscribe.hasValue()) {
24576
+ const lastSubscription = this.subscribe();
24577
+ lastSubscription();
24578
+ }
24579
+ };
24580
+ }
24581
+ }
24582
+
24583
+ {
24584
+ provide(TYPES.loggerService, () => new LoggerService());
24585
+ }
24586
+ {
24587
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
24588
+ provide(TYPES.methodContextService, () => new MethodContextService());
24589
+ }
24590
+ {
24591
+ provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
24592
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
24593
+ provide(TYPES.frameConnectionService, () => new FrameConnectionService());
24594
+ provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
24595
+ provide(TYPES.riskConnectionService, () => new RiskConnectionService());
24596
+ provide(TYPES.actionConnectionService, () => new ActionConnectionService());
24597
+ provide(TYPES.partialConnectionService, () => new PartialConnectionService());
24598
+ provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23101
24599
  }
23102
24600
  {
23103
24601
  provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
@@ -23145,6 +24643,7 @@ class RiskReportService {
23145
24643
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
23146
24644
  provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
23147
24645
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
24646
+ provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
23148
24647
  }
23149
24648
  {
23150
24649
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -23156,6 +24655,7 @@ class RiskReportService {
23156
24655
  provide(TYPES.partialReportService, () => new PartialReportService());
23157
24656
  provide(TYPES.breakevenReportService, () => new BreakevenReportService());
23158
24657
  provide(TYPES.riskReportService, () => new RiskReportService());
24658
+ provide(TYPES.strategyReportService, () => new StrategyReportService());
23159
24659
  }
23160
24660
  {
23161
24661
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -23232,6 +24732,7 @@ const markdownServices = {
23232
24732
  partialMarkdownService: inject(TYPES.partialMarkdownService),
23233
24733
  breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
23234
24734
  riskMarkdownService: inject(TYPES.riskMarkdownService),
24735
+ strategyMarkdownService: inject(TYPES.strategyMarkdownService),
23235
24736
  };
23236
24737
  const reportServices = {
23237
24738
  backtestReportService: inject(TYPES.backtestReportService),
@@ -23243,6 +24744,7 @@ const reportServices = {
23243
24744
  partialReportService: inject(TYPES.partialReportService),
23244
24745
  breakevenReportService: inject(TYPES.breakevenReportService),
23245
24746
  riskReportService: inject(TYPES.riskReportService),
24747
+ strategyReportService: inject(TYPES.strategyReportService),
23246
24748
  };
23247
24749
  const validationServices = {
23248
24750
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -25321,6 +26823,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
25321
26823
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
25322
26824
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
25323
26825
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
26826
+ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
26827
+ const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
25324
26828
  /**
25325
26829
  * Subscribes to all signal events with queued async processing.
25326
26830
  *
@@ -26350,6 +27854,78 @@ function listenActivePingOnce(filterFn, fn) {
26350
27854
  bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
26351
27855
  return activePingSubject.filter(filterFn).once(fn);
26352
27856
  }
27857
+ /**
27858
+ * Subscribes to strategy management events with queued async processing.
27859
+ *
27860
+ * Emits when strategy management actions are executed:
27861
+ * - cancel-scheduled: Scheduled signal cancelled
27862
+ * - close-pending: Pending signal closed
27863
+ * - partial-profit: Partial close at profit level
27864
+ * - partial-loss: Partial close at loss level
27865
+ * - trailing-stop: Stop-loss adjusted
27866
+ * - trailing-take: Take-profit adjusted
27867
+ * - breakeven: Stop-loss moved to entry price
27868
+ *
27869
+ * Events are processed sequentially in order received, even if callback is async.
27870
+ * Uses queued wrapper to prevent concurrent execution of the callback.
27871
+ *
27872
+ * @param fn - Callback function to handle strategy commit events
27873
+ * @returns Unsubscribe function to stop listening
27874
+ *
27875
+ * @example
27876
+ * ```typescript
27877
+ * import { listenStrategyCommit } from "./function/event";
27878
+ *
27879
+ * const unsubscribe = listenStrategyCommit((event) => {
27880
+ * console.log(`[${event.action}] ${event.symbol}`);
27881
+ * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
27882
+ * if (event.action === "partial-profit") {
27883
+ * console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
27884
+ * }
27885
+ * });
27886
+ *
27887
+ * // Later: stop listening
27888
+ * unsubscribe();
27889
+ * ```
27890
+ */
27891
+ function listenStrategyCommit(fn) {
27892
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
27893
+ return strategyCommitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
27894
+ }
27895
+ /**
27896
+ * Subscribes to filtered strategy management events with one-time execution.
27897
+ *
27898
+ * Listens for events matching the filter predicate, then executes callback once
27899
+ * and automatically unsubscribes. Useful for waiting for specific strategy actions.
27900
+ *
27901
+ * @param filterFn - Predicate to filter which events trigger the callback
27902
+ * @param fn - Callback function to handle the filtered event (called only once)
27903
+ * @returns Unsubscribe function to cancel the listener before it fires
27904
+ *
27905
+ * @example
27906
+ * ```typescript
27907
+ * import { listenStrategyCommitOnce } from "./function/event";
27908
+ *
27909
+ * // Wait for first trailing stop adjustment
27910
+ * listenStrategyCommitOnce(
27911
+ * (event) => event.action === "trailing-stop",
27912
+ * (event) => console.log("Trailing stop adjusted:", event.symbol)
27913
+ * );
27914
+ *
27915
+ * // Wait for breakeven on BTCUSDT
27916
+ * const cancel = listenStrategyCommitOnce(
27917
+ * (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
27918
+ * (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
27919
+ * );
27920
+ *
27921
+ * // Cancel if needed before event fires
27922
+ * cancel();
27923
+ * ```
27924
+ */
27925
+ function listenStrategyCommitOnce(filterFn, fn) {
27926
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
27927
+ return strategyCommitSubject.filter(filterFn).once(fn);
27928
+ }
26353
27929
 
26354
27930
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
26355
27931
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -30795,6 +32371,7 @@ class NotificationInstance {
30795
32371
  priceTakeProfit: data.signal.priceTakeProfit,
30796
32372
  priceStopLoss: data.signal.priceStopLoss,
30797
32373
  note: data.signal.note,
32374
+ createdAt: data.createdAt,
30798
32375
  });
30799
32376
  }
30800
32377
  else if (data.action === "closed") {
@@ -30816,6 +32393,7 @@ class NotificationInstance {
30816
32393
  closeReason: data.closeReason,
30817
32394
  duration: durationMin,
30818
32395
  note: data.signal.note,
32396
+ createdAt: data.createdAt,
30819
32397
  });
30820
32398
  }
30821
32399
  else if (data.action === "scheduled") {
@@ -30832,6 +32410,7 @@ class NotificationInstance {
30832
32410
  priceOpen: data.signal.priceOpen,
30833
32411
  scheduledAt: data.signal.scheduledAt,
30834
32412
  currentPrice: data.currentPrice,
32413
+ createdAt: data.createdAt,
30835
32414
  });
30836
32415
  }
30837
32416
  else if (data.action === "cancelled") {
@@ -30850,6 +32429,7 @@ class NotificationInstance {
30850
32429
  cancelReason: data.reason,
30851
32430
  cancelId: data.cancelId,
30852
32431
  duration: durationMin,
32432
+ createdAt: data.createdAt,
30853
32433
  });
30854
32434
  }
30855
32435
  };
@@ -30858,7 +32438,7 @@ class NotificationInstance {
30858
32438
  */
30859
32439
  this._handlePartialProfit = async (data) => {
30860
32440
  this._addNotification({
30861
- type: "partial.profit",
32441
+ type: "partial_profit.available",
30862
32442
  id: CREATE_KEY_FN(),
30863
32443
  timestamp: data.timestamp,
30864
32444
  backtest: data.backtest,
@@ -30877,7 +32457,7 @@ class NotificationInstance {
30877
32457
  */
30878
32458
  this._handlePartialLoss = async (data) => {
30879
32459
  this._addNotification({
30880
- type: "partial.loss",
32460
+ type: "partial_loss.available",
30881
32461
  id: CREATE_KEY_FN(),
30882
32462
  timestamp: data.timestamp,
30883
32463
  backtest: data.backtest,
@@ -30892,50 +32472,109 @@ class NotificationInstance {
30892
32472
  });
30893
32473
  };
30894
32474
  /**
30895
- * Processes risk rejection events.
32475
+ * Processes breakeven events.
30896
32476
  */
30897
- this._handleRisk = async (data) => {
32477
+ this._handleBreakeven = async (data) => {
30898
32478
  this._addNotification({
30899
- type: "risk.rejection",
32479
+ type: "breakeven.available",
30900
32480
  id: CREATE_KEY_FN(),
30901
32481
  timestamp: data.timestamp,
30902
32482
  backtest: data.backtest,
30903
32483
  symbol: data.symbol,
30904
32484
  strategyName: data.strategyName,
30905
32485
  exchangeName: data.exchangeName,
30906
- rejectionNote: data.rejectionNote,
30907
- rejectionId: data.rejectionId,
30908
- activePositionCount: data.activePositionCount,
32486
+ signalId: data.data.id,
30909
32487
  currentPrice: data.currentPrice,
30910
- pendingSignal: data.pendingSignal,
32488
+ priceOpen: data.data.priceOpen,
32489
+ position: data.data.position,
30911
32490
  });
30912
32491
  };
30913
32492
  /**
30914
- * Processes done events (live/backtest).
32493
+ * Processes strategy commit events.
30915
32494
  */
30916
- this._handleDoneLive = async (data) => {
30917
- this._addNotification({
30918
- type: "live.done",
30919
- id: CREATE_KEY_FN(),
30920
- timestamp: Date.now(),
30921
- backtest: false,
30922
- symbol: data.symbol,
30923
- strategyName: data.strategyName,
30924
- exchangeName: data.exchangeName,
30925
- });
32495
+ this._handleStrategyCommit = async (data) => {
32496
+ if (data.action === "partial-profit") {
32497
+ this._addNotification({
32498
+ type: "partial_profit.commit",
32499
+ id: CREATE_KEY_FN(),
32500
+ timestamp: Date.now(),
32501
+ backtest: data.backtest,
32502
+ symbol: data.symbol,
32503
+ strategyName: data.strategyName,
32504
+ exchangeName: data.exchangeName,
32505
+ percentToClose: data.percentToClose,
32506
+ currentPrice: data.currentPrice,
32507
+ });
32508
+ }
32509
+ else if (data.action === "partial-loss") {
32510
+ this._addNotification({
32511
+ type: "partial_loss.commit",
32512
+ id: CREATE_KEY_FN(),
32513
+ timestamp: Date.now(),
32514
+ backtest: data.backtest,
32515
+ symbol: data.symbol,
32516
+ strategyName: data.strategyName,
32517
+ exchangeName: data.exchangeName,
32518
+ percentToClose: data.percentToClose,
32519
+ currentPrice: data.currentPrice,
32520
+ });
32521
+ }
32522
+ else if (data.action === "breakeven") {
32523
+ this._addNotification({
32524
+ type: "breakeven.commit",
32525
+ id: CREATE_KEY_FN(),
32526
+ timestamp: Date.now(),
32527
+ backtest: data.backtest,
32528
+ symbol: data.symbol,
32529
+ strategyName: data.strategyName,
32530
+ exchangeName: data.exchangeName,
32531
+ currentPrice: data.currentPrice,
32532
+ });
32533
+ }
32534
+ else if (data.action === "trailing-stop") {
32535
+ this._addNotification({
32536
+ type: "trailing_stop.commit",
32537
+ id: CREATE_KEY_FN(),
32538
+ timestamp: Date.now(),
32539
+ backtest: data.backtest,
32540
+ symbol: data.symbol,
32541
+ strategyName: data.strategyName,
32542
+ exchangeName: data.exchangeName,
32543
+ percentShift: data.percentShift,
32544
+ currentPrice: data.currentPrice,
32545
+ });
32546
+ }
32547
+ else if (data.action === "trailing-take") {
32548
+ this._addNotification({
32549
+ type: "trailing_take.commit",
32550
+ id: CREATE_KEY_FN(),
32551
+ timestamp: Date.now(),
32552
+ backtest: data.backtest,
32553
+ symbol: data.symbol,
32554
+ strategyName: data.strategyName,
32555
+ exchangeName: data.exchangeName,
32556
+ percentShift: data.percentShift,
32557
+ currentPrice: data.currentPrice,
32558
+ });
32559
+ }
30926
32560
  };
30927
32561
  /**
30928
- * Processes done events (backtest).
32562
+ * Processes risk rejection events.
30929
32563
  */
30930
- this._handleDoneBacktest = async (data) => {
32564
+ this._handleRisk = async (data) => {
30931
32565
  this._addNotification({
30932
- type: "backtest.done",
32566
+ type: "risk.rejection",
30933
32567
  id: CREATE_KEY_FN(),
30934
- timestamp: Date.now(),
30935
- backtest: true,
32568
+ timestamp: data.timestamp,
32569
+ backtest: data.backtest,
30936
32570
  symbol: data.symbol,
30937
32571
  strategyName: data.strategyName,
30938
32572
  exchangeName: data.exchangeName,
32573
+ rejectionNote: data.rejectionNote,
32574
+ rejectionId: data.rejectionId,
32575
+ activePositionCount: data.activePositionCount,
32576
+ currentPrice: data.currentPrice,
32577
+ pendingSignal: data.pendingSignal,
30939
32578
  });
30940
32579
  };
30941
32580
  /**
@@ -30977,23 +32616,6 @@ class NotificationInstance {
30977
32616
  backtest: false,
30978
32617
  });
30979
32618
  };
30980
- /**
30981
- * Processes progress events.
30982
- */
30983
- this._handleProgressBacktest = async (data) => {
30984
- this._addNotification({
30985
- type: "progress.backtest",
30986
- id: CREATE_KEY_FN(),
30987
- timestamp: Date.now(),
30988
- backtest: true,
30989
- exchangeName: data.exchangeName,
30990
- strategyName: data.strategyName,
30991
- symbol: data.symbol,
30992
- totalFrames: data.totalFrames,
30993
- processedFrames: data.processedFrames,
30994
- progress: data.progress,
30995
- });
30996
- };
30997
32619
  /**
30998
32620
  * Subscribes to all notification emitters and returns an unsubscribe function.
30999
32621
  * Protected against multiple subscriptions using singleshot.
@@ -31012,14 +32634,13 @@ class NotificationInstance {
31012
32634
  const unSignal = signalEmitter.subscribe(this._handleSignal);
31013
32635
  const unProfit = partialProfitSubject.subscribe(this._handlePartialProfit);
31014
32636
  const unLoss = partialLossSubject.subscribe(this._handlePartialLoss);
32637
+ const unBreakeven = breakevenSubject.subscribe(this._handleBreakeven);
32638
+ const unStrategyCommit = strategyCommitSubject.subscribe(this._handleStrategyCommit);
31015
32639
  const unRisk = riskSubject.subscribe(this._handleRisk);
31016
- const unDoneLine = doneLiveSubject.subscribe(this._handleDoneLive);
31017
- const unDoneBacktest = doneBacktestSubject.subscribe(this._handleDoneBacktest);
31018
32640
  const unError = errorEmitter.subscribe(this._handleError);
31019
32641
  const unExit = exitEmitter.subscribe(this._handleCriticalError);
31020
32642
  const unValidation = validationSubject.subscribe(this._handleValidationError);
31021
- const unProgressBacktest = progressBacktestEmitter.subscribe(this._handleProgressBacktest);
31022
- const disposeFn = functoolsKit.compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unRisk(), () => unDoneLine(), () => unDoneBacktest(), () => unError(), () => unExit(), () => unValidation(), () => unProgressBacktest());
32643
+ const disposeFn = functoolsKit.compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unBreakeven(), () => unStrategyCommit(), () => unRisk(), () => unError(), () => unExit(), () => unValidation());
31023
32644
  return () => {
31024
32645
  disposeFn();
31025
32646
  this.enable.clear();
@@ -31409,6 +33030,201 @@ class BreakevenUtils {
31409
33030
  */
31410
33031
  const Breakeven = new BreakevenUtils();
31411
33032
 
33033
+ const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
33034
+ const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
33035
+ const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
33036
+ /**
33037
+ * Utility class for accessing strategy management reports and statistics.
33038
+ *
33039
+ * Provides static-like methods (via singleton instance) to retrieve data
33040
+ * accumulated by StrategyMarkdownService from strategy management events.
33041
+ *
33042
+ * Features:
33043
+ * - Statistical data extraction (event counts by action type)
33044
+ * - Markdown report generation with event tables
33045
+ * - File export to disk
33046
+ *
33047
+ * Data source:
33048
+ * - StrategyMarkdownService receives events via direct method calls
33049
+ * - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
33050
+ * - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
33051
+ * trailing-stop, trailing-take, breakeven
33052
+ *
33053
+ * @example
33054
+ * ```typescript
33055
+ * import { Strategy } from "./classes/Strategy";
33056
+ *
33057
+ * // Get statistical data for BTCUSDT:my-strategy
33058
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33059
+ * console.log(`Total events: ${stats.totalEvents}`);
33060
+ * console.log(`Partial profit events: ${stats.partialProfitCount}`);
33061
+ *
33062
+ * // Generate markdown report
33063
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33064
+ * console.log(markdown); // Formatted table with all events
33065
+ *
33066
+ * // Export report to file
33067
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
33068
+ * ```
33069
+ */
33070
+ class StrategyUtils {
33071
+ constructor() {
33072
+ /**
33073
+ * Retrieves statistical data from accumulated strategy events.
33074
+ *
33075
+ * Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
33076
+ * Returns aggregated metrics calculated from all strategy events.
33077
+ *
33078
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33079
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33080
+ * @param backtest - Whether to get backtest data (default: false)
33081
+ * @returns Promise resolving to StrategyStatisticsModel object with counts and event list
33082
+ *
33083
+ * @example
33084
+ * ```typescript
33085
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33086
+ *
33087
+ * console.log(`Total events: ${stats.totalEvents}`);
33088
+ * console.log(`Partial profit: ${stats.partialProfitCount}`);
33089
+ * console.log(`Trailing stop: ${stats.trailingStopCount}`);
33090
+ *
33091
+ * // Iterate through all events
33092
+ * for (const event of stats.eventList) {
33093
+ * console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
33094
+ * }
33095
+ * ```
33096
+ */
33097
+ this.getData = async (symbol, context, backtest = false) => {
33098
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
33099
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
33100
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
33101
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
33102
+ {
33103
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33104
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
33105
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
33106
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
33107
+ }
33108
+ return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
33109
+ };
33110
+ /**
33111
+ * Generates markdown report with all strategy events for a symbol-strategy pair.
33112
+ *
33113
+ * Creates formatted table containing:
33114
+ * - Symbol
33115
+ * - Strategy
33116
+ * - Signal ID
33117
+ * - Action (cancel-scheduled, close-pending, partial-profit, etc.)
33118
+ * - Price
33119
+ * - Percent values (% To Close, % Shift)
33120
+ * - Cancel/Close IDs
33121
+ * - Timestamp (ISO 8601)
33122
+ * - Mode (Backtest/Live)
33123
+ *
33124
+ * Also includes summary statistics at the end with counts by action type.
33125
+ *
33126
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33127
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33128
+ * @param backtest - Whether to get backtest data (default: false)
33129
+ * @param columns - Optional columns configuration for the report
33130
+ * @returns Promise resolving to markdown formatted report string
33131
+ *
33132
+ * @example
33133
+ * ```typescript
33134
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33135
+ * console.log(markdown);
33136
+ *
33137
+ * // Output:
33138
+ * // # Strategy Report: BTCUSDT:my-strategy
33139
+ * //
33140
+ * // | Symbol | Strategy | Signal ID | Action | Price | ... |
33141
+ * // | --- | --- | --- | --- | --- | ... |
33142
+ * // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
33143
+ * //
33144
+ * // **Total events:** 5
33145
+ * // - Cancel scheduled: 0
33146
+ * // - Close pending: 1
33147
+ * // - Partial profit: 2
33148
+ * // ...
33149
+ * ```
33150
+ */
33151
+ this.getReport = async (symbol, context, backtest = false, columns) => {
33152
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
33153
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
33154
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
33155
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
33156
+ {
33157
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33158
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
33159
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
33160
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
33161
+ }
33162
+ return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
33163
+ };
33164
+ /**
33165
+ * Generates and saves markdown report to file.
33166
+ *
33167
+ * Creates directory if it doesn't exist.
33168
+ * Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
33169
+ *
33170
+ * Delegates to StrategyMarkdownService.dump() which:
33171
+ * 1. Generates markdown report via getReport()
33172
+ * 2. Creates output directory (recursive mkdir)
33173
+ * 3. Writes file with UTF-8 encoding
33174
+ * 4. Logs success/failure to console
33175
+ *
33176
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33177
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33178
+ * @param backtest - Whether to dump backtest data (default: false)
33179
+ * @param path - Output directory path (default: "./dump/strategy")
33180
+ * @param columns - Optional columns configuration for the report
33181
+ * @returns Promise that resolves when file is written
33182
+ *
33183
+ * @example
33184
+ * ```typescript
33185
+ * // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
33186
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
33187
+ *
33188
+ * // Save to custom path
33189
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
33190
+ *
33191
+ * // After multiple symbols backtested, export all reports
33192
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
33193
+ * await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
33194
+ * }
33195
+ * ```
33196
+ */
33197
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
33198
+ bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
33199
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
33200
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
33201
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
33202
+ {
33203
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33204
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
33205
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
33206
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
33207
+ }
33208
+ await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
33209
+ };
33210
+ }
33211
+ }
33212
+ /**
33213
+ * Global singleton instance of StrategyUtils.
33214
+ * Provides static-like access to strategy management reporting methods.
33215
+ *
33216
+ * @example
33217
+ * ```typescript
33218
+ * import { Strategy } from "backtest-kit";
33219
+ *
33220
+ * // Usage same as StrategyUtils methods
33221
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33222
+ * const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33223
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33224
+ * ```
33225
+ */
33226
+ const Strategy = new StrategyUtils();
33227
+
31412
33228
  /**
31413
33229
  * Rounds a price to the appropriate precision based on the tick size.
31414
33230
  *
@@ -31569,6 +33385,7 @@ exports.Report = Report;
31569
33385
  exports.ReportBase = ReportBase;
31570
33386
  exports.Risk = Risk;
31571
33387
  exports.Schedule = Schedule;
33388
+ exports.Strategy = Strategy;
31572
33389
  exports.Walker = Walker;
31573
33390
  exports.addActionSchema = addActionSchema;
31574
33391
  exports.addExchangeSchema = addExchangeSchema;
@@ -31644,6 +33461,8 @@ exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
31644
33461
  exports.listenSignalLive = listenSignalLive;
31645
33462
  exports.listenSignalLiveOnce = listenSignalLiveOnce;
31646
33463
  exports.listenSignalOnce = listenSignalOnce;
33464
+ exports.listenStrategyCommit = listenStrategyCommit;
33465
+ exports.listenStrategyCommitOnce = listenStrategyCommitOnce;
31647
33466
  exports.listenValidation = listenValidation;
31648
33467
  exports.listenWalker = listenWalker;
31649
33468
  exports.listenWalkerComplete = listenWalkerComplete;