backtest-kit 2.2.1 → 2.2.3

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 +1891 -132
  3. package/build/index.mjs +1890 -134
  4. package/package.json +2 -1
  5. package/types.d.ts +1709 -865
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
  }
@@ -6533,7 +6557,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
6533
6557
  * @param backtest - Whether running in backtest mode
6534
6558
  * @returns Unique string key for memoization
6535
6559
  */
6536
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
6560
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
6537
6561
  const parts = [symbol, strategyName, exchangeName];
6538
6562
  if (frameName)
6539
6563
  parts.push(frameName);
@@ -6698,7 +6722,7 @@ class StrategyConnectionService {
6698
6722
  * @param backtest - Whether running in backtest mode
6699
6723
  * @returns Configured ClientStrategy instance
6700
6724
  */
6701
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6725
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6702
6726
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
6703
6727
  return new ClientStrategy({
6704
6728
  symbol,
@@ -6953,7 +6977,7 @@ class StrategyConnectionService {
6953
6977
  }
6954
6978
  return;
6955
6979
  }
6956
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6980
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6957
6981
  if (!this.getStrategy.has(key)) {
6958
6982
  return;
6959
6983
  }
@@ -7922,7 +7946,7 @@ class ClientRisk {
7922
7946
  * @param backtest - Whether running in backtest mode
7923
7947
  * @returns Unique string key for memoization
7924
7948
  */
7925
- const CREATE_KEY_FN$j = (riskName, exchangeName, frameName, backtest) => {
7949
+ const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
7926
7950
  const parts = [riskName, exchangeName];
7927
7951
  if (frameName)
7928
7952
  parts.push(frameName);
@@ -8021,7 +8045,7 @@ class RiskConnectionService {
8021
8045
  * @param backtest - True if backtest mode, false if live mode
8022
8046
  * @returns Configured ClientRisk instance
8023
8047
  */
8024
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8048
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8025
8049
  const schema = this.riskSchemaService.get(riskName);
8026
8050
  return new ClientRisk({
8027
8051
  ...schema,
@@ -8089,7 +8113,7 @@ class RiskConnectionService {
8089
8113
  payload,
8090
8114
  });
8091
8115
  if (payload) {
8092
- const key = CREATE_KEY_FN$j(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8116
+ const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8093
8117
  this.getRisk.clear(key);
8094
8118
  }
8095
8119
  else {
@@ -9512,7 +9536,7 @@ class ClientAction {
9512
9536
  * @param backtest - Whether running in backtest mode
9513
9537
  * @returns Unique string key for memoization
9514
9538
  */
9515
- const CREATE_KEY_FN$i = (actionName, strategyName, exchangeName, frameName, backtest) => {
9539
+ const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
9516
9540
  const parts = [actionName, strategyName, exchangeName];
9517
9541
  if (frameName)
9518
9542
  parts.push(frameName);
@@ -9563,7 +9587,7 @@ class ActionConnectionService {
9563
9587
  * @param backtest - True if backtest mode, false if live mode
9564
9588
  * @returns Configured ClientAction instance
9565
9589
  */
9566
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9590
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9567
9591
  const schema = this.actionSchemaService.get(actionName);
9568
9592
  return new ClientAction({
9569
9593
  ...schema,
@@ -9756,7 +9780,7 @@ class ActionConnectionService {
9756
9780
  await Promise.all(actions.map(async (action) => await action.dispose()));
9757
9781
  return;
9758
9782
  }
9759
- const key = CREATE_KEY_FN$i(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9783
+ const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9760
9784
  if (!this.getAction.has(key)) {
9761
9785
  return;
9762
9786
  }
@@ -9774,7 +9798,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
9774
9798
  * @param exchangeName - Exchange name
9775
9799
  * @returns Unique string key for memoization
9776
9800
  */
9777
- const CREATE_KEY_FN$h = (exchangeName) => {
9801
+ const CREATE_KEY_FN$i = (exchangeName) => {
9778
9802
  return exchangeName;
9779
9803
  };
9780
9804
  /**
@@ -9798,7 +9822,7 @@ class ExchangeCoreService {
9798
9822
  * @param exchangeName - Name of the exchange to validate
9799
9823
  * @returns Promise that resolves when validation is complete
9800
9824
  */
9801
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$h(exchangeName), async (exchangeName) => {
9825
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
9802
9826
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
9803
9827
  exchangeName,
9804
9828
  });
@@ -10022,12 +10046,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
10022
10046
  * @param context - Execution context with strategyName, exchangeName, frameName
10023
10047
  * @returns Unique string key for memoization
10024
10048
  */
10025
- const CREATE_KEY_FN$g = (context) => {
10049
+ const CREATE_KEY_FN$h = (context) => {
10026
10050
  const parts = [context.strategyName, context.exchangeName];
10027
10051
  if (context.frameName)
10028
10052
  parts.push(context.frameName);
10029
10053
  return parts.join(":");
10030
10054
  };
10055
+ /**
10056
+ * Broadcasts StrategyCommitContract event to strategyCommitSubject.
10057
+ *
10058
+ * @param event - The signal commit event to broadcast
10059
+ */
10060
+ const CALL_STRATEGY_COMMIT_FN = functoolsKit.trycatch(async (event) => {
10061
+ await strategyCommitSubject.next(event);
10062
+ }, {
10063
+ fallback: (error) => {
10064
+ const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
10065
+ const payload = {
10066
+ error: functoolsKit.errorData(error),
10067
+ message: functoolsKit.getErrorMessage(error),
10068
+ };
10069
+ bt.loggerService.warn(message, payload);
10070
+ console.warn(message, payload);
10071
+ errorEmitter.next(error);
10072
+ },
10073
+ defaultValue: null,
10074
+ });
10031
10075
  /**
10032
10076
  * Global service for strategy operations with execution context injection.
10033
10077
  *
@@ -10054,7 +10098,7 @@ class StrategyCoreService {
10054
10098
  * @param context - Execution context with strategyName, exchangeName, frameName
10055
10099
  * @returns Promise that resolves when validation is complete
10056
10100
  */
10057
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10101
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
10058
10102
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
10059
10103
  context,
10060
10104
  });
@@ -10260,7 +10304,19 @@ class StrategyCoreService {
10260
10304
  cancelId,
10261
10305
  });
10262
10306
  await this.validate(context);
10263
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10307
+ const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10308
+ {
10309
+ await CALL_STRATEGY_COMMIT_FN({
10310
+ action: "cancel-scheduled",
10311
+ symbol,
10312
+ strategyName: context.strategyName,
10313
+ exchangeName: context.exchangeName,
10314
+ frameName: context.frameName,
10315
+ backtest,
10316
+ cancelId,
10317
+ });
10318
+ }
10319
+ return result;
10264
10320
  };
10265
10321
  /**
10266
10322
  * Closes the pending signal without stopping the strategy.
@@ -10287,7 +10343,19 @@ class StrategyCoreService {
10287
10343
  closeId,
10288
10344
  });
10289
10345
  await this.validate(context);
10290
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10346
+ const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10347
+ {
10348
+ await CALL_STRATEGY_COMMIT_FN({
10349
+ action: "close-pending",
10350
+ symbol,
10351
+ strategyName: context.strategyName,
10352
+ exchangeName: context.exchangeName,
10353
+ frameName: context.frameName,
10354
+ backtest,
10355
+ closeId,
10356
+ });
10357
+ }
10358
+ return result;
10291
10359
  };
10292
10360
  /**
10293
10361
  * Disposes the ClientStrategy instance for the given context.
@@ -10368,7 +10436,20 @@ class StrategyCoreService {
10368
10436
  backtest,
10369
10437
  });
10370
10438
  await this.validate(context);
10371
- return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10439
+ const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10440
+ if (result) {
10441
+ await CALL_STRATEGY_COMMIT_FN({
10442
+ action: "partial-profit",
10443
+ symbol,
10444
+ strategyName: context.strategyName,
10445
+ exchangeName: context.exchangeName,
10446
+ frameName: context.frameName,
10447
+ backtest,
10448
+ percentToClose,
10449
+ currentPrice,
10450
+ });
10451
+ }
10452
+ return result;
10372
10453
  };
10373
10454
  /**
10374
10455
  * Executes partial close at loss level (moving toward SL).
@@ -10409,7 +10490,20 @@ class StrategyCoreService {
10409
10490
  backtest,
10410
10491
  });
10411
10492
  await this.validate(context);
10412
- return await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10493
+ const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10494
+ if (result) {
10495
+ await CALL_STRATEGY_COMMIT_FN({
10496
+ action: "partial-loss",
10497
+ symbol,
10498
+ strategyName: context.strategyName,
10499
+ exchangeName: context.exchangeName,
10500
+ frameName: context.frameName,
10501
+ backtest,
10502
+ percentToClose,
10503
+ currentPrice,
10504
+ });
10505
+ }
10506
+ return result;
10413
10507
  };
10414
10508
  /**
10415
10509
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -10448,7 +10542,20 @@ class StrategyCoreService {
10448
10542
  backtest,
10449
10543
  });
10450
10544
  await this.validate(context);
10451
- return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10545
+ const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10546
+ if (result) {
10547
+ await CALL_STRATEGY_COMMIT_FN({
10548
+ action: "trailing-stop",
10549
+ symbol,
10550
+ strategyName: context.strategyName,
10551
+ exchangeName: context.exchangeName,
10552
+ frameName: context.frameName,
10553
+ backtest,
10554
+ percentShift,
10555
+ currentPrice,
10556
+ });
10557
+ }
10558
+ return result;
10452
10559
  };
10453
10560
  /**
10454
10561
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -10483,7 +10590,20 @@ class StrategyCoreService {
10483
10590
  backtest,
10484
10591
  });
10485
10592
  await this.validate(context);
10486
- return await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10593
+ const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10594
+ if (result) {
10595
+ await CALL_STRATEGY_COMMIT_FN({
10596
+ action: "trailing-take",
10597
+ symbol,
10598
+ strategyName: context.strategyName,
10599
+ exchangeName: context.exchangeName,
10600
+ frameName: context.frameName,
10601
+ backtest,
10602
+ percentShift,
10603
+ currentPrice,
10604
+ });
10605
+ }
10606
+ return result;
10487
10607
  };
10488
10608
  /**
10489
10609
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -10513,7 +10633,19 @@ class StrategyCoreService {
10513
10633
  backtest,
10514
10634
  });
10515
10635
  await this.validate(context);
10516
- return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10636
+ const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10637
+ if (result) {
10638
+ await CALL_STRATEGY_COMMIT_FN({
10639
+ action: "breakeven",
10640
+ symbol,
10641
+ strategyName: context.strategyName,
10642
+ exchangeName: context.exchangeName,
10643
+ frameName: context.frameName,
10644
+ backtest,
10645
+ currentPrice,
10646
+ });
10647
+ }
10648
+ return result;
10517
10649
  };
10518
10650
  }
10519
10651
  }
@@ -10587,7 +10719,7 @@ class SizingGlobalService {
10587
10719
  * @param context - Context with riskName, exchangeName, frameName
10588
10720
  * @returns Unique string key for memoization
10589
10721
  */
10590
- const CREATE_KEY_FN$f = (context) => {
10722
+ const CREATE_KEY_FN$g = (context) => {
10591
10723
  const parts = [context.riskName, context.exchangeName];
10592
10724
  if (context.frameName)
10593
10725
  parts.push(context.frameName);
@@ -10613,7 +10745,7 @@ class RiskGlobalService {
10613
10745
  * @param payload - Payload with riskName, exchangeName and frameName
10614
10746
  * @returns Promise that resolves when validation is complete
10615
10747
  */
10616
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10748
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10617
10749
  this.loggerService.log("riskGlobalService validate", {
10618
10750
  context,
10619
10751
  });
@@ -10691,7 +10823,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
10691
10823
  * @param context - Execution context with strategyName, exchangeName, frameName
10692
10824
  * @returns Unique string key for memoization
10693
10825
  */
10694
- const CREATE_KEY_FN$e = (context) => {
10826
+ const CREATE_KEY_FN$f = (context) => {
10695
10827
  const parts = [context.strategyName, context.exchangeName];
10696
10828
  if (context.frameName)
10697
10829
  parts.push(context.frameName);
@@ -10735,7 +10867,7 @@ class ActionCoreService {
10735
10867
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
10736
10868
  * @returns Promise that resolves when all validations complete
10737
10869
  */
10738
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), async (context) => {
10870
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10739
10871
  this.loggerService.log(METHOD_NAME_VALIDATE, {
10740
10872
  context,
10741
10873
  });
@@ -13985,6 +14117,116 @@ const schedule_columns = [
13985
14117
  },
13986
14118
  ];
13987
14119
 
14120
+ /**
14121
+ * Column configuration for strategy markdown reports.
14122
+ *
14123
+ * Defines the table structure for displaying strategy management events in trading reports.
14124
+ * Each column specifies how to format and display strategy actions like partial profit/loss,
14125
+ * trailing stop/take, breakeven, cancel scheduled, and close pending.
14126
+ *
14127
+ * Used by {@link StrategyMarkdownService} to generate markdown tables showing:
14128
+ * - Signal identification (symbol, strategy name, signal ID)
14129
+ * - Action information (action type, percent values, prices)
14130
+ * - Timing information (timestamp, mode: backtest or live)
14131
+ *
14132
+ * @remarks
14133
+ * This configuration tracks all strategy management events - when signals are
14134
+ * modified, cancelled, or closed during their lifecycle.
14135
+ *
14136
+ * @example
14137
+ * ```typescript
14138
+ * import { strategy_columns } from "./assets/strategy.columns";
14139
+ *
14140
+ * // Use with StrategyMarkdownService
14141
+ * const service = new StrategyMarkdownService();
14142
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
14143
+ *
14144
+ * // Or customize to show only key fields
14145
+ * const customColumns = strategy_columns.filter(col =>
14146
+ * ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
14147
+ * );
14148
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
14149
+ * ```
14150
+ *
14151
+ * @see {@link StrategyMarkdownService} for usage in report generation
14152
+ * @see {@link ColumnModel} for column interface definition
14153
+ * @see {@link StrategyEvent} for data structure
14154
+ */
14155
+ const strategy_columns = [
14156
+ {
14157
+ key: "symbol",
14158
+ label: "Symbol",
14159
+ format: (data) => data.symbol,
14160
+ isVisible: () => true,
14161
+ },
14162
+ {
14163
+ key: "strategyName",
14164
+ label: "Strategy",
14165
+ format: (data) => data.strategyName,
14166
+ isVisible: () => true,
14167
+ },
14168
+ {
14169
+ key: "signalId",
14170
+ label: "Signal ID",
14171
+ format: (data) => data.signalId,
14172
+ isVisible: () => true,
14173
+ },
14174
+ {
14175
+ key: "action",
14176
+ label: "Action",
14177
+ format: (data) => data.action,
14178
+ isVisible: () => true,
14179
+ },
14180
+ {
14181
+ key: "currentPrice",
14182
+ label: "Price",
14183
+ format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
14184
+ isVisible: () => true,
14185
+ },
14186
+ {
14187
+ key: "percentToClose",
14188
+ label: "% To Close",
14189
+ format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
14190
+ isVisible: () => true,
14191
+ },
14192
+ {
14193
+ key: "percentShift",
14194
+ label: "% Shift",
14195
+ format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
14196
+ isVisible: () => true,
14197
+ },
14198
+ {
14199
+ key: "cancelId",
14200
+ label: "Cancel ID",
14201
+ format: (data) => data.cancelId || "N/A",
14202
+ isVisible: () => true,
14203
+ },
14204
+ {
14205
+ key: "closeId",
14206
+ label: "Close ID",
14207
+ format: (data) => data.closeId || "N/A",
14208
+ isVisible: () => true,
14209
+ },
14210
+ {
14211
+ key: "createdAt",
14212
+ label: "Created At",
14213
+ format: (data) => data.createdAt || "N/A",
14214
+ isVisible: () => true,
14215
+ },
14216
+ {
14217
+ key: "timestamp",
14218
+ label: "Timestamp",
14219
+ format: (data) => new Date(data.timestamp).toISOString(),
14220
+ isVisible: () => true,
14221
+ },
14222
+ {
14223
+ key: "mode",
14224
+ label: "Mode",
14225
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
14226
+ isVisible: () => true,
14227
+ },
14228
+ ];
14229
+
13988
14230
  /**
13989
14231
  * Column configuration for walker strategy comparison table in markdown reports.
13990
14232
  *
@@ -14198,6 +14440,8 @@ const COLUMN_CONFIG = {
14198
14440
  risk_columns,
14199
14441
  /** Columns for scheduled report output */
14200
14442
  schedule_columns,
14443
+ /** Columns for strategy management events */
14444
+ strategy_columns,
14201
14445
  /** Walker: PnL summary columns */
14202
14446
  walker_pnl_columns,
14203
14447
  /** Walker: strategy-level summary columns */
@@ -14234,6 +14478,7 @@ const WILDCARD_TARGET$1 = {
14234
14478
  partial: true,
14235
14479
  performance: true,
14236
14480
  risk: true,
14481
+ strategy: true,
14237
14482
  schedule: true,
14238
14483
  walker: true,
14239
14484
  };
@@ -14463,7 +14708,7 @@ class MarkdownUtils {
14463
14708
  *
14464
14709
  * @returns Cleanup function that unsubscribes from all enabled services
14465
14710
  */
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) => {
14711
+ 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
14712
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
14468
14713
  backtest: bt$1,
14469
14714
  breakeven,
@@ -14472,6 +14717,7 @@ class MarkdownUtils {
14472
14717
  partial,
14473
14718
  performance,
14474
14719
  risk,
14720
+ strategy,
14475
14721
  schedule,
14476
14722
  walker,
14477
14723
  });
@@ -14497,6 +14743,9 @@ class MarkdownUtils {
14497
14743
  if (risk) {
14498
14744
  unList.push(bt.riskMarkdownService.subscribe());
14499
14745
  }
14746
+ if (strategy) {
14747
+ unList.push(bt.strategyMarkdownService.subscribe());
14748
+ }
14500
14749
  if (schedule) {
14501
14750
  unList.push(bt.scheduleMarkdownService.subscribe());
14502
14751
  }
@@ -14542,7 +14791,7 @@ class MarkdownUtils {
14542
14791
  * Markdown.disable();
14543
14792
  * ```
14544
14793
  */
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) => {
14794
+ 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
14795
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
14547
14796
  backtest: bt$1,
14548
14797
  breakeven,
@@ -14551,6 +14800,7 @@ class MarkdownUtils {
14551
14800
  partial,
14552
14801
  performance,
14553
14802
  risk,
14803
+ strategy,
14554
14804
  schedule,
14555
14805
  walker,
14556
14806
  });
@@ -14575,6 +14825,9 @@ class MarkdownUtils {
14575
14825
  if (risk) {
14576
14826
  bt.riskMarkdownService.unsubscribe();
14577
14827
  }
14828
+ if (strategy) {
14829
+ bt.strategyMarkdownService.unsubscribe();
14830
+ }
14578
14831
  if (schedule) {
14579
14832
  bt.scheduleMarkdownService.unsubscribe();
14580
14833
  }
@@ -14687,7 +14940,7 @@ const Markdown = new MarkdownAdapter();
14687
14940
  * @param backtest - Whether running in backtest mode
14688
14941
  * @returns Unique string key for memoization
14689
14942
  */
14690
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
14943
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
14691
14944
  const parts = [symbol, strategyName, exchangeName];
14692
14945
  if (frameName)
14693
14946
  parts.push(frameName);
@@ -14704,7 +14957,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
14704
14957
  * @param timestamp - Unix timestamp in milliseconds
14705
14958
  * @returns Filename string
14706
14959
  */
14707
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14960
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14708
14961
  const parts = [symbol, strategyName, exchangeName];
14709
14962
  if (frameName) {
14710
14963
  parts.push(frameName);
@@ -14733,12 +14986,12 @@ function isUnsafe$3(value) {
14733
14986
  return false;
14734
14987
  }
14735
14988
  /** Maximum number of signals to store in backtest reports */
14736
- const MAX_EVENTS$7 = 250;
14989
+ const MAX_EVENTS$8 = 250;
14737
14990
  /**
14738
14991
  * Storage class for accumulating closed signals per strategy.
14739
14992
  * Maintains a list of all closed signals and provides methods to generate reports.
14740
14993
  */
14741
- let ReportStorage$6 = class ReportStorage {
14994
+ let ReportStorage$7 = class ReportStorage {
14742
14995
  constructor(symbol, strategyName, exchangeName, frameName) {
14743
14996
  this.symbol = symbol;
14744
14997
  this.strategyName = strategyName;
@@ -14755,7 +15008,7 @@ let ReportStorage$6 = class ReportStorage {
14755
15008
  addSignal(data) {
14756
15009
  this._signalList.unshift(data);
14757
15010
  // Trim queue if exceeded MAX_EVENTS
14758
- if (this._signalList.length > MAX_EVENTS$7) {
15011
+ if (this._signalList.length > MAX_EVENTS$8) {
14759
15012
  this._signalList.pop();
14760
15013
  }
14761
15014
  }
@@ -14879,7 +15132,7 @@ let ReportStorage$6 = class ReportStorage {
14879
15132
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
14880
15133
  const markdown = await this.getReport(strategyName, columns);
14881
15134
  const timestamp = Date.now();
14882
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15135
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
14883
15136
  await Markdown.writeData("backtest", markdown, {
14884
15137
  path,
14885
15138
  file: filename,
@@ -14926,7 +15179,7 @@ class BacktestMarkdownService {
14926
15179
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
14927
15180
  * Each combination gets its own isolated storage instance.
14928
15181
  */
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));
15182
+ 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
15183
  /**
14931
15184
  * Processes tick events and accumulates closed signals.
14932
15185
  * Should be called from IStrategyCallbacks.onTick.
@@ -15083,7 +15336,7 @@ class BacktestMarkdownService {
15083
15336
  payload,
15084
15337
  });
15085
15338
  if (payload) {
15086
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15339
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15087
15340
  this.getStorage.clear(key);
15088
15341
  }
15089
15342
  else {
@@ -15145,7 +15398,7 @@ class BacktestMarkdownService {
15145
15398
  * @param backtest - Whether running in backtest mode
15146
15399
  * @returns Unique string key for memoization
15147
15400
  */
15148
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15401
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
15149
15402
  const parts = [symbol, strategyName, exchangeName];
15150
15403
  if (frameName)
15151
15404
  parts.push(frameName);
@@ -15162,7 +15415,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
15162
15415
  * @param timestamp - Unix timestamp in milliseconds
15163
15416
  * @returns Filename string
15164
15417
  */
15165
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15418
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15166
15419
  const parts = [symbol, strategyName, exchangeName];
15167
15420
  if (frameName) {
15168
15421
  parts.push(frameName);
@@ -15191,12 +15444,12 @@ function isUnsafe$2(value) {
15191
15444
  return false;
15192
15445
  }
15193
15446
  /** Maximum number of events to store in live trading reports */
15194
- const MAX_EVENTS$6 = 250;
15447
+ const MAX_EVENTS$7 = 250;
15195
15448
  /**
15196
15449
  * Storage class for accumulating all tick events per strategy.
15197
15450
  * Maintains a chronological list of all events (idle, opened, active, closed).
15198
15451
  */
15199
- let ReportStorage$5 = class ReportStorage {
15452
+ let ReportStorage$6 = class ReportStorage {
15200
15453
  constructor(symbol, strategyName, exchangeName, frameName) {
15201
15454
  this.symbol = symbol;
15202
15455
  this.strategyName = strategyName;
@@ -15228,7 +15481,7 @@ let ReportStorage$5 = class ReportStorage {
15228
15481
  }
15229
15482
  {
15230
15483
  this._eventList.unshift(newEvent);
15231
- if (this._eventList.length > MAX_EVENTS$6) {
15484
+ if (this._eventList.length > MAX_EVENTS$7) {
15232
15485
  this._eventList.pop();
15233
15486
  }
15234
15487
  }
@@ -15255,7 +15508,7 @@ let ReportStorage$5 = class ReportStorage {
15255
15508
  totalExecuted: data.signal.totalExecuted,
15256
15509
  });
15257
15510
  // Trim queue if exceeded MAX_EVENTS
15258
- if (this._eventList.length > MAX_EVENTS$6) {
15511
+ if (this._eventList.length > MAX_EVENTS$7) {
15259
15512
  this._eventList.pop();
15260
15513
  }
15261
15514
  }
@@ -15294,7 +15547,7 @@ let ReportStorage$5 = class ReportStorage {
15294
15547
  // If no previous active event found, add new event
15295
15548
  this._eventList.unshift(newEvent);
15296
15549
  // Trim queue if exceeded MAX_EVENTS
15297
- if (this._eventList.length > MAX_EVENTS$6) {
15550
+ if (this._eventList.length > MAX_EVENTS$7) {
15298
15551
  this._eventList.pop();
15299
15552
  }
15300
15553
  }
@@ -15326,7 +15579,7 @@ let ReportStorage$5 = class ReportStorage {
15326
15579
  };
15327
15580
  this._eventList.unshift(newEvent);
15328
15581
  // Trim queue if exceeded MAX_EVENTS
15329
- if (this._eventList.length > MAX_EVENTS$6) {
15582
+ if (this._eventList.length > MAX_EVENTS$7) {
15330
15583
  this._eventList.pop();
15331
15584
  }
15332
15585
  }
@@ -15352,7 +15605,7 @@ let ReportStorage$5 = class ReportStorage {
15352
15605
  totalExecuted: data.signal.totalExecuted,
15353
15606
  });
15354
15607
  // Trim queue if exceeded MAX_EVENTS
15355
- if (this._eventList.length > MAX_EVENTS$6) {
15608
+ if (this._eventList.length > MAX_EVENTS$7) {
15356
15609
  this._eventList.pop();
15357
15610
  }
15358
15611
  }
@@ -15391,7 +15644,7 @@ let ReportStorage$5 = class ReportStorage {
15391
15644
  // If no previous waiting event found, add new event
15392
15645
  this._eventList.unshift(newEvent);
15393
15646
  // Trim queue if exceeded MAX_EVENTS
15394
- if (this._eventList.length > MAX_EVENTS$6) {
15647
+ if (this._eventList.length > MAX_EVENTS$7) {
15395
15648
  this._eventList.pop();
15396
15649
  }
15397
15650
  }
@@ -15418,7 +15671,7 @@ let ReportStorage$5 = class ReportStorage {
15418
15671
  cancelReason: data.reason,
15419
15672
  });
15420
15673
  // Trim queue if exceeded MAX_EVENTS
15421
- if (this._eventList.length > MAX_EVENTS$6) {
15674
+ if (this._eventList.length > MAX_EVENTS$7) {
15422
15675
  this._eventList.pop();
15423
15676
  }
15424
15677
  }
@@ -15557,7 +15810,7 @@ let ReportStorage$5 = class ReportStorage {
15557
15810
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
15558
15811
  const markdown = await this.getReport(strategyName, columns);
15559
15812
  const timestamp = Date.now();
15560
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15813
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15561
15814
  await Markdown.writeData("live", markdown, {
15562
15815
  path,
15563
15816
  signalId: "",
@@ -15607,7 +15860,7 @@ class LiveMarkdownService {
15607
15860
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
15608
15861
  * Each combination gets its own isolated storage instance.
15609
15862
  */
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));
15863
+ 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
15864
  /**
15612
15865
  * Subscribes to live signal emitter to receive tick events.
15613
15866
  * Protected against multiple subscriptions.
@@ -15825,7 +16078,7 @@ class LiveMarkdownService {
15825
16078
  payload,
15826
16079
  });
15827
16080
  if (payload) {
15828
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16081
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15829
16082
  this.getStorage.clear(key);
15830
16083
  }
15831
16084
  else {
@@ -15845,7 +16098,7 @@ class LiveMarkdownService {
15845
16098
  * @param backtest - Whether running in backtest mode
15846
16099
  * @returns Unique string key for memoization
15847
16100
  */
15848
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16101
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15849
16102
  const parts = [symbol, strategyName, exchangeName];
15850
16103
  if (frameName)
15851
16104
  parts.push(frameName);
@@ -15862,7 +16115,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
15862
16115
  * @param timestamp - Unix timestamp in milliseconds
15863
16116
  * @returns Filename string
15864
16117
  */
15865
- const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16118
+ const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15866
16119
  const parts = [symbol, strategyName, exchangeName];
15867
16120
  if (frameName) {
15868
16121
  parts.push(frameName);
@@ -15873,12 +16126,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
15873
16126
  return `${parts.join("_")}-${timestamp}.md`;
15874
16127
  };
15875
16128
  /** Maximum number of events to store in schedule reports */
15876
- const MAX_EVENTS$5 = 250;
16129
+ const MAX_EVENTS$6 = 250;
15877
16130
  /**
15878
16131
  * Storage class for accumulating scheduled signal events per strategy.
15879
16132
  * Maintains a chronological list of scheduled and cancelled events.
15880
16133
  */
15881
- let ReportStorage$4 = class ReportStorage {
16134
+ let ReportStorage$5 = class ReportStorage {
15882
16135
  constructor(symbol, strategyName, exchangeName, frameName) {
15883
16136
  this.symbol = symbol;
15884
16137
  this.strategyName = strategyName;
@@ -15909,7 +16162,7 @@ let ReportStorage$4 = class ReportStorage {
15909
16162
  totalExecuted: data.signal.totalExecuted,
15910
16163
  });
15911
16164
  // Trim queue if exceeded MAX_EVENTS
15912
- if (this._eventList.length > MAX_EVENTS$5) {
16165
+ if (this._eventList.length > MAX_EVENTS$6) {
15913
16166
  this._eventList.pop();
15914
16167
  }
15915
16168
  }
@@ -15939,7 +16192,7 @@ let ReportStorage$4 = class ReportStorage {
15939
16192
  };
15940
16193
  this._eventList.unshift(newEvent);
15941
16194
  // Trim queue if exceeded MAX_EVENTS
15942
- if (this._eventList.length > MAX_EVENTS$5) {
16195
+ if (this._eventList.length > MAX_EVENTS$6) {
15943
16196
  this._eventList.pop();
15944
16197
  }
15945
16198
  }
@@ -15972,7 +16225,7 @@ let ReportStorage$4 = class ReportStorage {
15972
16225
  };
15973
16226
  this._eventList.unshift(newEvent);
15974
16227
  // Trim queue if exceeded MAX_EVENTS
15975
- if (this._eventList.length > MAX_EVENTS$5) {
16228
+ if (this._eventList.length > MAX_EVENTS$6) {
15976
16229
  this._eventList.pop();
15977
16230
  }
15978
16231
  }
@@ -16079,7 +16332,7 @@ let ReportStorage$4 = class ReportStorage {
16079
16332
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
16080
16333
  const markdown = await this.getReport(strategyName, columns);
16081
16334
  const timestamp = Date.now();
16082
- const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16335
+ const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16083
16336
  await Markdown.writeData("schedule", markdown, {
16084
16337
  path,
16085
16338
  file: filename,
@@ -16120,7 +16373,7 @@ class ScheduleMarkdownService {
16120
16373
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
16121
16374
  * Each combination gets its own isolated storage instance.
16122
16375
  */
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));
16376
+ 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
16377
  /**
16125
16378
  * Subscribes to signal emitter to receive scheduled signal events.
16126
16379
  * Protected against multiple subscriptions.
@@ -16323,7 +16576,7 @@ class ScheduleMarkdownService {
16323
16576
  payload,
16324
16577
  });
16325
16578
  if (payload) {
16326
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16579
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16327
16580
  this.getStorage.clear(key);
16328
16581
  }
16329
16582
  else {
@@ -16343,7 +16596,7 @@ class ScheduleMarkdownService {
16343
16596
  * @param backtest - Whether running in backtest mode
16344
16597
  * @returns Unique string key for memoization
16345
16598
  */
16346
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
16599
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16347
16600
  const parts = [symbol, strategyName, exchangeName];
16348
16601
  if (frameName)
16349
16602
  parts.push(frameName);
@@ -16360,7 +16613,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
16360
16613
  * @param timestamp - Unix timestamp in milliseconds
16361
16614
  * @returns Filename string
16362
16615
  */
16363
- const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16616
+ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16364
16617
  const parts = [symbol, strategyName, exchangeName];
16365
16618
  if (frameName) {
16366
16619
  parts.push(frameName);
@@ -16380,7 +16633,7 @@ function percentile(sortedArray, p) {
16380
16633
  return sortedArray[Math.max(0, index)];
16381
16634
  }
16382
16635
  /** Maximum number of performance events to store per strategy */
16383
- const MAX_EVENTS$4 = 10000;
16636
+ const MAX_EVENTS$5 = 10000;
16384
16637
  /**
16385
16638
  * Storage class for accumulating performance metrics per strategy.
16386
16639
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -16402,7 +16655,7 @@ class PerformanceStorage {
16402
16655
  addEvent(event) {
16403
16656
  this._events.unshift(event);
16404
16657
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
16405
- if (this._events.length > MAX_EVENTS$4) {
16658
+ if (this._events.length > MAX_EVENTS$5) {
16406
16659
  this._events.pop();
16407
16660
  }
16408
16661
  }
@@ -16544,7 +16797,7 @@ class PerformanceStorage {
16544
16797
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
16545
16798
  const markdown = await this.getReport(strategyName, columns);
16546
16799
  const timestamp = Date.now();
16547
- const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16800
+ const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16548
16801
  await Markdown.writeData("performance", markdown, {
16549
16802
  path,
16550
16803
  file: filename,
@@ -16591,7 +16844,7 @@ class PerformanceMarkdownService {
16591
16844
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
16592
16845
  * Each combination gets its own isolated storage instance.
16593
16846
  */
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));
16847
+ 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
16848
  /**
16596
16849
  * Subscribes to performance emitter to receive performance events.
16597
16850
  * Protected against multiple subscriptions.
@@ -16758,7 +17011,7 @@ class PerformanceMarkdownService {
16758
17011
  payload,
16759
17012
  });
16760
17013
  if (payload) {
16761
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
17014
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16762
17015
  this.getStorage.clear(key);
16763
17016
  }
16764
17017
  else {
@@ -16772,7 +17025,7 @@ class PerformanceMarkdownService {
16772
17025
  * Creates a filename for markdown report based on walker name.
16773
17026
  * Filename format: "walkerName-timestamp.md"
16774
17027
  */
16775
- const CREATE_FILE_NAME_FN$4 = (walkerName, timestamp) => {
17028
+ const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
16776
17029
  return `${walkerName}-${timestamp}.md`;
16777
17030
  };
16778
17031
  /**
@@ -16807,7 +17060,7 @@ function formatMetric(value) {
16807
17060
  * Storage class for accumulating walker results.
16808
17061
  * Maintains a list of all strategy results and provides methods to generate reports.
16809
17062
  */
16810
- let ReportStorage$3 = class ReportStorage {
17063
+ let ReportStorage$4 = class ReportStorage {
16811
17064
  constructor(walkerName) {
16812
17065
  this.walkerName = walkerName;
16813
17066
  /** Walker metadata (set from first addResult call) */
@@ -16995,7 +17248,7 @@ let ReportStorage$3 = class ReportStorage {
16995
17248
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
16996
17249
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
16997
17250
  const timestamp = Date.now();
16998
- const filename = CREATE_FILE_NAME_FN$4(this.walkerName, timestamp);
17251
+ const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
16999
17252
  await Markdown.writeData("walker", markdown, {
17000
17253
  path,
17001
17254
  file: filename,
@@ -17031,7 +17284,7 @@ class WalkerMarkdownService {
17031
17284
  * Memoized function to get or create ReportStorage for a walker.
17032
17285
  * Each walker gets its own isolated storage instance.
17033
17286
  */
17034
- this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$3(walkerName));
17287
+ this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
17035
17288
  /**
17036
17289
  * Subscribes to walker emitter to receive walker progress events.
17037
17290
  * Protected against multiple subscriptions.
@@ -17228,7 +17481,7 @@ class WalkerMarkdownService {
17228
17481
  * @param backtest - Whether running in backtest mode
17229
17482
  * @returns Unique string key for memoization
17230
17483
  */
17231
- const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17484
+ const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
17232
17485
  const parts = [exchangeName];
17233
17486
  if (frameName)
17234
17487
  parts.push(frameName);
@@ -17239,7 +17492,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17239
17492
  * Creates a filename for markdown report based on memoization key components.
17240
17493
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
17241
17494
  */
17242
- const CREATE_FILE_NAME_FN$3 = (strategyName, exchangeName, frameName, timestamp) => {
17495
+ const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
17243
17496
  const parts = [strategyName, exchangeName];
17244
17497
  if (frameName) {
17245
17498
  parts.push(frameName);
@@ -17272,7 +17525,7 @@ function isUnsafe(value) {
17272
17525
  return false;
17273
17526
  }
17274
17527
  /** Maximum number of signals to store per symbol in heatmap reports */
17275
- const MAX_EVENTS$3 = 250;
17528
+ const MAX_EVENTS$4 = 250;
17276
17529
  /**
17277
17530
  * Storage class for accumulating closed signals per strategy and generating heatmap.
17278
17531
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -17298,7 +17551,7 @@ class HeatmapStorage {
17298
17551
  const signals = this.symbolData.get(symbol);
17299
17552
  signals.unshift(data);
17300
17553
  // Trim queue if exceeded MAX_EVENTS per symbol
17301
- if (signals.length > MAX_EVENTS$3) {
17554
+ if (signals.length > MAX_EVENTS$4) {
17302
17555
  signals.pop();
17303
17556
  }
17304
17557
  }
@@ -17549,7 +17802,7 @@ class HeatmapStorage {
17549
17802
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
17550
17803
  const markdown = await this.getReport(strategyName, columns);
17551
17804
  const timestamp = Date.now();
17552
- const filename = CREATE_FILE_NAME_FN$3(strategyName, this.exchangeName, this.frameName, timestamp);
17805
+ const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
17553
17806
  await Markdown.writeData("heat", markdown, {
17554
17807
  path,
17555
17808
  file: filename,
@@ -17595,7 +17848,7 @@ class HeatMarkdownService {
17595
17848
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
17596
17849
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
17597
17850
  */
17598
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17851
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17599
17852
  /**
17600
17853
  * Subscribes to signal emitter to receive tick events.
17601
17854
  * Protected against multiple subscriptions.
@@ -17790,7 +18043,7 @@ class HeatMarkdownService {
17790
18043
  payload,
17791
18044
  });
17792
18045
  if (payload) {
17793
- const key = CREATE_KEY_FN$9(payload.exchangeName, payload.frameName, payload.backtest);
18046
+ const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
17794
18047
  this.getStorage.clear(key);
17795
18048
  }
17796
18049
  else {
@@ -18821,7 +19074,7 @@ class ClientPartial {
18821
19074
  * @param backtest - Whether running in backtest mode
18822
19075
  * @returns Unique string key for memoization
18823
19076
  */
18824
- const CREATE_KEY_FN$8 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
19077
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
18825
19078
  /**
18826
19079
  * Creates a callback function for emitting profit events to partialProfitSubject.
18827
19080
  *
@@ -18943,7 +19196,7 @@ class PartialConnectionService {
18943
19196
  * Key format: "signalId:backtest" or "signalId:live"
18944
19197
  * Value: ClientPartial instance with logger and event emitters
18945
19198
  */
18946
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$8(signalId, backtest), (signalId, backtest) => {
19199
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
18947
19200
  return new ClientPartial({
18948
19201
  signalId,
18949
19202
  logger: this.loggerService,
@@ -19033,7 +19286,7 @@ class PartialConnectionService {
19033
19286
  const partial = this.getPartial(data.id, backtest);
19034
19287
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
19035
19288
  await partial.clear(symbol, data, priceClose, backtest);
19036
- const key = CREATE_KEY_FN$8(data.id, backtest);
19289
+ const key = CREATE_KEY_FN$9(data.id, backtest);
19037
19290
  this.getPartial.clear(key);
19038
19291
  };
19039
19292
  }
@@ -19049,7 +19302,7 @@ class PartialConnectionService {
19049
19302
  * @param backtest - Whether running in backtest mode
19050
19303
  * @returns Unique string key for memoization
19051
19304
  */
19052
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19305
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19053
19306
  const parts = [symbol, strategyName, exchangeName];
19054
19307
  if (frameName)
19055
19308
  parts.push(frameName);
@@ -19060,7 +19313,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
19060
19313
  * Creates a filename for markdown report based on memoization key components.
19061
19314
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
19062
19315
  */
19063
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19316
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19064
19317
  const parts = [symbol, strategyName, exchangeName];
19065
19318
  if (frameName) {
19066
19319
  parts.push(frameName);
@@ -19071,12 +19324,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
19071
19324
  return `${parts.join("_")}-${timestamp}.md`;
19072
19325
  };
19073
19326
  /** Maximum number of events to store in partial reports */
19074
- const MAX_EVENTS$2 = 250;
19327
+ const MAX_EVENTS$3 = 250;
19075
19328
  /**
19076
19329
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
19077
19330
  * Maintains a chronological list of profit and loss level events.
19078
19331
  */
19079
- let ReportStorage$2 = class ReportStorage {
19332
+ let ReportStorage$3 = class ReportStorage {
19080
19333
  constructor(symbol, strategyName, exchangeName, frameName) {
19081
19334
  this.symbol = symbol;
19082
19335
  this.strategyName = strategyName;
@@ -19113,7 +19366,7 @@ let ReportStorage$2 = class ReportStorage {
19113
19366
  backtest,
19114
19367
  });
19115
19368
  // Trim queue if exceeded MAX_EVENTS
19116
- if (this._eventList.length > MAX_EVENTS$2) {
19369
+ if (this._eventList.length > MAX_EVENTS$3) {
19117
19370
  this._eventList.pop();
19118
19371
  }
19119
19372
  }
@@ -19145,7 +19398,7 @@ let ReportStorage$2 = class ReportStorage {
19145
19398
  backtest,
19146
19399
  });
19147
19400
  // Trim queue if exceeded MAX_EVENTS
19148
- if (this._eventList.length > MAX_EVENTS$2) {
19401
+ if (this._eventList.length > MAX_EVENTS$3) {
19149
19402
  this._eventList.pop();
19150
19403
  }
19151
19404
  }
@@ -19221,7 +19474,7 @@ let ReportStorage$2 = class ReportStorage {
19221
19474
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
19222
19475
  const markdown = await this.getReport(symbol, strategyName, columns);
19223
19476
  const timestamp = Date.now();
19224
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19477
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19225
19478
  await Markdown.writeData("partial", markdown, {
19226
19479
  path,
19227
19480
  file: filename,
@@ -19262,7 +19515,7 @@ class PartialMarkdownService {
19262
19515
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19263
19516
  * Each combination gets its own isolated storage instance.
19264
19517
  */
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));
19518
+ 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
19519
  /**
19267
19520
  * Subscribes to partial profit/loss signal emitters to receive events.
19268
19521
  * Protected against multiple subscriptions.
@@ -19472,7 +19725,7 @@ class PartialMarkdownService {
19472
19725
  payload,
19473
19726
  });
19474
19727
  if (payload) {
19475
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19728
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19476
19729
  this.getStorage.clear(key);
19477
19730
  }
19478
19731
  else {
@@ -19488,7 +19741,7 @@ class PartialMarkdownService {
19488
19741
  * @param context - Context with strategyName, exchangeName, frameName
19489
19742
  * @returns Unique string key for memoization
19490
19743
  */
19491
- const CREATE_KEY_FN$6 = (context) => {
19744
+ const CREATE_KEY_FN$7 = (context) => {
19492
19745
  const parts = [context.strategyName, context.exchangeName];
19493
19746
  if (context.frameName)
19494
19747
  parts.push(context.frameName);
@@ -19562,7 +19815,7 @@ class PartialGlobalService {
19562
19815
  * @param context - Context with strategyName, exchangeName and frameName
19563
19816
  * @param methodName - Name of the calling method for error tracking
19564
19817
  */
19565
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), (context, methodName) => {
19818
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
19566
19819
  this.loggerService.log("partialGlobalService validate", {
19567
19820
  context,
19568
19821
  methodName,
@@ -20017,7 +20270,7 @@ class ClientBreakeven {
20017
20270
  * @param backtest - Whether running in backtest mode
20018
20271
  * @returns Unique string key for memoization
20019
20272
  */
20020
- const CREATE_KEY_FN$5 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20273
+ const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20021
20274
  /**
20022
20275
  * Creates a callback function for emitting breakeven events to breakevenSubject.
20023
20276
  *
@@ -20103,7 +20356,7 @@ class BreakevenConnectionService {
20103
20356
  * Key format: "signalId:backtest" or "signalId:live"
20104
20357
  * Value: ClientBreakeven instance with logger and event emitter
20105
20358
  */
20106
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$5(signalId, backtest), (signalId, backtest) => {
20359
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
20107
20360
  return new ClientBreakeven({
20108
20361
  signalId,
20109
20362
  logger: this.loggerService,
@@ -20164,7 +20417,7 @@ class BreakevenConnectionService {
20164
20417
  const breakeven = this.getBreakeven(data.id, backtest);
20165
20418
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
20166
20419
  await breakeven.clear(symbol, data, priceClose, backtest);
20167
- const key = CREATE_KEY_FN$5(data.id, backtest);
20420
+ const key = CREATE_KEY_FN$6(data.id, backtest);
20168
20421
  this.getBreakeven.clear(key);
20169
20422
  };
20170
20423
  }
@@ -20180,7 +20433,7 @@ class BreakevenConnectionService {
20180
20433
  * @param backtest - Whether running in backtest mode
20181
20434
  * @returns Unique string key for memoization
20182
20435
  */
20183
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20436
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20184
20437
  const parts = [symbol, strategyName, exchangeName];
20185
20438
  if (frameName)
20186
20439
  parts.push(frameName);
@@ -20191,7 +20444,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
20191
20444
  * Creates a filename for markdown report based on memoization key components.
20192
20445
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20193
20446
  */
20194
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20447
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20195
20448
  const parts = [symbol, strategyName, exchangeName];
20196
20449
  if (frameName) {
20197
20450
  parts.push(frameName);
@@ -20202,12 +20455,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
20202
20455
  return `${parts.join("_")}-${timestamp}.md`;
20203
20456
  };
20204
20457
  /** Maximum number of events to store in breakeven reports */
20205
- const MAX_EVENTS$1 = 250;
20458
+ const MAX_EVENTS$2 = 250;
20206
20459
  /**
20207
20460
  * Storage class for accumulating breakeven events per symbol-strategy pair.
20208
20461
  * Maintains a chronological list of breakeven events.
20209
20462
  */
20210
- let ReportStorage$1 = class ReportStorage {
20463
+ let ReportStorage$2 = class ReportStorage {
20211
20464
  constructor(symbol, strategyName, exchangeName, frameName) {
20212
20465
  this.symbol = symbol;
20213
20466
  this.strategyName = strategyName;
@@ -20242,7 +20495,7 @@ let ReportStorage$1 = class ReportStorage {
20242
20495
  backtest,
20243
20496
  });
20244
20497
  // Trim queue if exceeded MAX_EVENTS
20245
- if (this._eventList.length > MAX_EVENTS$1) {
20498
+ if (this._eventList.length > MAX_EVENTS$2) {
20246
20499
  this._eventList.pop();
20247
20500
  }
20248
20501
  }
@@ -20310,7 +20563,7 @@ let ReportStorage$1 = class ReportStorage {
20310
20563
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
20311
20564
  const markdown = await this.getReport(symbol, strategyName, columns);
20312
20565
  const timestamp = Date.now();
20313
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20566
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20314
20567
  await Markdown.writeData("breakeven", markdown, {
20315
20568
  path,
20316
20569
  file: filename,
@@ -20351,7 +20604,7 @@ class BreakevenMarkdownService {
20351
20604
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20352
20605
  * Each combination gets its own isolated storage instance.
20353
20606
  */
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));
20607
+ 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
20608
  /**
20356
20609
  * Subscribes to breakeven signal emitter to receive events.
20357
20610
  * Protected against multiple subscriptions.
@@ -20540,7 +20793,7 @@ class BreakevenMarkdownService {
20540
20793
  payload,
20541
20794
  });
20542
20795
  if (payload) {
20543
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20796
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20544
20797
  this.getStorage.clear(key);
20545
20798
  }
20546
20799
  else {
@@ -20556,7 +20809,7 @@ class BreakevenMarkdownService {
20556
20809
  * @param context - Context with strategyName, exchangeName, frameName
20557
20810
  * @returns Unique string key for memoization
20558
20811
  */
20559
- const CREATE_KEY_FN$3 = (context) => {
20812
+ const CREATE_KEY_FN$4 = (context) => {
20560
20813
  const parts = [context.strategyName, context.exchangeName];
20561
20814
  if (context.frameName)
20562
20815
  parts.push(context.frameName);
@@ -20630,7 +20883,7 @@ class BreakevenGlobalService {
20630
20883
  * @param context - Context with strategyName, exchangeName and frameName
20631
20884
  * @param methodName - Name of the calling method for error tracking
20632
20885
  */
20633
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$3(context), (context, methodName) => {
20886
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
20634
20887
  this.loggerService.log("breakevenGlobalService validate", {
20635
20888
  context,
20636
20889
  methodName,
@@ -20843,7 +21096,7 @@ class ConfigValidationService {
20843
21096
  * @param backtest - Whether running in backtest mode
20844
21097
  * @returns Unique string key for memoization
20845
21098
  */
20846
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
21099
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20847
21100
  const parts = [symbol, strategyName, exchangeName];
20848
21101
  if (frameName)
20849
21102
  parts.push(frameName);
@@ -20854,7 +21107,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
20854
21107
  * Creates a filename for markdown report based on memoization key components.
20855
21108
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20856
21109
  */
20857
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
21110
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20858
21111
  const parts = [symbol, strategyName, exchangeName];
20859
21112
  if (frameName) {
20860
21113
  parts.push(frameName);
@@ -20865,12 +21118,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
20865
21118
  return `${parts.join("_")}-${timestamp}.md`;
20866
21119
  };
20867
21120
  /** Maximum number of events to store in risk reports */
20868
- const MAX_EVENTS = 250;
21121
+ const MAX_EVENTS$1 = 250;
20869
21122
  /**
20870
21123
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
20871
21124
  * Maintains a chronological list of rejected signals due to risk limits.
20872
21125
  */
20873
- class ReportStorage {
21126
+ let ReportStorage$1 = class ReportStorage {
20874
21127
  constructor(symbol, strategyName, exchangeName, frameName) {
20875
21128
  this.symbol = symbol;
20876
21129
  this.strategyName = strategyName;
@@ -20887,7 +21140,7 @@ class ReportStorage {
20887
21140
  addRejectionEvent(event) {
20888
21141
  this._eventList.unshift(event);
20889
21142
  // Trim queue if exceeded MAX_EVENTS
20890
- if (this._eventList.length > MAX_EVENTS) {
21143
+ if (this._eventList.length > MAX_EVENTS$1) {
20891
21144
  this._eventList.pop();
20892
21145
  }
20893
21146
  }
@@ -20971,7 +21224,7 @@ class ReportStorage {
20971
21224
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
20972
21225
  const markdown = await this.getReport(symbol, strategyName, columns);
20973
21226
  const timestamp = Date.now();
20974
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21227
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20975
21228
  await Markdown.writeData("risk", markdown, {
20976
21229
  path,
20977
21230
  file: filename,
@@ -20982,7 +21235,7 @@ class ReportStorage {
20982
21235
  frameName: this.frameName
20983
21236
  });
20984
21237
  }
20985
- }
21238
+ };
20986
21239
  /**
20987
21240
  * Service for generating and saving risk rejection markdown reports.
20988
21241
  *
@@ -21012,7 +21265,7 @@ class RiskMarkdownService {
21012
21265
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21013
21266
  * Each combination gets its own isolated storage instance.
21014
21267
  */
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));
21268
+ 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
21269
  /**
21017
21270
  * Subscribes to risk rejection emitter to receive rejection events.
21018
21271
  * Protected against multiple subscriptions.
@@ -21201,7 +21454,7 @@ class RiskMarkdownService {
21201
21454
  payload,
21202
21455
  });
21203
21456
  if (payload) {
21204
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21457
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21205
21458
  this.getStorage.clear(key);
21206
21459
  }
21207
21460
  else {
@@ -21485,6 +21738,7 @@ class ReportDummy {
21485
21738
  */
21486
21739
  const WILDCARD_TARGET = {
21487
21740
  backtest: true,
21741
+ strategy: true,
21488
21742
  breakeven: true,
21489
21743
  heat: true,
21490
21744
  live: true,
@@ -21530,7 +21784,7 @@ class ReportUtils {
21530
21784
  *
21531
21785
  * @returns Cleanup function that unsubscribes from all enabled services
21532
21786
  */
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) => {
21787
+ 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
21788
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
21535
21789
  backtest: bt$1,
21536
21790
  breakeven,
@@ -21541,6 +21795,7 @@ class ReportUtils {
21541
21795
  risk,
21542
21796
  schedule,
21543
21797
  walker,
21798
+ strategy,
21544
21799
  });
21545
21800
  const unList = [];
21546
21801
  if (bt$1) {
@@ -21570,6 +21825,9 @@ class ReportUtils {
21570
21825
  if (walker) {
21571
21826
  unList.push(bt.walkerReportService.subscribe());
21572
21827
  }
21828
+ if (strategy) {
21829
+ unList.push(bt.scheduleReportService.subscribe());
21830
+ }
21573
21831
  return functoolsKit.compose(...unList.map((un) => () => void un()));
21574
21832
  };
21575
21833
  /**
@@ -21608,7 +21866,7 @@ class ReportUtils {
21608
21866
  * Report.disable();
21609
21867
  * ```
21610
21868
  */
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) => {
21869
+ 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
21870
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
21613
21871
  backtest: bt$1,
21614
21872
  breakeven,
@@ -21619,6 +21877,7 @@ class ReportUtils {
21619
21877
  risk,
21620
21878
  schedule,
21621
21879
  walker,
21880
+ strategy,
21622
21881
  });
21623
21882
  if (bt$1) {
21624
21883
  bt.backtestReportService.unsubscribe();
@@ -21647,6 +21906,9 @@ class ReportUtils {
21647
21906
  if (walker) {
21648
21907
  bt.walkerReportService.unsubscribe();
21649
21908
  }
21909
+ if (strategy) {
21910
+ bt.strategyReportService.unsubscribe();
21911
+ }
21650
21912
  };
21651
21913
  }
21652
21914
  }
@@ -23082,22 +23344,1243 @@ class RiskReportService {
23082
23344
  }
23083
23345
  }
23084
23346
 
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());
23347
+ /**
23348
+ * Extracts execution context timestamp for strategy event logging.
23349
+ *
23350
+ * @param self - The StrategyReportService instance to extract context from
23351
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23352
+ * @internal
23353
+ */
23354
+ const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
23355
+ if (ExecutionContextService.hasContext()) {
23356
+ const { when } = self.executionContextService.context;
23357
+ return { when: when.toISOString() };
23358
+ }
23359
+ return {
23360
+ when: "",
23361
+ };
23362
+ };
23363
+ /**
23364
+ * Service for persisting strategy management events to JSON report files.
23365
+ *
23366
+ * Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
23367
+ * partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
23368
+ * the Report class. Each event is written as a separate JSON record.
23369
+ *
23370
+ * Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
23371
+ * this service writes each event immediately to disk for audit trail purposes.
23372
+ *
23373
+ * Lifecycle:
23374
+ * - Call subscribe() to enable event logging
23375
+ * - Events are written via Report.writeData() with "strategy" category
23376
+ * - Call unsubscribe() to disable event logging
23377
+ *
23378
+ * @example
23379
+ * ```typescript
23380
+ * // Service is typically used internally by strategy management classes
23381
+ * strategyReportService.subscribe();
23382
+ *
23383
+ * // Events are logged automatically when strategy actions occur
23384
+ * await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
23385
+ * strategyName: "my-strategy",
23386
+ * exchangeName: "binance",
23387
+ * frameName: "1h"
23388
+ * });
23389
+ *
23390
+ * strategyReportService.unsubscribe();
23391
+ * ```
23392
+ *
23393
+ * @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
23394
+ * @see Report for the underlying persistence mechanism
23395
+ */
23396
+ class StrategyReportService {
23397
+ constructor() {
23398
+ this.loggerService = inject(TYPES.loggerService);
23399
+ this.executionContextService = inject(TYPES.executionContextService);
23400
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
23401
+ /**
23402
+ * Logs a cancel-scheduled event when a scheduled signal is cancelled.
23403
+ *
23404
+ * Retrieves the scheduled signal from StrategyCoreService and writes
23405
+ * the cancellation event to the report file.
23406
+ *
23407
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23408
+ * @param isBacktest - Whether this is a backtest or live trading event
23409
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23410
+ * @param cancelId - Optional identifier for the cancellation reason
23411
+ */
23412
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
23413
+ this.loggerService.log("strategyReportService cancelScheduled", {
23414
+ symbol,
23415
+ isBacktest,
23416
+ cancelId,
23417
+ });
23418
+ if (!this.subscribe.hasValue()) {
23419
+ return;
23420
+ }
23421
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23422
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
23423
+ exchangeName: context.exchangeName,
23424
+ strategyName: context.strategyName,
23425
+ frameName: context.frameName,
23426
+ });
23427
+ if (!scheduledRow) {
23428
+ return;
23429
+ }
23430
+ await Report.writeData("strategy", {
23431
+ action: "cancel-scheduled",
23432
+ cancelId,
23433
+ symbol,
23434
+ createdAt,
23435
+ }, {
23436
+ signalId: scheduledRow.id,
23437
+ exchangeName: context.exchangeName,
23438
+ frameName: context.frameName,
23439
+ strategyName: context.strategyName,
23440
+ symbol,
23441
+ walkerName: "",
23442
+ });
23443
+ };
23444
+ /**
23445
+ * Logs a close-pending event when a pending signal is closed.
23446
+ *
23447
+ * Retrieves the pending signal from StrategyCoreService and writes
23448
+ * the close event to the report file.
23449
+ *
23450
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23451
+ * @param isBacktest - Whether this is a backtest or live trading event
23452
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23453
+ * @param closeId - Optional identifier for the close reason
23454
+ */
23455
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
23456
+ this.loggerService.log("strategyReportService closePending", {
23457
+ symbol,
23458
+ isBacktest,
23459
+ closeId,
23460
+ });
23461
+ if (!this.subscribe.hasValue()) {
23462
+ return;
23463
+ }
23464
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23465
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23466
+ exchangeName: context.exchangeName,
23467
+ strategyName: context.strategyName,
23468
+ frameName: context.frameName,
23469
+ });
23470
+ if (!pendingRow) {
23471
+ return;
23472
+ }
23473
+ await Report.writeData("strategy", {
23474
+ action: "close-pending",
23475
+ closeId,
23476
+ symbol,
23477
+ createdAt,
23478
+ }, {
23479
+ signalId: pendingRow.id,
23480
+ exchangeName: context.exchangeName,
23481
+ frameName: context.frameName,
23482
+ strategyName: context.strategyName,
23483
+ symbol,
23484
+ walkerName: "",
23485
+ });
23486
+ };
23487
+ /**
23488
+ * Logs a partial-profit event when a portion of the position is closed at profit.
23489
+ *
23490
+ * Records the percentage closed and current price when partial profit-taking occurs.
23491
+ *
23492
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23493
+ * @param percentToClose - Percentage of position to close (0-100)
23494
+ * @param currentPrice - Current market price at time of partial close
23495
+ * @param isBacktest - Whether this is a backtest or live trading event
23496
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23497
+ */
23498
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23499
+ this.loggerService.log("strategyReportService partialProfit", {
23500
+ symbol,
23501
+ percentToClose,
23502
+ currentPrice,
23503
+ isBacktest,
23504
+ });
23505
+ if (!this.subscribe.hasValue()) {
23506
+ return;
23507
+ }
23508
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23509
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23510
+ exchangeName: context.exchangeName,
23511
+ strategyName: context.strategyName,
23512
+ frameName: context.frameName,
23513
+ });
23514
+ if (!pendingRow) {
23515
+ return;
23516
+ }
23517
+ await Report.writeData("strategy", {
23518
+ action: "partial-profit",
23519
+ percentToClose,
23520
+ currentPrice,
23521
+ symbol,
23522
+ createdAt,
23523
+ }, {
23524
+ signalId: pendingRow.id,
23525
+ exchangeName: context.exchangeName,
23526
+ frameName: context.frameName,
23527
+ strategyName: context.strategyName,
23528
+ symbol,
23529
+ walkerName: "",
23530
+ });
23531
+ };
23532
+ /**
23533
+ * Logs a partial-loss event when a portion of the position is closed at loss.
23534
+ *
23535
+ * Records the percentage closed and current price when partial loss-cutting occurs.
23536
+ *
23537
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23538
+ * @param percentToClose - Percentage of position to close (0-100)
23539
+ * @param currentPrice - Current market price at time of partial close
23540
+ * @param isBacktest - Whether this is a backtest or live trading event
23541
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23542
+ */
23543
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23544
+ this.loggerService.log("strategyReportService partialLoss", {
23545
+ symbol,
23546
+ percentToClose,
23547
+ currentPrice,
23548
+ isBacktest,
23549
+ });
23550
+ if (!this.subscribe.hasValue()) {
23551
+ return;
23552
+ }
23553
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23554
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23555
+ exchangeName: context.exchangeName,
23556
+ strategyName: context.strategyName,
23557
+ frameName: context.frameName,
23558
+ });
23559
+ if (!pendingRow) {
23560
+ return;
23561
+ }
23562
+ await Report.writeData("strategy", {
23563
+ action: "partial-loss",
23564
+ percentToClose,
23565
+ currentPrice,
23566
+ symbol,
23567
+ createdAt,
23568
+ }, {
23569
+ signalId: pendingRow.id,
23570
+ exchangeName: context.exchangeName,
23571
+ frameName: context.frameName,
23572
+ strategyName: context.strategyName,
23573
+ symbol,
23574
+ walkerName: "",
23575
+ });
23576
+ };
23577
+ /**
23578
+ * Logs a trailing-stop event when the stop-loss is adjusted.
23579
+ *
23580
+ * Records the percentage shift and current price when trailing stop moves.
23581
+ *
23582
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23583
+ * @param percentShift - Percentage the stop-loss was shifted
23584
+ * @param currentPrice - Current market price at time of adjustment
23585
+ * @param isBacktest - Whether this is a backtest or live trading event
23586
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23587
+ */
23588
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23589
+ this.loggerService.log("strategyReportService trailingStop", {
23590
+ symbol,
23591
+ percentShift,
23592
+ currentPrice,
23593
+ isBacktest,
23594
+ });
23595
+ if (!this.subscribe.hasValue()) {
23596
+ return;
23597
+ }
23598
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23599
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23600
+ exchangeName: context.exchangeName,
23601
+ strategyName: context.strategyName,
23602
+ frameName: context.frameName,
23603
+ });
23604
+ if (!pendingRow) {
23605
+ return;
23606
+ }
23607
+ await Report.writeData("strategy", {
23608
+ action: "trailing-stop",
23609
+ percentShift,
23610
+ currentPrice,
23611
+ symbol,
23612
+ createdAt,
23613
+ }, {
23614
+ signalId: pendingRow.id,
23615
+ exchangeName: context.exchangeName,
23616
+ frameName: context.frameName,
23617
+ strategyName: context.strategyName,
23618
+ symbol,
23619
+ walkerName: "",
23620
+ });
23621
+ };
23622
+ /**
23623
+ * Logs a trailing-take event when the take-profit is adjusted.
23624
+ *
23625
+ * Records the percentage shift and current price when trailing take-profit moves.
23626
+ *
23627
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23628
+ * @param percentShift - Percentage the take-profit was shifted
23629
+ * @param currentPrice - Current market price at time of adjustment
23630
+ * @param isBacktest - Whether this is a backtest or live trading event
23631
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23632
+ */
23633
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23634
+ this.loggerService.log("strategyReportService trailingTake", {
23635
+ symbol,
23636
+ percentShift,
23637
+ currentPrice,
23638
+ isBacktest,
23639
+ });
23640
+ if (!this.subscribe.hasValue()) {
23641
+ return;
23642
+ }
23643
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23644
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23645
+ exchangeName: context.exchangeName,
23646
+ strategyName: context.strategyName,
23647
+ frameName: context.frameName,
23648
+ });
23649
+ if (!pendingRow) {
23650
+ return;
23651
+ }
23652
+ await Report.writeData("strategy", {
23653
+ action: "trailing-take",
23654
+ percentShift,
23655
+ currentPrice,
23656
+ symbol,
23657
+ createdAt,
23658
+ }, {
23659
+ signalId: pendingRow.id,
23660
+ exchangeName: context.exchangeName,
23661
+ frameName: context.frameName,
23662
+ strategyName: context.strategyName,
23663
+ symbol,
23664
+ walkerName: "",
23665
+ });
23666
+ };
23667
+ /**
23668
+ * Logs a breakeven event when the stop-loss is moved to entry price.
23669
+ *
23670
+ * Records the current price when breakeven protection is activated.
23671
+ *
23672
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23673
+ * @param currentPrice - Current market price at time of breakeven activation
23674
+ * @param isBacktest - Whether this is a backtest or live trading event
23675
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23676
+ */
23677
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
23678
+ this.loggerService.log("strategyReportService breakeven", {
23679
+ symbol,
23680
+ currentPrice,
23681
+ isBacktest,
23682
+ });
23683
+ if (!this.subscribe.hasValue()) {
23684
+ return;
23685
+ }
23686
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23687
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23688
+ exchangeName: context.exchangeName,
23689
+ strategyName: context.strategyName,
23690
+ frameName: context.frameName,
23691
+ });
23692
+ if (!pendingRow) {
23693
+ return;
23694
+ }
23695
+ await Report.writeData("strategy", {
23696
+ action: "breakeven",
23697
+ currentPrice,
23698
+ symbol,
23699
+ createdAt,
23700
+ }, {
23701
+ signalId: pendingRow.id,
23702
+ exchangeName: context.exchangeName,
23703
+ frameName: context.frameName,
23704
+ strategyName: context.strategyName,
23705
+ symbol,
23706
+ walkerName: "",
23707
+ });
23708
+ };
23709
+ /**
23710
+ * Initializes the service for event logging.
23711
+ *
23712
+ * Must be called before any events can be logged. Uses singleshot pattern
23713
+ * to ensure only one subscription exists at a time.
23714
+ *
23715
+ * @returns Cleanup function that clears the subscription when called
23716
+ */
23717
+ this.subscribe = functoolsKit.singleshot(() => {
23718
+ this.loggerService.log("strategyReportService subscribe");
23719
+ const unCancelSchedule = strategyCommitSubject
23720
+ .filter(({ action }) => action === "cancel-scheduled")
23721
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
23722
+ exchangeName: event.exchangeName,
23723
+ frameName: event.frameName,
23724
+ strategyName: event.strategyName,
23725
+ }, event.cancelId));
23726
+ const unClosePending = strategyCommitSubject
23727
+ .filter(({ action }) => action === "close-pending")
23728
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
23729
+ exchangeName: event.exchangeName,
23730
+ frameName: event.frameName,
23731
+ strategyName: event.strategyName,
23732
+ }, event.closeId));
23733
+ const unPartialProfit = strategyCommitSubject
23734
+ .filter(({ action }) => action === "partial-profit")
23735
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23736
+ exchangeName: event.exchangeName,
23737
+ frameName: event.frameName,
23738
+ strategyName: event.strategyName,
23739
+ }));
23740
+ const unPartialLoss = strategyCommitSubject
23741
+ .filter(({ action }) => action === "partial-loss")
23742
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23743
+ exchangeName: event.exchangeName,
23744
+ frameName: event.frameName,
23745
+ strategyName: event.strategyName,
23746
+ }));
23747
+ const unTrailingStop = strategyCommitSubject
23748
+ .filter(({ action }) => action === "trailing-stop")
23749
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23750
+ exchangeName: event.exchangeName,
23751
+ frameName: event.frameName,
23752
+ strategyName: event.strategyName,
23753
+ }));
23754
+ const unTrailingTake = strategyCommitSubject
23755
+ .filter(({ action }) => action === "trailing-take")
23756
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23757
+ exchangeName: event.exchangeName,
23758
+ frameName: event.frameName,
23759
+ strategyName: event.strategyName,
23760
+ }));
23761
+ const unBreakeven = strategyCommitSubject
23762
+ .filter(({ action }) => action === "breakeven")
23763
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
23764
+ exchangeName: event.exchangeName,
23765
+ frameName: event.frameName,
23766
+ strategyName: event.strategyName,
23767
+ }));
23768
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
23769
+ return () => {
23770
+ disposeFn();
23771
+ this.subscribe.clear();
23772
+ };
23773
+ });
23774
+ /**
23775
+ * Stops event logging and cleans up the subscription.
23776
+ *
23777
+ * Safe to call multiple times - only clears if subscription exists.
23778
+ */
23779
+ this.unsubscribe = async () => {
23780
+ this.loggerService.log("strategyReportService unsubscribe");
23781
+ if (this.subscribe.hasValue()) {
23782
+ const lastSubscription = this.subscribe();
23783
+ lastSubscription();
23784
+ }
23785
+ };
23786
+ }
23787
+ }
23788
+
23789
+ /**
23790
+ * Extracts execution context timestamp for strategy event logging.
23791
+ *
23792
+ * @param self - The StrategyMarkdownService instance to extract context from
23793
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23794
+ * @internal
23795
+ */
23796
+ const GET_EXECUTION_CONTEXT_FN = (self) => {
23797
+ if (ExecutionContextService.hasContext()) {
23798
+ const { when } = self.executionContextService.context;
23799
+ return { when: when.toISOString() };
23800
+ }
23801
+ return {
23802
+ when: "",
23803
+ };
23804
+ };
23805
+ /**
23806
+ * Creates a unique key for memoizing ReportStorage instances.
23807
+ *
23808
+ * Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
23809
+ *
23810
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23811
+ * @param strategyName - Name of the trading strategy
23812
+ * @param exchangeName - Name of the exchange
23813
+ * @param frameName - Timeframe name (optional, included if present)
23814
+ * @param backtest - Whether this is backtest or live mode
23815
+ * @returns Colon-separated key string for memoization
23816
+ * @internal
23817
+ */
23818
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23819
+ const parts = [symbol, strategyName, exchangeName];
23820
+ if (frameName)
23821
+ parts.push(frameName);
23822
+ parts.push(backtest ? "backtest" : "live");
23823
+ return parts.join(":");
23824
+ };
23825
+ /**
23826
+ * Creates a filename for markdown report output.
23827
+ *
23828
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
23829
+ *
23830
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23831
+ * @param strategyName - Name of the trading strategy
23832
+ * @param exchangeName - Name of the exchange
23833
+ * @param frameName - Timeframe name (indicates backtest mode if present)
23834
+ * @param timestamp - Unix timestamp in milliseconds for uniqueness
23835
+ * @returns Underscore-separated filename with .md extension
23836
+ * @internal
23837
+ */
23838
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23839
+ const parts = [symbol, strategyName, exchangeName];
23840
+ if (frameName) {
23841
+ parts.push(frameName);
23842
+ parts.push("backtest");
23843
+ }
23844
+ else
23845
+ parts.push("live");
23846
+ return `${parts.join("_")}-${timestamp}.md`;
23847
+ };
23848
+ /**
23849
+ * Maximum number of events to store per symbol-strategy pair.
23850
+ * Older events are discarded when this limit is exceeded.
23851
+ * @internal
23852
+ */
23853
+ const MAX_EVENTS = 250;
23854
+ /**
23855
+ * In-memory storage for accumulating strategy events per symbol-strategy pair.
23856
+ *
23857
+ * Maintains a rolling window of the most recent events (up to MAX_EVENTS),
23858
+ * with newer events added to the front of the list. Provides methods to:
23859
+ * - Add new events (FIFO queue with max size)
23860
+ * - Retrieve aggregated statistics
23861
+ * - Generate markdown reports
23862
+ * - Dump reports to disk
23863
+ *
23864
+ * @internal
23865
+ */
23866
+ class ReportStorage {
23867
+ /**
23868
+ * Creates a new ReportStorage instance.
23869
+ *
23870
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23871
+ * @param strategyName - Name of the trading strategy
23872
+ * @param exchangeName - Name of the exchange
23873
+ * @param frameName - Timeframe name for backtest identification
23874
+ */
23875
+ constructor(symbol, strategyName, exchangeName, frameName) {
23876
+ this.symbol = symbol;
23877
+ this.strategyName = strategyName;
23878
+ this.exchangeName = exchangeName;
23879
+ this.frameName = frameName;
23880
+ this._eventList = [];
23881
+ }
23882
+ /**
23883
+ * Adds a new event to the storage.
23884
+ *
23885
+ * Events are added to the front of the list (most recent first).
23886
+ * If the list exceeds MAX_EVENTS, the oldest event is removed.
23887
+ *
23888
+ * @param event - The strategy event to store
23889
+ */
23890
+ addEvent(event) {
23891
+ this._eventList.unshift(event);
23892
+ if (this._eventList.length > MAX_EVENTS) {
23893
+ this._eventList.pop();
23894
+ }
23895
+ }
23896
+ /**
23897
+ * Retrieves aggregated statistics from stored events.
23898
+ *
23899
+ * Calculates counts for each action type from the event list.
23900
+ *
23901
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
23902
+ */
23903
+ async getData() {
23904
+ if (this._eventList.length === 0) {
23905
+ return {
23906
+ eventList: [],
23907
+ totalEvents: 0,
23908
+ cancelScheduledCount: 0,
23909
+ closePendingCount: 0,
23910
+ partialProfitCount: 0,
23911
+ partialLossCount: 0,
23912
+ trailingStopCount: 0,
23913
+ trailingTakeCount: 0,
23914
+ breakevenCount: 0,
23915
+ };
23916
+ }
23917
+ return {
23918
+ eventList: this._eventList,
23919
+ totalEvents: this._eventList.length,
23920
+ cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
23921
+ closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
23922
+ partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
23923
+ partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
23924
+ trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
23925
+ trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
23926
+ breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
23927
+ };
23928
+ }
23929
+ /**
23930
+ * Generates a markdown report from stored events.
23931
+ *
23932
+ * Creates a formatted markdown document containing:
23933
+ * - Header with symbol and strategy name
23934
+ * - Table of all events with configurable columns
23935
+ * - Summary statistics with counts by action type
23936
+ *
23937
+ * @param symbol - Trading pair symbol for report header
23938
+ * @param strategyName - Strategy name for report header
23939
+ * @param columns - Column configuration for the event table
23940
+ * @returns Promise resolving to formatted markdown string
23941
+ */
23942
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
23943
+ const stats = await this.getData();
23944
+ if (stats.totalEvents === 0) {
23945
+ return [
23946
+ `# Strategy Report: ${symbol}:${strategyName}`,
23947
+ "",
23948
+ "No strategy events recorded yet."
23949
+ ].join("\n");
23950
+ }
23951
+ const visibleColumns = [];
23952
+ for (const col of columns) {
23953
+ if (await col.isVisible()) {
23954
+ visibleColumns.push(col);
23955
+ }
23956
+ }
23957
+ const header = visibleColumns.map((col) => col.label);
23958
+ const separator = visibleColumns.map(() => "---");
23959
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
23960
+ const tableData = [header, separator, ...rows];
23961
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
23962
+ return [
23963
+ `# Strategy Report: ${symbol}:${strategyName}`,
23964
+ "",
23965
+ table,
23966
+ "",
23967
+ `**Total events:** ${stats.totalEvents}`,
23968
+ `- Cancel scheduled: ${stats.cancelScheduledCount}`,
23969
+ `- Close pending: ${stats.closePendingCount}`,
23970
+ `- Partial profit: ${stats.partialProfitCount}`,
23971
+ `- Partial loss: ${stats.partialLossCount}`,
23972
+ `- Trailing stop: ${stats.trailingStopCount}`,
23973
+ `- Trailing take: ${stats.trailingTakeCount}`,
23974
+ `- Breakeven: ${stats.breakevenCount}`,
23975
+ ].join("\n");
23976
+ }
23977
+ /**
23978
+ * Generates and saves a markdown report to disk.
23979
+ *
23980
+ * Creates the output directory if it doesn't exist and writes
23981
+ * the report with a timestamped filename.
23982
+ *
23983
+ * @param symbol - Trading pair symbol for report
23984
+ * @param strategyName - Strategy name for report
23985
+ * @param path - Output directory path (default: "./dump/strategy")
23986
+ * @param columns - Column configuration for the event table
23987
+ */
23988
+ async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
23989
+ const markdown = await this.getReport(symbol, strategyName, columns);
23990
+ const timestamp = Date.now();
23991
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23992
+ await Markdown.writeData("strategy", markdown, {
23993
+ path,
23994
+ file: filename,
23995
+ symbol: this.symbol,
23996
+ strategyName: this.strategyName,
23997
+ exchangeName: this.exchangeName,
23998
+ signalId: "",
23999
+ frameName: this.frameName
24000
+ });
24001
+ }
24002
+ }
24003
+ /**
24004
+ * Service for accumulating strategy management events and generating markdown reports.
24005
+ *
24006
+ * Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
24007
+ * partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
24008
+ * methods to retrieve statistics, generate reports, and export to files.
24009
+ *
24010
+ * Unlike StrategyReportService which writes each event to disk immediately,
24011
+ * this service accumulates events in ReportStorage instances (max 250 per
24012
+ * symbol-strategy pair) for batch reporting.
24013
+ *
24014
+ * Features:
24015
+ * - In-memory event accumulation with memoized storage per symbol-strategy pair
24016
+ * - Statistical data extraction (event counts by action type)
24017
+ * - Markdown report generation with configurable columns
24018
+ * - File export with timestamped filenames
24019
+ * - Selective or full cache clearing
24020
+ *
24021
+ * Lifecycle:
24022
+ * - Call subscribe() to enable event collection
24023
+ * - Events are collected automatically via cancelScheduled, closePending, etc.
24024
+ * - Use getData(), getReport(), or dump() to retrieve accumulated data
24025
+ * - Call unsubscribe() to disable collection and clear all data
24026
+ *
24027
+ * @example
24028
+ * ```typescript
24029
+ * strategyMarkdownService.subscribe();
24030
+ *
24031
+ * // Events are collected automatically during strategy execution
24032
+ * // ...
24033
+ *
24034
+ * // Get statistics
24035
+ * const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
24036
+ *
24037
+ * // Generate markdown report
24038
+ * const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
24039
+ *
24040
+ * // Export to file
24041
+ * await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
24042
+ *
24043
+ * strategyMarkdownService.unsubscribe();
24044
+ * ```
24045
+ *
24046
+ * @see StrategyReportService for immediate event persistence to JSON files
24047
+ * @see Strategy for the high-level utility class that wraps this service
24048
+ */
24049
+ class StrategyMarkdownService {
24050
+ constructor() {
24051
+ this.loggerService = inject(TYPES.loggerService);
24052
+ this.executionContextService = inject(TYPES.executionContextService);
24053
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
24054
+ /**
24055
+ * Memoized factory for ReportStorage instances.
24056
+ *
24057
+ * Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
24058
+ * Uses CREATE_KEY_FN for cache key generation.
24059
+ *
24060
+ * @internal
24061
+ */
24062
+ 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));
24063
+ /**
24064
+ * Records a cancel-scheduled event when a scheduled signal is cancelled.
24065
+ *
24066
+ * Retrieves the scheduled signal from StrategyCoreService and stores
24067
+ * the cancellation event in the appropriate ReportStorage.
24068
+ *
24069
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24070
+ * @param isBacktest - Whether this is a backtest or live trading event
24071
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24072
+ * @param cancelId - Optional identifier for the cancellation reason
24073
+ */
24074
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
24075
+ this.loggerService.log("strategyMarkdownService cancelScheduled", {
24076
+ symbol,
24077
+ isBacktest,
24078
+ cancelId,
24079
+ });
24080
+ if (!this.subscribe.hasValue()) {
24081
+ return;
24082
+ }
24083
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24084
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24085
+ exchangeName: context.exchangeName,
24086
+ strategyName: context.strategyName,
24087
+ frameName: context.frameName,
24088
+ });
24089
+ if (!scheduledRow) {
24090
+ return;
24091
+ }
24092
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24093
+ storage.addEvent({
24094
+ timestamp: Date.now(),
24095
+ symbol,
24096
+ strategyName: context.strategyName,
24097
+ exchangeName: context.exchangeName,
24098
+ frameName: context.frameName,
24099
+ signalId: scheduledRow.id,
24100
+ action: "cancel-scheduled",
24101
+ cancelId,
24102
+ createdAt,
24103
+ backtest: isBacktest,
24104
+ });
24105
+ };
24106
+ /**
24107
+ * Records a close-pending event when a pending signal is closed.
24108
+ *
24109
+ * Retrieves the pending signal from StrategyCoreService and stores
24110
+ * the close event in the appropriate ReportStorage.
24111
+ *
24112
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24113
+ * @param isBacktest - Whether this is a backtest or live trading event
24114
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24115
+ * @param closeId - Optional identifier for the close reason
24116
+ */
24117
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
24118
+ this.loggerService.log("strategyMarkdownService closePending", {
24119
+ symbol,
24120
+ isBacktest,
24121
+ closeId,
24122
+ });
24123
+ if (!this.subscribe.hasValue()) {
24124
+ return;
24125
+ }
24126
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24127
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24128
+ exchangeName: context.exchangeName,
24129
+ strategyName: context.strategyName,
24130
+ frameName: context.frameName,
24131
+ });
24132
+ if (!pendingRow) {
24133
+ return;
24134
+ }
24135
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24136
+ storage.addEvent({
24137
+ timestamp: Date.now(),
24138
+ symbol,
24139
+ strategyName: context.strategyName,
24140
+ exchangeName: context.exchangeName,
24141
+ frameName: context.frameName,
24142
+ signalId: pendingRow.id,
24143
+ action: "close-pending",
24144
+ closeId,
24145
+ createdAt,
24146
+ backtest: isBacktest,
24147
+ });
24148
+ };
24149
+ /**
24150
+ * Records a partial-profit event when a portion of the position is closed at profit.
24151
+ *
24152
+ * Stores the percentage closed and current price when partial profit-taking occurs.
24153
+ *
24154
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24155
+ * @param percentToClose - Percentage of position to close (0-100)
24156
+ * @param currentPrice - Current market price at time of partial close
24157
+ * @param isBacktest - Whether this is a backtest or live trading event
24158
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24159
+ */
24160
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24161
+ this.loggerService.log("strategyMarkdownService partialProfit", {
24162
+ symbol,
24163
+ percentToClose,
24164
+ currentPrice,
24165
+ isBacktest,
24166
+ });
24167
+ if (!this.subscribe.hasValue()) {
24168
+ return;
24169
+ }
24170
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24171
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24172
+ exchangeName: context.exchangeName,
24173
+ strategyName: context.strategyName,
24174
+ frameName: context.frameName,
24175
+ });
24176
+ if (!pendingRow) {
24177
+ return;
24178
+ }
24179
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24180
+ storage.addEvent({
24181
+ timestamp: Date.now(),
24182
+ symbol,
24183
+ strategyName: context.strategyName,
24184
+ exchangeName: context.exchangeName,
24185
+ frameName: context.frameName,
24186
+ signalId: pendingRow.id,
24187
+ action: "partial-profit",
24188
+ percentToClose,
24189
+ currentPrice,
24190
+ createdAt,
24191
+ backtest: isBacktest,
24192
+ });
24193
+ };
24194
+ /**
24195
+ * Records a partial-loss event when a portion of the position is closed at loss.
24196
+ *
24197
+ * Stores the percentage closed and current price when partial loss-cutting occurs.
24198
+ *
24199
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24200
+ * @param percentToClose - Percentage of position to close (0-100)
24201
+ * @param currentPrice - Current market price at time of partial close
24202
+ * @param isBacktest - Whether this is a backtest or live trading event
24203
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24204
+ */
24205
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24206
+ this.loggerService.log("strategyMarkdownService partialLoss", {
24207
+ symbol,
24208
+ percentToClose,
24209
+ currentPrice,
24210
+ isBacktest,
24211
+ });
24212
+ if (!this.subscribe.hasValue()) {
24213
+ return;
24214
+ }
24215
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24216
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24217
+ exchangeName: context.exchangeName,
24218
+ strategyName: context.strategyName,
24219
+ frameName: context.frameName,
24220
+ });
24221
+ if (!pendingRow) {
24222
+ return;
24223
+ }
24224
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24225
+ storage.addEvent({
24226
+ timestamp: Date.now(),
24227
+ symbol,
24228
+ strategyName: context.strategyName,
24229
+ exchangeName: context.exchangeName,
24230
+ frameName: context.frameName,
24231
+ signalId: pendingRow.id,
24232
+ action: "partial-loss",
24233
+ percentToClose,
24234
+ currentPrice,
24235
+ createdAt,
24236
+ backtest: isBacktest,
24237
+ });
24238
+ };
24239
+ /**
24240
+ * Records a trailing-stop event when the stop-loss is adjusted.
24241
+ *
24242
+ * Stores the percentage shift and current price when trailing stop moves.
24243
+ *
24244
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24245
+ * @param percentShift - Percentage the stop-loss was shifted
24246
+ * @param currentPrice - Current market price at time of adjustment
24247
+ * @param isBacktest - Whether this is a backtest or live trading event
24248
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24249
+ */
24250
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24251
+ this.loggerService.log("strategyMarkdownService trailingStop", {
24252
+ symbol,
24253
+ percentShift,
24254
+ currentPrice,
24255
+ isBacktest,
24256
+ });
24257
+ if (!this.subscribe.hasValue()) {
24258
+ return;
24259
+ }
24260
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24261
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24262
+ exchangeName: context.exchangeName,
24263
+ strategyName: context.strategyName,
24264
+ frameName: context.frameName,
24265
+ });
24266
+ if (!pendingRow) {
24267
+ return;
24268
+ }
24269
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24270
+ storage.addEvent({
24271
+ timestamp: Date.now(),
24272
+ symbol,
24273
+ strategyName: context.strategyName,
24274
+ exchangeName: context.exchangeName,
24275
+ frameName: context.frameName,
24276
+ signalId: pendingRow.id,
24277
+ action: "trailing-stop",
24278
+ percentShift,
24279
+ currentPrice,
24280
+ createdAt,
24281
+ backtest: isBacktest,
24282
+ });
24283
+ };
24284
+ /**
24285
+ * Records a trailing-take event when the take-profit is adjusted.
24286
+ *
24287
+ * Stores the percentage shift and current price when trailing take-profit moves.
24288
+ *
24289
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24290
+ * @param percentShift - Percentage the take-profit was shifted
24291
+ * @param currentPrice - Current market price at time of adjustment
24292
+ * @param isBacktest - Whether this is a backtest or live trading event
24293
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24294
+ */
24295
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24296
+ this.loggerService.log("strategyMarkdownService trailingTake", {
24297
+ symbol,
24298
+ percentShift,
24299
+ currentPrice,
24300
+ isBacktest,
24301
+ });
24302
+ if (!this.subscribe.hasValue()) {
24303
+ return;
24304
+ }
24305
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24306
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24307
+ exchangeName: context.exchangeName,
24308
+ strategyName: context.strategyName,
24309
+ frameName: context.frameName,
24310
+ });
24311
+ if (!pendingRow) {
24312
+ return;
24313
+ }
24314
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24315
+ storage.addEvent({
24316
+ timestamp: Date.now(),
24317
+ symbol,
24318
+ strategyName: context.strategyName,
24319
+ exchangeName: context.exchangeName,
24320
+ frameName: context.frameName,
24321
+ signalId: pendingRow.id,
24322
+ action: "trailing-take",
24323
+ percentShift,
24324
+ currentPrice,
24325
+ createdAt,
24326
+ backtest: isBacktest,
24327
+ });
24328
+ };
24329
+ /**
24330
+ * Records a breakeven event when the stop-loss is moved to entry price.
24331
+ *
24332
+ * Stores the current price when breakeven protection is activated.
24333
+ *
24334
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24335
+ * @param currentPrice - Current market price at time of breakeven activation
24336
+ * @param isBacktest - Whether this is a backtest or live trading event
24337
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24338
+ */
24339
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
24340
+ this.loggerService.log("strategyMarkdownService breakeven", {
24341
+ symbol,
24342
+ currentPrice,
24343
+ isBacktest,
24344
+ });
24345
+ if (!this.subscribe.hasValue()) {
24346
+ return;
24347
+ }
24348
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24349
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24350
+ exchangeName: context.exchangeName,
24351
+ strategyName: context.strategyName,
24352
+ frameName: context.frameName,
24353
+ });
24354
+ if (!pendingRow) {
24355
+ return;
24356
+ }
24357
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24358
+ storage.addEvent({
24359
+ timestamp: Date.now(),
24360
+ symbol,
24361
+ strategyName: context.strategyName,
24362
+ exchangeName: context.exchangeName,
24363
+ frameName: context.frameName,
24364
+ signalId: pendingRow.id,
24365
+ action: "breakeven",
24366
+ currentPrice,
24367
+ createdAt,
24368
+ backtest: isBacktest,
24369
+ });
24370
+ };
24371
+ /**
24372
+ * Retrieves aggregated statistics from accumulated strategy events.
24373
+ *
24374
+ * Returns counts for each action type and the full event list from the
24375
+ * ReportStorage for the specified symbol-strategy pair.
24376
+ *
24377
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24378
+ * @param strategyName - Name of the trading strategy
24379
+ * @param exchangeName - Name of the exchange
24380
+ * @param frameName - Timeframe name for backtest identification
24381
+ * @param backtest - Whether to get backtest or live data
24382
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
24383
+ * @throws Error if service not initialized (subscribe() not called)
24384
+ */
24385
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
24386
+ this.loggerService.log("strategyMarkdownService getData", {
24387
+ symbol,
24388
+ strategyName,
24389
+ exchangeName,
24390
+ frameName,
24391
+ backtest,
24392
+ });
24393
+ if (!this.subscribe.hasValue()) {
24394
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
24395
+ }
24396
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24397
+ return storage.getData();
24398
+ };
24399
+ /**
24400
+ * Generates a markdown report from accumulated strategy events.
24401
+ *
24402
+ * Creates a formatted markdown document containing:
24403
+ * - Header with symbol and strategy name
24404
+ * - Table of all events with configurable columns
24405
+ * - Summary statistics with counts by action type
24406
+ *
24407
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24408
+ * @param strategyName - Name of the trading strategy
24409
+ * @param exchangeName - Name of the exchange
24410
+ * @param frameName - Timeframe name for backtest identification
24411
+ * @param backtest - Whether to get backtest or live data
24412
+ * @param columns - Column configuration for the event table
24413
+ * @returns Promise resolving to formatted markdown string
24414
+ * @throws Error if service not initialized (subscribe() not called)
24415
+ */
24416
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
24417
+ this.loggerService.log("strategyMarkdownService getReport", {
24418
+ symbol,
24419
+ strategyName,
24420
+ exchangeName,
24421
+ frameName,
24422
+ backtest,
24423
+ });
24424
+ if (!this.subscribe.hasValue()) {
24425
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
24426
+ }
24427
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24428
+ return storage.getReport(symbol, strategyName, columns);
24429
+ };
24430
+ /**
24431
+ * Generates and saves a markdown report to disk.
24432
+ *
24433
+ * Creates the output directory if it doesn't exist and writes
24434
+ * the report with a timestamped filename via Markdown.writeData().
24435
+ *
24436
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
24437
+ *
24438
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24439
+ * @param strategyName - Name of the trading strategy
24440
+ * @param exchangeName - Name of the exchange
24441
+ * @param frameName - Timeframe name for backtest identification
24442
+ * @param backtest - Whether to dump backtest or live data
24443
+ * @param path - Output directory path (default: "./dump/strategy")
24444
+ * @param columns - Column configuration for the event table
24445
+ * @returns Promise that resolves when file is written
24446
+ * @throws Error if service not initialized (subscribe() not called)
24447
+ */
24448
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
24449
+ this.loggerService.log("strategyMarkdownService dump", {
24450
+ symbol,
24451
+ strategyName,
24452
+ exchangeName,
24453
+ frameName,
24454
+ backtest,
24455
+ path,
24456
+ });
24457
+ if (!this.subscribe.hasValue()) {
24458
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
24459
+ }
24460
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24461
+ await storage.dump(symbol, strategyName, path, columns);
24462
+ };
24463
+ /**
24464
+ * Clears accumulated events from storage.
24465
+ *
24466
+ * Can clear either a specific symbol-strategy pair or all stored data.
24467
+ *
24468
+ * @param payload - Optional filter to clear specific storage. If omitted, clears all.
24469
+ * @param payload.symbol - Trading pair symbol
24470
+ * @param payload.strategyName - Strategy name
24471
+ * @param payload.exchangeName - Exchange name
24472
+ * @param payload.frameName - Frame name
24473
+ * @param payload.backtest - Backtest mode flag
24474
+ */
24475
+ this.clear = async (payload) => {
24476
+ this.loggerService.log("strategyMarkdownService clear", { payload });
24477
+ if (payload) {
24478
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24479
+ this.getStorage.clear(key);
24480
+ }
24481
+ else {
24482
+ this.getStorage.clear();
24483
+ }
24484
+ };
24485
+ /**
24486
+ * Initializes the service for event collection.
24487
+ *
24488
+ * Must be called before any events can be collected or reports generated.
24489
+ * Uses singleshot pattern to ensure only one subscription exists at a time.
24490
+ *
24491
+ * @returns Cleanup function that clears the subscription and all accumulated data
24492
+ */
24493
+ this.subscribe = functoolsKit.singleshot(() => {
24494
+ this.loggerService.log("strategyMarkdownService subscribe");
24495
+ const unCancelSchedule = strategyCommitSubject
24496
+ .filter(({ action }) => action === "cancel-scheduled")
24497
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
24498
+ exchangeName: event.exchangeName,
24499
+ frameName: event.frameName,
24500
+ strategyName: event.strategyName,
24501
+ }, event.cancelId));
24502
+ const unClosePending = strategyCommitSubject
24503
+ .filter(({ action }) => action === "close-pending")
24504
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
24505
+ exchangeName: event.exchangeName,
24506
+ frameName: event.frameName,
24507
+ strategyName: event.strategyName,
24508
+ }, event.closeId));
24509
+ const unPartialProfit = strategyCommitSubject
24510
+ .filter(({ action }) => action === "partial-profit")
24511
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24512
+ exchangeName: event.exchangeName,
24513
+ frameName: event.frameName,
24514
+ strategyName: event.strategyName,
24515
+ }));
24516
+ const unPartialLoss = strategyCommitSubject
24517
+ .filter(({ action }) => action === "partial-loss")
24518
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24519
+ exchangeName: event.exchangeName,
24520
+ frameName: event.frameName,
24521
+ strategyName: event.strategyName,
24522
+ }));
24523
+ const unTrailingStop = strategyCommitSubject
24524
+ .filter(({ action }) => action === "trailing-stop")
24525
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24526
+ exchangeName: event.exchangeName,
24527
+ frameName: event.frameName,
24528
+ strategyName: event.strategyName,
24529
+ }));
24530
+ const unTrailingTake = strategyCommitSubject
24531
+ .filter(({ action }) => action === "trailing-take")
24532
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24533
+ exchangeName: event.exchangeName,
24534
+ frameName: event.frameName,
24535
+ strategyName: event.strategyName,
24536
+ }));
24537
+ const unBreakeven = strategyCommitSubject
24538
+ .filter(({ action }) => action === "breakeven")
24539
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24540
+ exchangeName: event.exchangeName,
24541
+ frameName: event.frameName,
24542
+ strategyName: event.strategyName,
24543
+ }));
24544
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24545
+ return () => {
24546
+ disposeFn();
24547
+ this.subscribe.clear();
24548
+ this.clear();
24549
+ };
24550
+ });
24551
+ /**
24552
+ * Stops event collection and clears all accumulated data.
24553
+ *
24554
+ * Invokes the cleanup function returned by subscribe(), which clears
24555
+ * both the subscription and all ReportStorage instances.
24556
+ * Safe to call multiple times - only acts if subscription exists.
24557
+ */
24558
+ this.unsubscribe = async () => {
24559
+ this.loggerService.log("strategyMarkdownService unsubscribe");
24560
+ if (this.subscribe.hasValue()) {
24561
+ const lastSubscription = this.subscribe();
24562
+ lastSubscription();
24563
+ }
24564
+ };
24565
+ }
24566
+ }
24567
+
24568
+ {
24569
+ provide(TYPES.loggerService, () => new LoggerService());
24570
+ }
24571
+ {
24572
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
24573
+ provide(TYPES.methodContextService, () => new MethodContextService());
24574
+ }
24575
+ {
24576
+ provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
24577
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
24578
+ provide(TYPES.frameConnectionService, () => new FrameConnectionService());
24579
+ provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
24580
+ provide(TYPES.riskConnectionService, () => new RiskConnectionService());
24581
+ provide(TYPES.actionConnectionService, () => new ActionConnectionService());
24582
+ provide(TYPES.partialConnectionService, () => new PartialConnectionService());
24583
+ provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23101
24584
  }
23102
24585
  {
23103
24586
  provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
@@ -23145,6 +24628,7 @@ class RiskReportService {
23145
24628
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
23146
24629
  provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
23147
24630
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
24631
+ provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
23148
24632
  }
23149
24633
  {
23150
24634
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -23156,6 +24640,7 @@ class RiskReportService {
23156
24640
  provide(TYPES.partialReportService, () => new PartialReportService());
23157
24641
  provide(TYPES.breakevenReportService, () => new BreakevenReportService());
23158
24642
  provide(TYPES.riskReportService, () => new RiskReportService());
24643
+ provide(TYPES.strategyReportService, () => new StrategyReportService());
23159
24644
  }
23160
24645
  {
23161
24646
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -23232,6 +24717,7 @@ const markdownServices = {
23232
24717
  partialMarkdownService: inject(TYPES.partialMarkdownService),
23233
24718
  breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
23234
24719
  riskMarkdownService: inject(TYPES.riskMarkdownService),
24720
+ strategyMarkdownService: inject(TYPES.strategyMarkdownService),
23235
24721
  };
23236
24722
  const reportServices = {
23237
24723
  backtestReportService: inject(TYPES.backtestReportService),
@@ -23243,6 +24729,7 @@ const reportServices = {
23243
24729
  partialReportService: inject(TYPES.partialReportService),
23244
24730
  breakevenReportService: inject(TYPES.breakevenReportService),
23245
24731
  riskReportService: inject(TYPES.riskReportService),
24732
+ strategyReportService: inject(TYPES.strategyReportService),
23246
24733
  };
23247
24734
  const validationServices = {
23248
24735
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -25321,6 +26808,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
25321
26808
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
25322
26809
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
25323
26810
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
26811
+ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
26812
+ const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
25324
26813
  /**
25325
26814
  * Subscribes to all signal events with queued async processing.
25326
26815
  *
@@ -26350,6 +27839,78 @@ function listenActivePingOnce(filterFn, fn) {
26350
27839
  bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
26351
27840
  return activePingSubject.filter(filterFn).once(fn);
26352
27841
  }
27842
+ /**
27843
+ * Subscribes to strategy management events with queued async processing.
27844
+ *
27845
+ * Emits when strategy management actions are executed:
27846
+ * - cancel-scheduled: Scheduled signal cancelled
27847
+ * - close-pending: Pending signal closed
27848
+ * - partial-profit: Partial close at profit level
27849
+ * - partial-loss: Partial close at loss level
27850
+ * - trailing-stop: Stop-loss adjusted
27851
+ * - trailing-take: Take-profit adjusted
27852
+ * - breakeven: Stop-loss moved to entry price
27853
+ *
27854
+ * Events are processed sequentially in order received, even if callback is async.
27855
+ * Uses queued wrapper to prevent concurrent execution of the callback.
27856
+ *
27857
+ * @param fn - Callback function to handle strategy commit events
27858
+ * @returns Unsubscribe function to stop listening
27859
+ *
27860
+ * @example
27861
+ * ```typescript
27862
+ * import { listenStrategyCommit } from "./function/event";
27863
+ *
27864
+ * const unsubscribe = listenStrategyCommit((event) => {
27865
+ * console.log(`[${event.action}] ${event.symbol}`);
27866
+ * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
27867
+ * if (event.action === "partial-profit") {
27868
+ * console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
27869
+ * }
27870
+ * });
27871
+ *
27872
+ * // Later: stop listening
27873
+ * unsubscribe();
27874
+ * ```
27875
+ */
27876
+ function listenStrategyCommit(fn) {
27877
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
27878
+ return strategyCommitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
27879
+ }
27880
+ /**
27881
+ * Subscribes to filtered strategy management events with one-time execution.
27882
+ *
27883
+ * Listens for events matching the filter predicate, then executes callback once
27884
+ * and automatically unsubscribes. Useful for waiting for specific strategy actions.
27885
+ *
27886
+ * @param filterFn - Predicate to filter which events trigger the callback
27887
+ * @param fn - Callback function to handle the filtered event (called only once)
27888
+ * @returns Unsubscribe function to cancel the listener before it fires
27889
+ *
27890
+ * @example
27891
+ * ```typescript
27892
+ * import { listenStrategyCommitOnce } from "./function/event";
27893
+ *
27894
+ * // Wait for first trailing stop adjustment
27895
+ * listenStrategyCommitOnce(
27896
+ * (event) => event.action === "trailing-stop",
27897
+ * (event) => console.log("Trailing stop adjusted:", event.symbol)
27898
+ * );
27899
+ *
27900
+ * // Wait for breakeven on BTCUSDT
27901
+ * const cancel = listenStrategyCommitOnce(
27902
+ * (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
27903
+ * (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
27904
+ * );
27905
+ *
27906
+ * // Cancel if needed before event fires
27907
+ * cancel();
27908
+ * ```
27909
+ */
27910
+ function listenStrategyCommitOnce(filterFn, fn) {
27911
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
27912
+ return strategyCommitSubject.filter(filterFn).once(fn);
27913
+ }
26353
27914
 
26354
27915
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
26355
27916
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -31409,6 +32970,201 @@ class BreakevenUtils {
31409
32970
  */
31410
32971
  const Breakeven = new BreakevenUtils();
31411
32972
 
32973
+ const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
32974
+ const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
32975
+ const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
32976
+ /**
32977
+ * Utility class for accessing strategy management reports and statistics.
32978
+ *
32979
+ * Provides static-like methods (via singleton instance) to retrieve data
32980
+ * accumulated by StrategyMarkdownService from strategy management events.
32981
+ *
32982
+ * Features:
32983
+ * - Statistical data extraction (event counts by action type)
32984
+ * - Markdown report generation with event tables
32985
+ * - File export to disk
32986
+ *
32987
+ * Data source:
32988
+ * - StrategyMarkdownService receives events via direct method calls
32989
+ * - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
32990
+ * - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
32991
+ * trailing-stop, trailing-take, breakeven
32992
+ *
32993
+ * @example
32994
+ * ```typescript
32995
+ * import { Strategy } from "./classes/Strategy";
32996
+ *
32997
+ * // Get statistical data for BTCUSDT:my-strategy
32998
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
32999
+ * console.log(`Total events: ${stats.totalEvents}`);
33000
+ * console.log(`Partial profit events: ${stats.partialProfitCount}`);
33001
+ *
33002
+ * // Generate markdown report
33003
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33004
+ * console.log(markdown); // Formatted table with all events
33005
+ *
33006
+ * // Export report to file
33007
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
33008
+ * ```
33009
+ */
33010
+ class StrategyUtils {
33011
+ constructor() {
33012
+ /**
33013
+ * Retrieves statistical data from accumulated strategy events.
33014
+ *
33015
+ * Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
33016
+ * Returns aggregated metrics calculated from all strategy events.
33017
+ *
33018
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33019
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33020
+ * @param backtest - Whether to get backtest data (default: false)
33021
+ * @returns Promise resolving to StrategyStatisticsModel object with counts and event list
33022
+ *
33023
+ * @example
33024
+ * ```typescript
33025
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33026
+ *
33027
+ * console.log(`Total events: ${stats.totalEvents}`);
33028
+ * console.log(`Partial profit: ${stats.partialProfitCount}`);
33029
+ * console.log(`Trailing stop: ${stats.trailingStopCount}`);
33030
+ *
33031
+ * // Iterate through all events
33032
+ * for (const event of stats.eventList) {
33033
+ * console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
33034
+ * }
33035
+ * ```
33036
+ */
33037
+ this.getData = async (symbol, context, backtest = false) => {
33038
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
33039
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
33040
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
33041
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
33042
+ {
33043
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33044
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
33045
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
33046
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
33047
+ }
33048
+ return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
33049
+ };
33050
+ /**
33051
+ * Generates markdown report with all strategy events for a symbol-strategy pair.
33052
+ *
33053
+ * Creates formatted table containing:
33054
+ * - Symbol
33055
+ * - Strategy
33056
+ * - Signal ID
33057
+ * - Action (cancel-scheduled, close-pending, partial-profit, etc.)
33058
+ * - Price
33059
+ * - Percent values (% To Close, % Shift)
33060
+ * - Cancel/Close IDs
33061
+ * - Timestamp (ISO 8601)
33062
+ * - Mode (Backtest/Live)
33063
+ *
33064
+ * Also includes summary statistics at the end with counts by action type.
33065
+ *
33066
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33067
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33068
+ * @param backtest - Whether to get backtest data (default: false)
33069
+ * @param columns - Optional columns configuration for the report
33070
+ * @returns Promise resolving to markdown formatted report string
33071
+ *
33072
+ * @example
33073
+ * ```typescript
33074
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33075
+ * console.log(markdown);
33076
+ *
33077
+ * // Output:
33078
+ * // # Strategy Report: BTCUSDT:my-strategy
33079
+ * //
33080
+ * // | Symbol | Strategy | Signal ID | Action | Price | ... |
33081
+ * // | --- | --- | --- | --- | --- | ... |
33082
+ * // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
33083
+ * //
33084
+ * // **Total events:** 5
33085
+ * // - Cancel scheduled: 0
33086
+ * // - Close pending: 1
33087
+ * // - Partial profit: 2
33088
+ * // ...
33089
+ * ```
33090
+ */
33091
+ this.getReport = async (symbol, context, backtest = false, columns) => {
33092
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
33093
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
33094
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
33095
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
33096
+ {
33097
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33098
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
33099
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
33100
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
33101
+ }
33102
+ return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
33103
+ };
33104
+ /**
33105
+ * Generates and saves markdown report to file.
33106
+ *
33107
+ * Creates directory if it doesn't exist.
33108
+ * Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
33109
+ *
33110
+ * Delegates to StrategyMarkdownService.dump() which:
33111
+ * 1. Generates markdown report via getReport()
33112
+ * 2. Creates output directory (recursive mkdir)
33113
+ * 3. Writes file with UTF-8 encoding
33114
+ * 4. Logs success/failure to console
33115
+ *
33116
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33117
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33118
+ * @param backtest - Whether to dump backtest data (default: false)
33119
+ * @param path - Output directory path (default: "./dump/strategy")
33120
+ * @param columns - Optional columns configuration for the report
33121
+ * @returns Promise that resolves when file is written
33122
+ *
33123
+ * @example
33124
+ * ```typescript
33125
+ * // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
33126
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
33127
+ *
33128
+ * // Save to custom path
33129
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
33130
+ *
33131
+ * // After multiple symbols backtested, export all reports
33132
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
33133
+ * await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
33134
+ * }
33135
+ * ```
33136
+ */
33137
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
33138
+ bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
33139
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
33140
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
33141
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
33142
+ {
33143
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33144
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
33145
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
33146
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
33147
+ }
33148
+ await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
33149
+ };
33150
+ }
33151
+ }
33152
+ /**
33153
+ * Global singleton instance of StrategyUtils.
33154
+ * Provides static-like access to strategy management reporting methods.
33155
+ *
33156
+ * @example
33157
+ * ```typescript
33158
+ * import { Strategy } from "backtest-kit";
33159
+ *
33160
+ * // Usage same as StrategyUtils methods
33161
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33162
+ * const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33163
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33164
+ * ```
33165
+ */
33166
+ const Strategy = new StrategyUtils();
33167
+
31412
33168
  /**
31413
33169
  * Rounds a price to the appropriate precision based on the tick size.
31414
33170
  *
@@ -31569,6 +33325,7 @@ exports.Report = Report;
31569
33325
  exports.ReportBase = ReportBase;
31570
33326
  exports.Risk = Risk;
31571
33327
  exports.Schedule = Schedule;
33328
+ exports.Strategy = Strategy;
31572
33329
  exports.Walker = Walker;
31573
33330
  exports.addActionSchema = addActionSchema;
31574
33331
  exports.addExchangeSchema = addExchangeSchema;
@@ -31644,6 +33401,8 @@ exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
31644
33401
  exports.listenSignalLive = listenSignalLive;
31645
33402
  exports.listenSignalLiveOnce = listenSignalLiveOnce;
31646
33403
  exports.listenSignalOnce = listenSignalOnce;
33404
+ exports.listenStrategyCommit = listenStrategyCommit;
33405
+ exports.listenStrategyCommitOnce = listenStrategyCommitOnce;
31647
33406
  exports.listenValidation = listenValidation;
31648
33407
  exports.listenWalker = listenWalker;
31649
33408
  exports.listenWalkerComplete = listenWalkerComplete;