backtest-kit 2.2.1 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +32 -10
  2. package/build/index.cjs +1997 -178
  3. package/build/index.mjs +1996 -180
  4. package/package.json +2 -1
  5. package/types.d.ts +2039 -963
package/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createActivator } from 'di-kit';
2
2
  import { scoped } from 'di-scoped';
3
- import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, trycatch, retry, errorData, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, singlerun } from 'functools-kit';
3
+ import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, errorData, not, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, singlerun } from 'functools-kit';
4
4
  import * as fs from 'fs/promises';
5
5
  import fs__default from 'fs/promises';
6
6
  import path, { join, dirname } from 'path';
@@ -101,6 +101,7 @@ const markdownServices$1 = {
101
101
  breakevenMarkdownService: Symbol('breakevenMarkdownService'),
102
102
  outlineMarkdownService: Symbol('outlineMarkdownService'),
103
103
  riskMarkdownService: Symbol('riskMarkdownService'),
104
+ strategyMarkdownService: Symbol('strategyMarkdownService'),
104
105
  };
105
106
  const reportServices$1 = {
106
107
  backtestReportService: Symbol('backtestReportService'),
@@ -112,6 +113,7 @@ const reportServices$1 = {
112
113
  partialReportService: Symbol('partialReportService'),
113
114
  breakevenReportService: Symbol('breakevenReportService'),
114
115
  riskReportService: Symbol('riskReportService'),
116
+ strategyReportService: Symbol('strategyReportService'),
115
117
  };
116
118
  const validationServices$1 = {
117
119
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -520,6 +522,20 @@ const schedulePingSubject = new Subject();
520
522
  * Allows users to track active signal lifecycle and implement custom dynamic management logic.
521
523
  */
522
524
  const activePingSubject = new Subject();
525
+ /**
526
+ * Strategy management signal emitter.
527
+ * Emits when strategy management actions are executed:
528
+ * - cancel-scheduled: Scheduled signal cancelled
529
+ * - close-pending: Pending signal closed
530
+ * - partial-profit: Partial close at profit level
531
+ * - partial-loss: Partial close at loss level
532
+ * - trailing-stop: Stop-loss adjusted
533
+ * - trailing-take: Take-profit adjusted
534
+ * - breakeven: Stop-loss moved to entry price
535
+ *
536
+ * Used by StrategyReportService and StrategyMarkdownService for event logging and reporting.
537
+ */
538
+ const strategyCommitSubject = new Subject();
523
539
 
524
540
  var emitters = /*#__PURE__*/Object.freeze({
525
541
  __proto__: null,
@@ -540,6 +556,7 @@ var emitters = /*#__PURE__*/Object.freeze({
540
556
  signalBacktestEmitter: signalBacktestEmitter,
541
557
  signalEmitter: signalEmitter,
542
558
  signalLiveEmitter: signalLiveEmitter,
559
+ strategyCommitSubject: strategyCommitSubject,
543
560
  validationSubject: validationSubject,
544
561
  walkerCompleteSubject: walkerCompleteSubject,
545
562
  walkerEmitter: walkerEmitter,
@@ -1545,8 +1562,15 @@ class PersistCandleUtils {
1545
1562
  const candle = await stateStorage.readValue(timestamp);
1546
1563
  cachedCandles.push(candle);
1547
1564
  }
1548
- catch {
1549
- // Skip invalid candles
1565
+ catch (error) {
1566
+ const message = `PersistCandleUtils.readCandlesData found invalid candle symbol=${symbol} interval=${interval} timestamp=${timestamp}`;
1567
+ const payload = {
1568
+ error: errorData(error),
1569
+ message: getErrorMessage(error),
1570
+ };
1571
+ bt.loggerService.warn(message, payload);
1572
+ console.warn(message, payload);
1573
+ errorEmitter.next(error);
1550
1574
  continue;
1551
1575
  }
1552
1576
  }
@@ -3658,6 +3682,7 @@ const CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN = async (self, scheduled, currentPrice)
3658
3682
  symbol: self.params.execution.context.symbol,
3659
3683
  backtest: self.params.execution.context.backtest,
3660
3684
  reason: "timeout",
3685
+ createdAt: currentTime,
3661
3686
  };
3662
3687
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
3663
3688
  return result;
@@ -3713,6 +3738,7 @@ const CANCEL_SCHEDULED_SIGNAL_BY_STOPLOSS_FN = async (self, scheduled, currentPr
3713
3738
  symbol: self.params.execution.context.symbol,
3714
3739
  backtest: self.params.execution.context.backtest,
3715
3740
  reason: "price_reject",
3741
+ createdAt: currentTime,
3716
3742
  };
3717
3743
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
3718
3744
  return result;
@@ -3768,6 +3794,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
3768
3794
  symbol: self.params.execution.context.symbol,
3769
3795
  currentPrice: self._pendingSignal.priceOpen,
3770
3796
  backtest: self.params.execution.context.backtest,
3797
+ createdAt: activationTime,
3771
3798
  };
3772
3799
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
3773
3800
  return result;
@@ -4200,6 +4227,7 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
4200
4227
  percentSl: 0,
4201
4228
  pnl,
4202
4229
  backtest: self.params.execution.context.backtest,
4230
+ createdAt: currentTime,
4203
4231
  };
4204
4232
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4205
4233
  return result;
@@ -4224,6 +4252,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
4224
4252
  symbol: self.params.execution.context.symbol,
4225
4253
  currentPrice: currentPrice,
4226
4254
  backtest: self.params.execution.context.backtest,
4255
+ createdAt: currentTime,
4227
4256
  };
4228
4257
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4229
4258
  return result;
@@ -4244,6 +4273,7 @@ const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
4244
4273
  symbol: self.params.execution.context.symbol,
4245
4274
  currentPrice: signal.priceOpen,
4246
4275
  backtest: self.params.execution.context.backtest,
4276
+ createdAt: currentTime,
4247
4277
  };
4248
4278
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4249
4279
  return result;
@@ -4308,6 +4338,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
4308
4338
  frameName: self.params.method.context.frameName,
4309
4339
  symbol: self.params.execution.context.symbol,
4310
4340
  backtest: self.params.execution.context.backtest,
4341
+ createdAt: currentTime,
4311
4342
  };
4312
4343
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4313
4344
  return result;
@@ -4381,6 +4412,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4381
4412
  percentSl,
4382
4413
  pnl,
4383
4414
  backtest: self.params.execution.context.backtest,
4415
+ createdAt: currentTime,
4384
4416
  };
4385
4417
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4386
4418
  return result;
@@ -4397,6 +4429,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
4397
4429
  symbol: self.params.execution.context.symbol,
4398
4430
  currentPrice: currentPrice,
4399
4431
  backtest: self.params.execution.context.backtest,
4432
+ createdAt: currentTime,
4400
4433
  };
4401
4434
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
4402
4435
  return result;
@@ -4423,6 +4456,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
4423
4456
  symbol: self.params.execution.context.symbol,
4424
4457
  backtest: self.params.execution.context.backtest,
4425
4458
  reason,
4459
+ createdAt: closeTimestamp,
4426
4460
  };
4427
4461
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
4428
4462
  return result;
@@ -4503,6 +4537,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
4503
4537
  frameName: self.params.method.context.frameName,
4504
4538
  symbol: self.params.execution.context.symbol,
4505
4539
  backtest: self.params.execution.context.backtest,
4540
+ createdAt: closeTimestamp,
4506
4541
  };
4507
4542
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
4508
4543
  return result;
@@ -5019,6 +5054,7 @@ class ClientStrategy {
5019
5054
  backtest: this.params.execution.context.backtest,
5020
5055
  reason: "user",
5021
5056
  cancelId: cancelledSignal.cancelId,
5057
+ createdAt: currentTime,
5022
5058
  };
5023
5059
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5024
5060
  return result;
@@ -5053,6 +5089,7 @@ class ClientStrategy {
5053
5089
  symbol: this.params.execution.context.symbol,
5054
5090
  backtest: this.params.execution.context.backtest,
5055
5091
  closeId: closedSignal.closeId,
5092
+ createdAt: currentTime,
5056
5093
  };
5057
5094
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5058
5095
  return result;
@@ -5167,6 +5204,7 @@ class ClientStrategy {
5167
5204
  backtest: true,
5168
5205
  reason: "user",
5169
5206
  cancelId: cancelledSignal.cancelId,
5207
+ createdAt: closeTimestamp,
5170
5208
  };
5171
5209
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledResult, closeTimestamp, this.params.execution.context.backtest);
5172
5210
  return cancelledResult;
@@ -5198,6 +5236,7 @@ class ClientStrategy {
5198
5236
  symbol: this.params.execution.context.symbol,
5199
5237
  backtest: true,
5200
5238
  closeId: closedSignal.closeId,
5239
+ createdAt: closeTimestamp,
5201
5240
  };
5202
5241
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, closedResult, closeTimestamp, this.params.execution.context.backtest);
5203
5242
  return closedResult;
@@ -6513,7 +6552,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
6513
6552
  * @param backtest - Whether running in backtest mode
6514
6553
  * @returns Unique string key for memoization
6515
6554
  */
6516
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
6555
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
6517
6556
  const parts = [symbol, strategyName, exchangeName];
6518
6557
  if (frameName)
6519
6558
  parts.push(frameName);
@@ -6678,7 +6717,7 @@ class StrategyConnectionService {
6678
6717
  * @param backtest - Whether running in backtest mode
6679
6718
  * @returns Configured ClientStrategy instance
6680
6719
  */
6681
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6720
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6682
6721
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
6683
6722
  return new ClientStrategy({
6684
6723
  symbol,
@@ -6933,7 +6972,7 @@ class StrategyConnectionService {
6933
6972
  }
6934
6973
  return;
6935
6974
  }
6936
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6975
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6937
6976
  if (!this.getStrategy.has(key)) {
6938
6977
  return;
6939
6978
  }
@@ -7902,7 +7941,7 @@ class ClientRisk {
7902
7941
  * @param backtest - Whether running in backtest mode
7903
7942
  * @returns Unique string key for memoization
7904
7943
  */
7905
- const CREATE_KEY_FN$j = (riskName, exchangeName, frameName, backtest) => {
7944
+ const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
7906
7945
  const parts = [riskName, exchangeName];
7907
7946
  if (frameName)
7908
7947
  parts.push(frameName);
@@ -8001,7 +8040,7 @@ class RiskConnectionService {
8001
8040
  * @param backtest - True if backtest mode, false if live mode
8002
8041
  * @returns Configured ClientRisk instance
8003
8042
  */
8004
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8043
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8005
8044
  const schema = this.riskSchemaService.get(riskName);
8006
8045
  return new ClientRisk({
8007
8046
  ...schema,
@@ -8069,7 +8108,7 @@ class RiskConnectionService {
8069
8108
  payload,
8070
8109
  });
8071
8110
  if (payload) {
8072
- const key = CREATE_KEY_FN$j(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8111
+ const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8073
8112
  this.getRisk.clear(key);
8074
8113
  }
8075
8114
  else {
@@ -9492,7 +9531,7 @@ class ClientAction {
9492
9531
  * @param backtest - Whether running in backtest mode
9493
9532
  * @returns Unique string key for memoization
9494
9533
  */
9495
- const CREATE_KEY_FN$i = (actionName, strategyName, exchangeName, frameName, backtest) => {
9534
+ const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
9496
9535
  const parts = [actionName, strategyName, exchangeName];
9497
9536
  if (frameName)
9498
9537
  parts.push(frameName);
@@ -9543,7 +9582,7 @@ class ActionConnectionService {
9543
9582
  * @param backtest - True if backtest mode, false if live mode
9544
9583
  * @returns Configured ClientAction instance
9545
9584
  */
9546
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9585
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9547
9586
  const schema = this.actionSchemaService.get(actionName);
9548
9587
  return new ClientAction({
9549
9588
  ...schema,
@@ -9736,7 +9775,7 @@ class ActionConnectionService {
9736
9775
  await Promise.all(actions.map(async (action) => await action.dispose()));
9737
9776
  return;
9738
9777
  }
9739
- const key = CREATE_KEY_FN$i(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9778
+ const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9740
9779
  if (!this.getAction.has(key)) {
9741
9780
  return;
9742
9781
  }
@@ -9754,7 +9793,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
9754
9793
  * @param exchangeName - Exchange name
9755
9794
  * @returns Unique string key for memoization
9756
9795
  */
9757
- const CREATE_KEY_FN$h = (exchangeName) => {
9796
+ const CREATE_KEY_FN$i = (exchangeName) => {
9758
9797
  return exchangeName;
9759
9798
  };
9760
9799
  /**
@@ -9778,7 +9817,7 @@ class ExchangeCoreService {
9778
9817
  * @param exchangeName - Name of the exchange to validate
9779
9818
  * @returns Promise that resolves when validation is complete
9780
9819
  */
9781
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$h(exchangeName), async (exchangeName) => {
9820
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
9782
9821
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
9783
9822
  exchangeName,
9784
9823
  });
@@ -10002,12 +10041,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
10002
10041
  * @param context - Execution context with strategyName, exchangeName, frameName
10003
10042
  * @returns Unique string key for memoization
10004
10043
  */
10005
- const CREATE_KEY_FN$g = (context) => {
10044
+ const CREATE_KEY_FN$h = (context) => {
10006
10045
  const parts = [context.strategyName, context.exchangeName];
10007
10046
  if (context.frameName)
10008
10047
  parts.push(context.frameName);
10009
10048
  return parts.join(":");
10010
10049
  };
10050
+ /**
10051
+ * Broadcasts StrategyCommitContract event to strategyCommitSubject.
10052
+ *
10053
+ * @param event - The signal commit event to broadcast
10054
+ */
10055
+ const CALL_STRATEGY_COMMIT_FN = trycatch(async (event) => {
10056
+ await strategyCommitSubject.next(event);
10057
+ }, {
10058
+ fallback: (error) => {
10059
+ const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
10060
+ const payload = {
10061
+ error: errorData(error),
10062
+ message: getErrorMessage(error),
10063
+ };
10064
+ bt.loggerService.warn(message, payload);
10065
+ console.warn(message, payload);
10066
+ errorEmitter.next(error);
10067
+ },
10068
+ defaultValue: null,
10069
+ });
10011
10070
  /**
10012
10071
  * Global service for strategy operations with execution context injection.
10013
10072
  *
@@ -10034,7 +10093,7 @@ class StrategyCoreService {
10034
10093
  * @param context - Execution context with strategyName, exchangeName, frameName
10035
10094
  * @returns Promise that resolves when validation is complete
10036
10095
  */
10037
- this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10096
+ this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
10038
10097
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
10039
10098
  context,
10040
10099
  });
@@ -10240,7 +10299,19 @@ class StrategyCoreService {
10240
10299
  cancelId,
10241
10300
  });
10242
10301
  await this.validate(context);
10243
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10302
+ const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10303
+ {
10304
+ await CALL_STRATEGY_COMMIT_FN({
10305
+ action: "cancel-scheduled",
10306
+ symbol,
10307
+ strategyName: context.strategyName,
10308
+ exchangeName: context.exchangeName,
10309
+ frameName: context.frameName,
10310
+ backtest,
10311
+ cancelId,
10312
+ });
10313
+ }
10314
+ return result;
10244
10315
  };
10245
10316
  /**
10246
10317
  * Closes the pending signal without stopping the strategy.
@@ -10267,7 +10338,19 @@ class StrategyCoreService {
10267
10338
  closeId,
10268
10339
  });
10269
10340
  await this.validate(context);
10270
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10341
+ const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10342
+ {
10343
+ await CALL_STRATEGY_COMMIT_FN({
10344
+ action: "close-pending",
10345
+ symbol,
10346
+ strategyName: context.strategyName,
10347
+ exchangeName: context.exchangeName,
10348
+ frameName: context.frameName,
10349
+ backtest,
10350
+ closeId,
10351
+ });
10352
+ }
10353
+ return result;
10271
10354
  };
10272
10355
  /**
10273
10356
  * Disposes the ClientStrategy instance for the given context.
@@ -10348,7 +10431,20 @@ class StrategyCoreService {
10348
10431
  backtest,
10349
10432
  });
10350
10433
  await this.validate(context);
10351
- return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10434
+ const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10435
+ if (result) {
10436
+ await CALL_STRATEGY_COMMIT_FN({
10437
+ action: "partial-profit",
10438
+ symbol,
10439
+ strategyName: context.strategyName,
10440
+ exchangeName: context.exchangeName,
10441
+ frameName: context.frameName,
10442
+ backtest,
10443
+ percentToClose,
10444
+ currentPrice,
10445
+ });
10446
+ }
10447
+ return result;
10352
10448
  };
10353
10449
  /**
10354
10450
  * Executes partial close at loss level (moving toward SL).
@@ -10389,7 +10485,20 @@ class StrategyCoreService {
10389
10485
  backtest,
10390
10486
  });
10391
10487
  await this.validate(context);
10392
- return await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10488
+ const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10489
+ if (result) {
10490
+ await CALL_STRATEGY_COMMIT_FN({
10491
+ action: "partial-loss",
10492
+ symbol,
10493
+ strategyName: context.strategyName,
10494
+ exchangeName: context.exchangeName,
10495
+ frameName: context.frameName,
10496
+ backtest,
10497
+ percentToClose,
10498
+ currentPrice,
10499
+ });
10500
+ }
10501
+ return result;
10393
10502
  };
10394
10503
  /**
10395
10504
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -10428,7 +10537,20 @@ class StrategyCoreService {
10428
10537
  backtest,
10429
10538
  });
10430
10539
  await this.validate(context);
10431
- return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10540
+ const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10541
+ if (result) {
10542
+ await CALL_STRATEGY_COMMIT_FN({
10543
+ action: "trailing-stop",
10544
+ symbol,
10545
+ strategyName: context.strategyName,
10546
+ exchangeName: context.exchangeName,
10547
+ frameName: context.frameName,
10548
+ backtest,
10549
+ percentShift,
10550
+ currentPrice,
10551
+ });
10552
+ }
10553
+ return result;
10432
10554
  };
10433
10555
  /**
10434
10556
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -10463,7 +10585,20 @@ class StrategyCoreService {
10463
10585
  backtest,
10464
10586
  });
10465
10587
  await this.validate(context);
10466
- return await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10588
+ const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10589
+ if (result) {
10590
+ await CALL_STRATEGY_COMMIT_FN({
10591
+ action: "trailing-take",
10592
+ symbol,
10593
+ strategyName: context.strategyName,
10594
+ exchangeName: context.exchangeName,
10595
+ frameName: context.frameName,
10596
+ backtest,
10597
+ percentShift,
10598
+ currentPrice,
10599
+ });
10600
+ }
10601
+ return result;
10467
10602
  };
10468
10603
  /**
10469
10604
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -10493,7 +10628,19 @@ class StrategyCoreService {
10493
10628
  backtest,
10494
10629
  });
10495
10630
  await this.validate(context);
10496
- return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10631
+ const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10632
+ if (result) {
10633
+ await CALL_STRATEGY_COMMIT_FN({
10634
+ action: "breakeven",
10635
+ symbol,
10636
+ strategyName: context.strategyName,
10637
+ exchangeName: context.exchangeName,
10638
+ frameName: context.frameName,
10639
+ backtest,
10640
+ currentPrice,
10641
+ });
10642
+ }
10643
+ return result;
10497
10644
  };
10498
10645
  }
10499
10646
  }
@@ -10567,7 +10714,7 @@ class SizingGlobalService {
10567
10714
  * @param context - Context with riskName, exchangeName, frameName
10568
10715
  * @returns Unique string key for memoization
10569
10716
  */
10570
- const CREATE_KEY_FN$f = (context) => {
10717
+ const CREATE_KEY_FN$g = (context) => {
10571
10718
  const parts = [context.riskName, context.exchangeName];
10572
10719
  if (context.frameName)
10573
10720
  parts.push(context.frameName);
@@ -10593,7 +10740,7 @@ class RiskGlobalService {
10593
10740
  * @param payload - Payload with riskName, exchangeName and frameName
10594
10741
  * @returns Promise that resolves when validation is complete
10595
10742
  */
10596
- this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10743
+ this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10597
10744
  this.loggerService.log("riskGlobalService validate", {
10598
10745
  context,
10599
10746
  });
@@ -10671,7 +10818,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
10671
10818
  * @param context - Execution context with strategyName, exchangeName, frameName
10672
10819
  * @returns Unique string key for memoization
10673
10820
  */
10674
- const CREATE_KEY_FN$e = (context) => {
10821
+ const CREATE_KEY_FN$f = (context) => {
10675
10822
  const parts = [context.strategyName, context.exchangeName];
10676
10823
  if (context.frameName)
10677
10824
  parts.push(context.frameName);
@@ -10715,7 +10862,7 @@ class ActionCoreService {
10715
10862
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
10716
10863
  * @returns Promise that resolves when all validations complete
10717
10864
  */
10718
- this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), async (context) => {
10865
+ this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10719
10866
  this.loggerService.log(METHOD_NAME_VALIDATE, {
10720
10867
  context,
10721
10868
  });
@@ -13965,6 +14112,116 @@ const schedule_columns = [
13965
14112
  },
13966
14113
  ];
13967
14114
 
14115
+ /**
14116
+ * Column configuration for strategy markdown reports.
14117
+ *
14118
+ * Defines the table structure for displaying strategy management events in trading reports.
14119
+ * Each column specifies how to format and display strategy actions like partial profit/loss,
14120
+ * trailing stop/take, breakeven, cancel scheduled, and close pending.
14121
+ *
14122
+ * Used by {@link StrategyMarkdownService} to generate markdown tables showing:
14123
+ * - Signal identification (symbol, strategy name, signal ID)
14124
+ * - Action information (action type, percent values, prices)
14125
+ * - Timing information (timestamp, mode: backtest or live)
14126
+ *
14127
+ * @remarks
14128
+ * This configuration tracks all strategy management events - when signals are
14129
+ * modified, cancelled, or closed during their lifecycle.
14130
+ *
14131
+ * @example
14132
+ * ```typescript
14133
+ * import { strategy_columns } from "./assets/strategy.columns";
14134
+ *
14135
+ * // Use with StrategyMarkdownService
14136
+ * const service = new StrategyMarkdownService();
14137
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
14138
+ *
14139
+ * // Or customize to show only key fields
14140
+ * const customColumns = strategy_columns.filter(col =>
14141
+ * ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
14142
+ * );
14143
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
14144
+ * ```
14145
+ *
14146
+ * @see {@link StrategyMarkdownService} for usage in report generation
14147
+ * @see {@link ColumnModel} for column interface definition
14148
+ * @see {@link StrategyEvent} for data structure
14149
+ */
14150
+ const strategy_columns = [
14151
+ {
14152
+ key: "symbol",
14153
+ label: "Symbol",
14154
+ format: (data) => data.symbol,
14155
+ isVisible: () => true,
14156
+ },
14157
+ {
14158
+ key: "strategyName",
14159
+ label: "Strategy",
14160
+ format: (data) => data.strategyName,
14161
+ isVisible: () => true,
14162
+ },
14163
+ {
14164
+ key: "signalId",
14165
+ label: "Signal ID",
14166
+ format: (data) => data.signalId,
14167
+ isVisible: () => true,
14168
+ },
14169
+ {
14170
+ key: "action",
14171
+ label: "Action",
14172
+ format: (data) => data.action,
14173
+ isVisible: () => true,
14174
+ },
14175
+ {
14176
+ key: "currentPrice",
14177
+ label: "Price",
14178
+ format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
14179
+ isVisible: () => true,
14180
+ },
14181
+ {
14182
+ key: "percentToClose",
14183
+ label: "% To Close",
14184
+ format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
14185
+ isVisible: () => true,
14186
+ },
14187
+ {
14188
+ key: "percentShift",
14189
+ label: "% Shift",
14190
+ format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
14191
+ isVisible: () => true,
14192
+ },
14193
+ {
14194
+ key: "cancelId",
14195
+ label: "Cancel ID",
14196
+ format: (data) => data.cancelId || "N/A",
14197
+ isVisible: () => true,
14198
+ },
14199
+ {
14200
+ key: "closeId",
14201
+ label: "Close ID",
14202
+ format: (data) => data.closeId || "N/A",
14203
+ isVisible: () => true,
14204
+ },
14205
+ {
14206
+ key: "createdAt",
14207
+ label: "Created At",
14208
+ format: (data) => data.createdAt || "N/A",
14209
+ isVisible: () => true,
14210
+ },
14211
+ {
14212
+ key: "timestamp",
14213
+ label: "Timestamp",
14214
+ format: (data) => new Date(data.timestamp).toISOString(),
14215
+ isVisible: () => true,
14216
+ },
14217
+ {
14218
+ key: "mode",
14219
+ label: "Mode",
14220
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
14221
+ isVisible: () => true,
14222
+ },
14223
+ ];
14224
+
13968
14225
  /**
13969
14226
  * Column configuration for walker strategy comparison table in markdown reports.
13970
14227
  *
@@ -14178,6 +14435,8 @@ const COLUMN_CONFIG = {
14178
14435
  risk_columns,
14179
14436
  /** Columns for scheduled report output */
14180
14437
  schedule_columns,
14438
+ /** Columns for strategy management events */
14439
+ strategy_columns,
14181
14440
  /** Walker: PnL summary columns */
14182
14441
  walker_pnl_columns,
14183
14442
  /** Walker: strategy-level summary columns */
@@ -14214,6 +14473,7 @@ const WILDCARD_TARGET$1 = {
14214
14473
  partial: true,
14215
14474
  performance: true,
14216
14475
  risk: true,
14476
+ strategy: true,
14217
14477
  schedule: true,
14218
14478
  walker: true,
14219
14479
  };
@@ -14443,7 +14703,7 @@ class MarkdownUtils {
14443
14703
  *
14444
14704
  * @returns Cleanup function that unsubscribes from all enabled services
14445
14705
  */
14446
- 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) => {
14706
+ 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) => {
14447
14707
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
14448
14708
  backtest: bt$1,
14449
14709
  breakeven,
@@ -14452,6 +14712,7 @@ class MarkdownUtils {
14452
14712
  partial,
14453
14713
  performance,
14454
14714
  risk,
14715
+ strategy,
14455
14716
  schedule,
14456
14717
  walker,
14457
14718
  });
@@ -14477,6 +14738,9 @@ class MarkdownUtils {
14477
14738
  if (risk) {
14478
14739
  unList.push(bt.riskMarkdownService.subscribe());
14479
14740
  }
14741
+ if (strategy) {
14742
+ unList.push(bt.strategyMarkdownService.subscribe());
14743
+ }
14480
14744
  if (schedule) {
14481
14745
  unList.push(bt.scheduleMarkdownService.subscribe());
14482
14746
  }
@@ -14522,7 +14786,7 @@ class MarkdownUtils {
14522
14786
  * Markdown.disable();
14523
14787
  * ```
14524
14788
  */
14525
- 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) => {
14789
+ 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) => {
14526
14790
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
14527
14791
  backtest: bt$1,
14528
14792
  breakeven,
@@ -14531,6 +14795,7 @@ class MarkdownUtils {
14531
14795
  partial,
14532
14796
  performance,
14533
14797
  risk,
14798
+ strategy,
14534
14799
  schedule,
14535
14800
  walker,
14536
14801
  });
@@ -14555,6 +14820,9 @@ class MarkdownUtils {
14555
14820
  if (risk) {
14556
14821
  bt.riskMarkdownService.unsubscribe();
14557
14822
  }
14823
+ if (strategy) {
14824
+ bt.strategyMarkdownService.unsubscribe();
14825
+ }
14558
14826
  if (schedule) {
14559
14827
  bt.scheduleMarkdownService.unsubscribe();
14560
14828
  }
@@ -14667,7 +14935,7 @@ const Markdown = new MarkdownAdapter();
14667
14935
  * @param backtest - Whether running in backtest mode
14668
14936
  * @returns Unique string key for memoization
14669
14937
  */
14670
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
14938
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
14671
14939
  const parts = [symbol, strategyName, exchangeName];
14672
14940
  if (frameName)
14673
14941
  parts.push(frameName);
@@ -14684,7 +14952,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
14684
14952
  * @param timestamp - Unix timestamp in milliseconds
14685
14953
  * @returns Filename string
14686
14954
  */
14687
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14955
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14688
14956
  const parts = [symbol, strategyName, exchangeName];
14689
14957
  if (frameName) {
14690
14958
  parts.push(frameName);
@@ -14713,12 +14981,12 @@ function isUnsafe$3(value) {
14713
14981
  return false;
14714
14982
  }
14715
14983
  /** Maximum number of signals to store in backtest reports */
14716
- const MAX_EVENTS$7 = 250;
14984
+ const MAX_EVENTS$8 = 250;
14717
14985
  /**
14718
14986
  * Storage class for accumulating closed signals per strategy.
14719
14987
  * Maintains a list of all closed signals and provides methods to generate reports.
14720
14988
  */
14721
- let ReportStorage$6 = class ReportStorage {
14989
+ let ReportStorage$7 = class ReportStorage {
14722
14990
  constructor(symbol, strategyName, exchangeName, frameName) {
14723
14991
  this.symbol = symbol;
14724
14992
  this.strategyName = strategyName;
@@ -14735,7 +15003,7 @@ let ReportStorage$6 = class ReportStorage {
14735
15003
  addSignal(data) {
14736
15004
  this._signalList.unshift(data);
14737
15005
  // Trim queue if exceeded MAX_EVENTS
14738
- if (this._signalList.length > MAX_EVENTS$7) {
15006
+ if (this._signalList.length > MAX_EVENTS$8) {
14739
15007
  this._signalList.pop();
14740
15008
  }
14741
15009
  }
@@ -14859,7 +15127,7 @@ let ReportStorage$6 = class ReportStorage {
14859
15127
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
14860
15128
  const markdown = await this.getReport(strategyName, columns);
14861
15129
  const timestamp = Date.now();
14862
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15130
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
14863
15131
  await Markdown.writeData("backtest", markdown, {
14864
15132
  path,
14865
15133
  file: filename,
@@ -14906,7 +15174,7 @@ class BacktestMarkdownService {
14906
15174
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
14907
15175
  * Each combination gets its own isolated storage instance.
14908
15176
  */
14909
- this.getStorage = 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));
15177
+ this.getStorage = 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));
14910
15178
  /**
14911
15179
  * Processes tick events and accumulates closed signals.
14912
15180
  * Should be called from IStrategyCallbacks.onTick.
@@ -15063,7 +15331,7 @@ class BacktestMarkdownService {
15063
15331
  payload,
15064
15332
  });
15065
15333
  if (payload) {
15066
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15334
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15067
15335
  this.getStorage.clear(key);
15068
15336
  }
15069
15337
  else {
@@ -15125,7 +15393,7 @@ class BacktestMarkdownService {
15125
15393
  * @param backtest - Whether running in backtest mode
15126
15394
  * @returns Unique string key for memoization
15127
15395
  */
15128
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15396
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
15129
15397
  const parts = [symbol, strategyName, exchangeName];
15130
15398
  if (frameName)
15131
15399
  parts.push(frameName);
@@ -15142,7 +15410,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
15142
15410
  * @param timestamp - Unix timestamp in milliseconds
15143
15411
  * @returns Filename string
15144
15412
  */
15145
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15413
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15146
15414
  const parts = [symbol, strategyName, exchangeName];
15147
15415
  if (frameName) {
15148
15416
  parts.push(frameName);
@@ -15171,12 +15439,12 @@ function isUnsafe$2(value) {
15171
15439
  return false;
15172
15440
  }
15173
15441
  /** Maximum number of events to store in live trading reports */
15174
- const MAX_EVENTS$6 = 250;
15442
+ const MAX_EVENTS$7 = 250;
15175
15443
  /**
15176
15444
  * Storage class for accumulating all tick events per strategy.
15177
15445
  * Maintains a chronological list of all events (idle, opened, active, closed).
15178
15446
  */
15179
- let ReportStorage$5 = class ReportStorage {
15447
+ let ReportStorage$6 = class ReportStorage {
15180
15448
  constructor(symbol, strategyName, exchangeName, frameName) {
15181
15449
  this.symbol = symbol;
15182
15450
  this.strategyName = strategyName;
@@ -15208,7 +15476,7 @@ let ReportStorage$5 = class ReportStorage {
15208
15476
  }
15209
15477
  {
15210
15478
  this._eventList.unshift(newEvent);
15211
- if (this._eventList.length > MAX_EVENTS$6) {
15479
+ if (this._eventList.length > MAX_EVENTS$7) {
15212
15480
  this._eventList.pop();
15213
15481
  }
15214
15482
  }
@@ -15235,7 +15503,7 @@ let ReportStorage$5 = class ReportStorage {
15235
15503
  totalExecuted: data.signal.totalExecuted,
15236
15504
  });
15237
15505
  // Trim queue if exceeded MAX_EVENTS
15238
- if (this._eventList.length > MAX_EVENTS$6) {
15506
+ if (this._eventList.length > MAX_EVENTS$7) {
15239
15507
  this._eventList.pop();
15240
15508
  }
15241
15509
  }
@@ -15274,7 +15542,7 @@ let ReportStorage$5 = class ReportStorage {
15274
15542
  // If no previous active event found, add new event
15275
15543
  this._eventList.unshift(newEvent);
15276
15544
  // Trim queue if exceeded MAX_EVENTS
15277
- if (this._eventList.length > MAX_EVENTS$6) {
15545
+ if (this._eventList.length > MAX_EVENTS$7) {
15278
15546
  this._eventList.pop();
15279
15547
  }
15280
15548
  }
@@ -15306,7 +15574,7 @@ let ReportStorage$5 = class ReportStorage {
15306
15574
  };
15307
15575
  this._eventList.unshift(newEvent);
15308
15576
  // Trim queue if exceeded MAX_EVENTS
15309
- if (this._eventList.length > MAX_EVENTS$6) {
15577
+ if (this._eventList.length > MAX_EVENTS$7) {
15310
15578
  this._eventList.pop();
15311
15579
  }
15312
15580
  }
@@ -15332,7 +15600,7 @@ let ReportStorage$5 = class ReportStorage {
15332
15600
  totalExecuted: data.signal.totalExecuted,
15333
15601
  });
15334
15602
  // Trim queue if exceeded MAX_EVENTS
15335
- if (this._eventList.length > MAX_EVENTS$6) {
15603
+ if (this._eventList.length > MAX_EVENTS$7) {
15336
15604
  this._eventList.pop();
15337
15605
  }
15338
15606
  }
@@ -15371,7 +15639,7 @@ let ReportStorage$5 = class ReportStorage {
15371
15639
  // If no previous waiting event found, add new event
15372
15640
  this._eventList.unshift(newEvent);
15373
15641
  // Trim queue if exceeded MAX_EVENTS
15374
- if (this._eventList.length > MAX_EVENTS$6) {
15642
+ if (this._eventList.length > MAX_EVENTS$7) {
15375
15643
  this._eventList.pop();
15376
15644
  }
15377
15645
  }
@@ -15398,7 +15666,7 @@ let ReportStorage$5 = class ReportStorage {
15398
15666
  cancelReason: data.reason,
15399
15667
  });
15400
15668
  // Trim queue if exceeded MAX_EVENTS
15401
- if (this._eventList.length > MAX_EVENTS$6) {
15669
+ if (this._eventList.length > MAX_EVENTS$7) {
15402
15670
  this._eventList.pop();
15403
15671
  }
15404
15672
  }
@@ -15537,7 +15805,7 @@ let ReportStorage$5 = class ReportStorage {
15537
15805
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
15538
15806
  const markdown = await this.getReport(strategyName, columns);
15539
15807
  const timestamp = Date.now();
15540
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15808
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15541
15809
  await Markdown.writeData("live", markdown, {
15542
15810
  path,
15543
15811
  signalId: "",
@@ -15587,7 +15855,7 @@ class LiveMarkdownService {
15587
15855
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
15588
15856
  * Each combination gets its own isolated storage instance.
15589
15857
  */
15590
- this.getStorage = 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));
15858
+ this.getStorage = 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));
15591
15859
  /**
15592
15860
  * Subscribes to live signal emitter to receive tick events.
15593
15861
  * Protected against multiple subscriptions.
@@ -15805,7 +16073,7 @@ class LiveMarkdownService {
15805
16073
  payload,
15806
16074
  });
15807
16075
  if (payload) {
15808
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16076
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15809
16077
  this.getStorage.clear(key);
15810
16078
  }
15811
16079
  else {
@@ -15825,7 +16093,7 @@ class LiveMarkdownService {
15825
16093
  * @param backtest - Whether running in backtest mode
15826
16094
  * @returns Unique string key for memoization
15827
16095
  */
15828
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16096
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15829
16097
  const parts = [symbol, strategyName, exchangeName];
15830
16098
  if (frameName)
15831
16099
  parts.push(frameName);
@@ -15842,7 +16110,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
15842
16110
  * @param timestamp - Unix timestamp in milliseconds
15843
16111
  * @returns Filename string
15844
16112
  */
15845
- const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16113
+ const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15846
16114
  const parts = [symbol, strategyName, exchangeName];
15847
16115
  if (frameName) {
15848
16116
  parts.push(frameName);
@@ -15853,12 +16121,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
15853
16121
  return `${parts.join("_")}-${timestamp}.md`;
15854
16122
  };
15855
16123
  /** Maximum number of events to store in schedule reports */
15856
- const MAX_EVENTS$5 = 250;
16124
+ const MAX_EVENTS$6 = 250;
15857
16125
  /**
15858
16126
  * Storage class for accumulating scheduled signal events per strategy.
15859
16127
  * Maintains a chronological list of scheduled and cancelled events.
15860
16128
  */
15861
- let ReportStorage$4 = class ReportStorage {
16129
+ let ReportStorage$5 = class ReportStorage {
15862
16130
  constructor(symbol, strategyName, exchangeName, frameName) {
15863
16131
  this.symbol = symbol;
15864
16132
  this.strategyName = strategyName;
@@ -15889,7 +16157,7 @@ let ReportStorage$4 = class ReportStorage {
15889
16157
  totalExecuted: data.signal.totalExecuted,
15890
16158
  });
15891
16159
  // Trim queue if exceeded MAX_EVENTS
15892
- if (this._eventList.length > MAX_EVENTS$5) {
16160
+ if (this._eventList.length > MAX_EVENTS$6) {
15893
16161
  this._eventList.pop();
15894
16162
  }
15895
16163
  }
@@ -15919,7 +16187,7 @@ let ReportStorage$4 = class ReportStorage {
15919
16187
  };
15920
16188
  this._eventList.unshift(newEvent);
15921
16189
  // Trim queue if exceeded MAX_EVENTS
15922
- if (this._eventList.length > MAX_EVENTS$5) {
16190
+ if (this._eventList.length > MAX_EVENTS$6) {
15923
16191
  this._eventList.pop();
15924
16192
  }
15925
16193
  }
@@ -15952,7 +16220,7 @@ let ReportStorage$4 = class ReportStorage {
15952
16220
  };
15953
16221
  this._eventList.unshift(newEvent);
15954
16222
  // Trim queue if exceeded MAX_EVENTS
15955
- if (this._eventList.length > MAX_EVENTS$5) {
16223
+ if (this._eventList.length > MAX_EVENTS$6) {
15956
16224
  this._eventList.pop();
15957
16225
  }
15958
16226
  }
@@ -16059,7 +16327,7 @@ let ReportStorage$4 = class ReportStorage {
16059
16327
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
16060
16328
  const markdown = await this.getReport(strategyName, columns);
16061
16329
  const timestamp = Date.now();
16062
- const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16330
+ const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16063
16331
  await Markdown.writeData("schedule", markdown, {
16064
16332
  path,
16065
16333
  file: filename,
@@ -16100,7 +16368,7 @@ class ScheduleMarkdownService {
16100
16368
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
16101
16369
  * Each combination gets its own isolated storage instance.
16102
16370
  */
16103
- this.getStorage = 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));
16371
+ this.getStorage = 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));
16104
16372
  /**
16105
16373
  * Subscribes to signal emitter to receive scheduled signal events.
16106
16374
  * Protected against multiple subscriptions.
@@ -16303,7 +16571,7 @@ class ScheduleMarkdownService {
16303
16571
  payload,
16304
16572
  });
16305
16573
  if (payload) {
16306
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16574
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16307
16575
  this.getStorage.clear(key);
16308
16576
  }
16309
16577
  else {
@@ -16323,7 +16591,7 @@ class ScheduleMarkdownService {
16323
16591
  * @param backtest - Whether running in backtest mode
16324
16592
  * @returns Unique string key for memoization
16325
16593
  */
16326
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
16594
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16327
16595
  const parts = [symbol, strategyName, exchangeName];
16328
16596
  if (frameName)
16329
16597
  parts.push(frameName);
@@ -16340,7 +16608,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
16340
16608
  * @param timestamp - Unix timestamp in milliseconds
16341
16609
  * @returns Filename string
16342
16610
  */
16343
- const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16611
+ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16344
16612
  const parts = [symbol, strategyName, exchangeName];
16345
16613
  if (frameName) {
16346
16614
  parts.push(frameName);
@@ -16360,7 +16628,7 @@ function percentile(sortedArray, p) {
16360
16628
  return sortedArray[Math.max(0, index)];
16361
16629
  }
16362
16630
  /** Maximum number of performance events to store per strategy */
16363
- const MAX_EVENTS$4 = 10000;
16631
+ const MAX_EVENTS$5 = 10000;
16364
16632
  /**
16365
16633
  * Storage class for accumulating performance metrics per strategy.
16366
16634
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -16382,7 +16650,7 @@ class PerformanceStorage {
16382
16650
  addEvent(event) {
16383
16651
  this._events.unshift(event);
16384
16652
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
16385
- if (this._events.length > MAX_EVENTS$4) {
16653
+ if (this._events.length > MAX_EVENTS$5) {
16386
16654
  this._events.pop();
16387
16655
  }
16388
16656
  }
@@ -16524,7 +16792,7 @@ class PerformanceStorage {
16524
16792
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
16525
16793
  const markdown = await this.getReport(strategyName, columns);
16526
16794
  const timestamp = Date.now();
16527
- const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16795
+ const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16528
16796
  await Markdown.writeData("performance", markdown, {
16529
16797
  path,
16530
16798
  file: filename,
@@ -16571,7 +16839,7 @@ class PerformanceMarkdownService {
16571
16839
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
16572
16840
  * Each combination gets its own isolated storage instance.
16573
16841
  */
16574
- this.getStorage = 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));
16842
+ this.getStorage = 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));
16575
16843
  /**
16576
16844
  * Subscribes to performance emitter to receive performance events.
16577
16845
  * Protected against multiple subscriptions.
@@ -16738,7 +17006,7 @@ class PerformanceMarkdownService {
16738
17006
  payload,
16739
17007
  });
16740
17008
  if (payload) {
16741
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
17009
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16742
17010
  this.getStorage.clear(key);
16743
17011
  }
16744
17012
  else {
@@ -16752,7 +17020,7 @@ class PerformanceMarkdownService {
16752
17020
  * Creates a filename for markdown report based on walker name.
16753
17021
  * Filename format: "walkerName-timestamp.md"
16754
17022
  */
16755
- const CREATE_FILE_NAME_FN$4 = (walkerName, timestamp) => {
17023
+ const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
16756
17024
  return `${walkerName}-${timestamp}.md`;
16757
17025
  };
16758
17026
  /**
@@ -16787,7 +17055,7 @@ function formatMetric(value) {
16787
17055
  * Storage class for accumulating walker results.
16788
17056
  * Maintains a list of all strategy results and provides methods to generate reports.
16789
17057
  */
16790
- let ReportStorage$3 = class ReportStorage {
17058
+ let ReportStorage$4 = class ReportStorage {
16791
17059
  constructor(walkerName) {
16792
17060
  this.walkerName = walkerName;
16793
17061
  /** Walker metadata (set from first addResult call) */
@@ -16975,7 +17243,7 @@ let ReportStorage$3 = class ReportStorage {
16975
17243
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
16976
17244
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
16977
17245
  const timestamp = Date.now();
16978
- const filename = CREATE_FILE_NAME_FN$4(this.walkerName, timestamp);
17246
+ const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
16979
17247
  await Markdown.writeData("walker", markdown, {
16980
17248
  path,
16981
17249
  file: filename,
@@ -17011,7 +17279,7 @@ class WalkerMarkdownService {
17011
17279
  * Memoized function to get or create ReportStorage for a walker.
17012
17280
  * Each walker gets its own isolated storage instance.
17013
17281
  */
17014
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$3(walkerName));
17282
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
17015
17283
  /**
17016
17284
  * Subscribes to walker emitter to receive walker progress events.
17017
17285
  * Protected against multiple subscriptions.
@@ -17208,7 +17476,7 @@ class WalkerMarkdownService {
17208
17476
  * @param backtest - Whether running in backtest mode
17209
17477
  * @returns Unique string key for memoization
17210
17478
  */
17211
- const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17479
+ const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
17212
17480
  const parts = [exchangeName];
17213
17481
  if (frameName)
17214
17482
  parts.push(frameName);
@@ -17219,7 +17487,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17219
17487
  * Creates a filename for markdown report based on memoization key components.
17220
17488
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
17221
17489
  */
17222
- const CREATE_FILE_NAME_FN$3 = (strategyName, exchangeName, frameName, timestamp) => {
17490
+ const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
17223
17491
  const parts = [strategyName, exchangeName];
17224
17492
  if (frameName) {
17225
17493
  parts.push(frameName);
@@ -17252,7 +17520,7 @@ function isUnsafe(value) {
17252
17520
  return false;
17253
17521
  }
17254
17522
  /** Maximum number of signals to store per symbol in heatmap reports */
17255
- const MAX_EVENTS$3 = 250;
17523
+ const MAX_EVENTS$4 = 250;
17256
17524
  /**
17257
17525
  * Storage class for accumulating closed signals per strategy and generating heatmap.
17258
17526
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -17278,7 +17546,7 @@ class HeatmapStorage {
17278
17546
  const signals = this.symbolData.get(symbol);
17279
17547
  signals.unshift(data);
17280
17548
  // Trim queue if exceeded MAX_EVENTS per symbol
17281
- if (signals.length > MAX_EVENTS$3) {
17549
+ if (signals.length > MAX_EVENTS$4) {
17282
17550
  signals.pop();
17283
17551
  }
17284
17552
  }
@@ -17529,7 +17797,7 @@ class HeatmapStorage {
17529
17797
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
17530
17798
  const markdown = await this.getReport(strategyName, columns);
17531
17799
  const timestamp = Date.now();
17532
- const filename = CREATE_FILE_NAME_FN$3(strategyName, this.exchangeName, this.frameName, timestamp);
17800
+ const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
17533
17801
  await Markdown.writeData("heat", markdown, {
17534
17802
  path,
17535
17803
  file: filename,
@@ -17575,7 +17843,7 @@ class HeatMarkdownService {
17575
17843
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
17576
17844
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
17577
17845
  */
17578
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17846
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17579
17847
  /**
17580
17848
  * Subscribes to signal emitter to receive tick events.
17581
17849
  * Protected against multiple subscriptions.
@@ -17770,7 +18038,7 @@ class HeatMarkdownService {
17770
18038
  payload,
17771
18039
  });
17772
18040
  if (payload) {
17773
- const key = CREATE_KEY_FN$9(payload.exchangeName, payload.frameName, payload.backtest);
18041
+ const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
17774
18042
  this.getStorage.clear(key);
17775
18043
  }
17776
18044
  else {
@@ -18801,7 +19069,7 @@ class ClientPartial {
18801
19069
  * @param backtest - Whether running in backtest mode
18802
19070
  * @returns Unique string key for memoization
18803
19071
  */
18804
- const CREATE_KEY_FN$8 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
19072
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
18805
19073
  /**
18806
19074
  * Creates a callback function for emitting profit events to partialProfitSubject.
18807
19075
  *
@@ -18923,7 +19191,7 @@ class PartialConnectionService {
18923
19191
  * Key format: "signalId:backtest" or "signalId:live"
18924
19192
  * Value: ClientPartial instance with logger and event emitters
18925
19193
  */
18926
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$8(signalId, backtest), (signalId, backtest) => {
19194
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
18927
19195
  return new ClientPartial({
18928
19196
  signalId,
18929
19197
  logger: this.loggerService,
@@ -19013,7 +19281,7 @@ class PartialConnectionService {
19013
19281
  const partial = this.getPartial(data.id, backtest);
19014
19282
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
19015
19283
  await partial.clear(symbol, data, priceClose, backtest);
19016
- const key = CREATE_KEY_FN$8(data.id, backtest);
19284
+ const key = CREATE_KEY_FN$9(data.id, backtest);
19017
19285
  this.getPartial.clear(key);
19018
19286
  };
19019
19287
  }
@@ -19029,7 +19297,7 @@ class PartialConnectionService {
19029
19297
  * @param backtest - Whether running in backtest mode
19030
19298
  * @returns Unique string key for memoization
19031
19299
  */
19032
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19300
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19033
19301
  const parts = [symbol, strategyName, exchangeName];
19034
19302
  if (frameName)
19035
19303
  parts.push(frameName);
@@ -19040,7 +19308,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
19040
19308
  * Creates a filename for markdown report based on memoization key components.
19041
19309
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
19042
19310
  */
19043
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19311
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19044
19312
  const parts = [symbol, strategyName, exchangeName];
19045
19313
  if (frameName) {
19046
19314
  parts.push(frameName);
@@ -19051,12 +19319,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
19051
19319
  return `${parts.join("_")}-${timestamp}.md`;
19052
19320
  };
19053
19321
  /** Maximum number of events to store in partial reports */
19054
- const MAX_EVENTS$2 = 250;
19322
+ const MAX_EVENTS$3 = 250;
19055
19323
  /**
19056
19324
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
19057
19325
  * Maintains a chronological list of profit and loss level events.
19058
19326
  */
19059
- let ReportStorage$2 = class ReportStorage {
19327
+ let ReportStorage$3 = class ReportStorage {
19060
19328
  constructor(symbol, strategyName, exchangeName, frameName) {
19061
19329
  this.symbol = symbol;
19062
19330
  this.strategyName = strategyName;
@@ -19093,7 +19361,7 @@ let ReportStorage$2 = class ReportStorage {
19093
19361
  backtest,
19094
19362
  });
19095
19363
  // Trim queue if exceeded MAX_EVENTS
19096
- if (this._eventList.length > MAX_EVENTS$2) {
19364
+ if (this._eventList.length > MAX_EVENTS$3) {
19097
19365
  this._eventList.pop();
19098
19366
  }
19099
19367
  }
@@ -19125,7 +19393,7 @@ let ReportStorage$2 = class ReportStorage {
19125
19393
  backtest,
19126
19394
  });
19127
19395
  // Trim queue if exceeded MAX_EVENTS
19128
- if (this._eventList.length > MAX_EVENTS$2) {
19396
+ if (this._eventList.length > MAX_EVENTS$3) {
19129
19397
  this._eventList.pop();
19130
19398
  }
19131
19399
  }
@@ -19201,7 +19469,7 @@ let ReportStorage$2 = class ReportStorage {
19201
19469
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
19202
19470
  const markdown = await this.getReport(symbol, strategyName, columns);
19203
19471
  const timestamp = Date.now();
19204
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19472
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19205
19473
  await Markdown.writeData("partial", markdown, {
19206
19474
  path,
19207
19475
  file: filename,
@@ -19242,7 +19510,7 @@ class PartialMarkdownService {
19242
19510
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19243
19511
  * Each combination gets its own isolated storage instance.
19244
19512
  */
19245
- this.getStorage = 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));
19513
+ this.getStorage = 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));
19246
19514
  /**
19247
19515
  * Subscribes to partial profit/loss signal emitters to receive events.
19248
19516
  * Protected against multiple subscriptions.
@@ -19452,7 +19720,7 @@ class PartialMarkdownService {
19452
19720
  payload,
19453
19721
  });
19454
19722
  if (payload) {
19455
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19723
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19456
19724
  this.getStorage.clear(key);
19457
19725
  }
19458
19726
  else {
@@ -19468,7 +19736,7 @@ class PartialMarkdownService {
19468
19736
  * @param context - Context with strategyName, exchangeName, frameName
19469
19737
  * @returns Unique string key for memoization
19470
19738
  */
19471
- const CREATE_KEY_FN$6 = (context) => {
19739
+ const CREATE_KEY_FN$7 = (context) => {
19472
19740
  const parts = [context.strategyName, context.exchangeName];
19473
19741
  if (context.frameName)
19474
19742
  parts.push(context.frameName);
@@ -19542,7 +19810,7 @@ class PartialGlobalService {
19542
19810
  * @param context - Context with strategyName, exchangeName and frameName
19543
19811
  * @param methodName - Name of the calling method for error tracking
19544
19812
  */
19545
- this.validate = memoize(([context]) => CREATE_KEY_FN$6(context), (context, methodName) => {
19813
+ this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
19546
19814
  this.loggerService.log("partialGlobalService validate", {
19547
19815
  context,
19548
19816
  methodName,
@@ -19997,7 +20265,7 @@ class ClientBreakeven {
19997
20265
  * @param backtest - Whether running in backtest mode
19998
20266
  * @returns Unique string key for memoization
19999
20267
  */
20000
- const CREATE_KEY_FN$5 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20268
+ const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20001
20269
  /**
20002
20270
  * Creates a callback function for emitting breakeven events to breakevenSubject.
20003
20271
  *
@@ -20083,7 +20351,7 @@ class BreakevenConnectionService {
20083
20351
  * Key format: "signalId:backtest" or "signalId:live"
20084
20352
  * Value: ClientBreakeven instance with logger and event emitter
20085
20353
  */
20086
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$5(signalId, backtest), (signalId, backtest) => {
20354
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
20087
20355
  return new ClientBreakeven({
20088
20356
  signalId,
20089
20357
  logger: this.loggerService,
@@ -20144,7 +20412,7 @@ class BreakevenConnectionService {
20144
20412
  const breakeven = this.getBreakeven(data.id, backtest);
20145
20413
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
20146
20414
  await breakeven.clear(symbol, data, priceClose, backtest);
20147
- const key = CREATE_KEY_FN$5(data.id, backtest);
20415
+ const key = CREATE_KEY_FN$6(data.id, backtest);
20148
20416
  this.getBreakeven.clear(key);
20149
20417
  };
20150
20418
  }
@@ -20160,7 +20428,7 @@ class BreakevenConnectionService {
20160
20428
  * @param backtest - Whether running in backtest mode
20161
20429
  * @returns Unique string key for memoization
20162
20430
  */
20163
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20431
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20164
20432
  const parts = [symbol, strategyName, exchangeName];
20165
20433
  if (frameName)
20166
20434
  parts.push(frameName);
@@ -20171,7 +20439,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
20171
20439
  * Creates a filename for markdown report based on memoization key components.
20172
20440
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20173
20441
  */
20174
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20442
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20175
20443
  const parts = [symbol, strategyName, exchangeName];
20176
20444
  if (frameName) {
20177
20445
  parts.push(frameName);
@@ -20182,12 +20450,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
20182
20450
  return `${parts.join("_")}-${timestamp}.md`;
20183
20451
  };
20184
20452
  /** Maximum number of events to store in breakeven reports */
20185
- const MAX_EVENTS$1 = 250;
20453
+ const MAX_EVENTS$2 = 250;
20186
20454
  /**
20187
20455
  * Storage class for accumulating breakeven events per symbol-strategy pair.
20188
20456
  * Maintains a chronological list of breakeven events.
20189
20457
  */
20190
- let ReportStorage$1 = class ReportStorage {
20458
+ let ReportStorage$2 = class ReportStorage {
20191
20459
  constructor(symbol, strategyName, exchangeName, frameName) {
20192
20460
  this.symbol = symbol;
20193
20461
  this.strategyName = strategyName;
@@ -20222,7 +20490,7 @@ let ReportStorage$1 = class ReportStorage {
20222
20490
  backtest,
20223
20491
  });
20224
20492
  // Trim queue if exceeded MAX_EVENTS
20225
- if (this._eventList.length > MAX_EVENTS$1) {
20493
+ if (this._eventList.length > MAX_EVENTS$2) {
20226
20494
  this._eventList.pop();
20227
20495
  }
20228
20496
  }
@@ -20290,7 +20558,7 @@ let ReportStorage$1 = class ReportStorage {
20290
20558
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
20291
20559
  const markdown = await this.getReport(symbol, strategyName, columns);
20292
20560
  const timestamp = Date.now();
20293
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20561
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20294
20562
  await Markdown.writeData("breakeven", markdown, {
20295
20563
  path,
20296
20564
  file: filename,
@@ -20331,7 +20599,7 @@ class BreakevenMarkdownService {
20331
20599
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20332
20600
  * Each combination gets its own isolated storage instance.
20333
20601
  */
20334
- this.getStorage = 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));
20602
+ this.getStorage = 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));
20335
20603
  /**
20336
20604
  * Subscribes to breakeven signal emitter to receive events.
20337
20605
  * Protected against multiple subscriptions.
@@ -20520,7 +20788,7 @@ class BreakevenMarkdownService {
20520
20788
  payload,
20521
20789
  });
20522
20790
  if (payload) {
20523
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20791
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20524
20792
  this.getStorage.clear(key);
20525
20793
  }
20526
20794
  else {
@@ -20536,7 +20804,7 @@ class BreakevenMarkdownService {
20536
20804
  * @param context - Context with strategyName, exchangeName, frameName
20537
20805
  * @returns Unique string key for memoization
20538
20806
  */
20539
- const CREATE_KEY_FN$3 = (context) => {
20807
+ const CREATE_KEY_FN$4 = (context) => {
20540
20808
  const parts = [context.strategyName, context.exchangeName];
20541
20809
  if (context.frameName)
20542
20810
  parts.push(context.frameName);
@@ -20610,7 +20878,7 @@ class BreakevenGlobalService {
20610
20878
  * @param context - Context with strategyName, exchangeName and frameName
20611
20879
  * @param methodName - Name of the calling method for error tracking
20612
20880
  */
20613
- this.validate = memoize(([context]) => CREATE_KEY_FN$3(context), (context, methodName) => {
20881
+ this.validate = memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
20614
20882
  this.loggerService.log("breakevenGlobalService validate", {
20615
20883
  context,
20616
20884
  methodName,
@@ -20823,7 +21091,7 @@ class ConfigValidationService {
20823
21091
  * @param backtest - Whether running in backtest mode
20824
21092
  * @returns Unique string key for memoization
20825
21093
  */
20826
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
21094
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20827
21095
  const parts = [symbol, strategyName, exchangeName];
20828
21096
  if (frameName)
20829
21097
  parts.push(frameName);
@@ -20834,7 +21102,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
20834
21102
  * Creates a filename for markdown report based on memoization key components.
20835
21103
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20836
21104
  */
20837
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
21105
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20838
21106
  const parts = [symbol, strategyName, exchangeName];
20839
21107
  if (frameName) {
20840
21108
  parts.push(frameName);
@@ -20845,12 +21113,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
20845
21113
  return `${parts.join("_")}-${timestamp}.md`;
20846
21114
  };
20847
21115
  /** Maximum number of events to store in risk reports */
20848
- const MAX_EVENTS = 250;
21116
+ const MAX_EVENTS$1 = 250;
20849
21117
  /**
20850
21118
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
20851
21119
  * Maintains a chronological list of rejected signals due to risk limits.
20852
21120
  */
20853
- class ReportStorage {
21121
+ let ReportStorage$1 = class ReportStorage {
20854
21122
  constructor(symbol, strategyName, exchangeName, frameName) {
20855
21123
  this.symbol = symbol;
20856
21124
  this.strategyName = strategyName;
@@ -20867,7 +21135,7 @@ class ReportStorage {
20867
21135
  addRejectionEvent(event) {
20868
21136
  this._eventList.unshift(event);
20869
21137
  // Trim queue if exceeded MAX_EVENTS
20870
- if (this._eventList.length > MAX_EVENTS) {
21138
+ if (this._eventList.length > MAX_EVENTS$1) {
20871
21139
  this._eventList.pop();
20872
21140
  }
20873
21141
  }
@@ -20951,7 +21219,7 @@ class ReportStorage {
20951
21219
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
20952
21220
  const markdown = await this.getReport(symbol, strategyName, columns);
20953
21221
  const timestamp = Date.now();
20954
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21222
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20955
21223
  await Markdown.writeData("risk", markdown, {
20956
21224
  path,
20957
21225
  file: filename,
@@ -20962,7 +21230,7 @@ class ReportStorage {
20962
21230
  frameName: this.frameName
20963
21231
  });
20964
21232
  }
20965
- }
21233
+ };
20966
21234
  /**
20967
21235
  * Service for generating and saving risk rejection markdown reports.
20968
21236
  *
@@ -20992,7 +21260,7 @@ class RiskMarkdownService {
20992
21260
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20993
21261
  * Each combination gets its own isolated storage instance.
20994
21262
  */
20995
- this.getStorage = 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));
21263
+ this.getStorage = 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));
20996
21264
  /**
20997
21265
  * Subscribes to risk rejection emitter to receive rejection events.
20998
21266
  * Protected against multiple subscriptions.
@@ -21181,7 +21449,7 @@ class RiskMarkdownService {
21181
21449
  payload,
21182
21450
  });
21183
21451
  if (payload) {
21184
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21452
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21185
21453
  this.getStorage.clear(key);
21186
21454
  }
21187
21455
  else {
@@ -21465,6 +21733,7 @@ class ReportDummy {
21465
21733
  */
21466
21734
  const WILDCARD_TARGET = {
21467
21735
  backtest: true,
21736
+ strategy: true,
21468
21737
  breakeven: true,
21469
21738
  heat: true,
21470
21739
  live: true,
@@ -21510,7 +21779,7 @@ class ReportUtils {
21510
21779
  *
21511
21780
  * @returns Cleanup function that unsubscribes from all enabled services
21512
21781
  */
21513
- this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
21782
+ 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) => {
21514
21783
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
21515
21784
  backtest: bt$1,
21516
21785
  breakeven,
@@ -21521,6 +21790,7 @@ class ReportUtils {
21521
21790
  risk,
21522
21791
  schedule,
21523
21792
  walker,
21793
+ strategy,
21524
21794
  });
21525
21795
  const unList = [];
21526
21796
  if (bt$1) {
@@ -21550,6 +21820,9 @@ class ReportUtils {
21550
21820
  if (walker) {
21551
21821
  unList.push(bt.walkerReportService.subscribe());
21552
21822
  }
21823
+ if (strategy) {
21824
+ unList.push(bt.scheduleReportService.subscribe());
21825
+ }
21553
21826
  return compose(...unList.map((un) => () => void un()));
21554
21827
  };
21555
21828
  /**
@@ -21588,7 +21861,7 @@ class ReportUtils {
21588
21861
  * Report.disable();
21589
21862
  * ```
21590
21863
  */
21591
- this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
21864
+ 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) => {
21592
21865
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
21593
21866
  backtest: bt$1,
21594
21867
  breakeven,
@@ -21599,6 +21872,7 @@ class ReportUtils {
21599
21872
  risk,
21600
21873
  schedule,
21601
21874
  walker,
21875
+ strategy,
21602
21876
  });
21603
21877
  if (bt$1) {
21604
21878
  bt.backtestReportService.unsubscribe();
@@ -21627,6 +21901,9 @@ class ReportUtils {
21627
21901
  if (walker) {
21628
21902
  bt.walkerReportService.unsubscribe();
21629
21903
  }
21904
+ if (strategy) {
21905
+ bt.strategyReportService.unsubscribe();
21906
+ }
21630
21907
  };
21631
21908
  }
21632
21909
  }
@@ -23062,22 +23339,1243 @@ class RiskReportService {
23062
23339
  }
23063
23340
  }
23064
23341
 
23065
- {
23066
- provide(TYPES.loggerService, () => new LoggerService());
23067
- }
23068
- {
23069
- provide(TYPES.executionContextService, () => new ExecutionContextService());
23070
- provide(TYPES.methodContextService, () => new MethodContextService());
23071
- }
23072
- {
23073
- provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
23074
- provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
23075
- provide(TYPES.frameConnectionService, () => new FrameConnectionService());
23076
- provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
23077
- provide(TYPES.riskConnectionService, () => new RiskConnectionService());
23078
- provide(TYPES.actionConnectionService, () => new ActionConnectionService());
23079
- provide(TYPES.partialConnectionService, () => new PartialConnectionService());
23080
- provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23342
+ /**
23343
+ * Extracts execution context timestamp for strategy event logging.
23344
+ *
23345
+ * @param self - The StrategyReportService instance to extract context from
23346
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23347
+ * @internal
23348
+ */
23349
+ const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
23350
+ if (ExecutionContextService.hasContext()) {
23351
+ const { when } = self.executionContextService.context;
23352
+ return { when: when.toISOString() };
23353
+ }
23354
+ return {
23355
+ when: "",
23356
+ };
23357
+ };
23358
+ /**
23359
+ * Service for persisting strategy management events to JSON report files.
23360
+ *
23361
+ * Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
23362
+ * partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
23363
+ * the Report class. Each event is written as a separate JSON record.
23364
+ *
23365
+ * Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
23366
+ * this service writes each event immediately to disk for audit trail purposes.
23367
+ *
23368
+ * Lifecycle:
23369
+ * - Call subscribe() to enable event logging
23370
+ * - Events are written via Report.writeData() with "strategy" category
23371
+ * - Call unsubscribe() to disable event logging
23372
+ *
23373
+ * @example
23374
+ * ```typescript
23375
+ * // Service is typically used internally by strategy management classes
23376
+ * strategyReportService.subscribe();
23377
+ *
23378
+ * // Events are logged automatically when strategy actions occur
23379
+ * await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
23380
+ * strategyName: "my-strategy",
23381
+ * exchangeName: "binance",
23382
+ * frameName: "1h"
23383
+ * });
23384
+ *
23385
+ * strategyReportService.unsubscribe();
23386
+ * ```
23387
+ *
23388
+ * @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
23389
+ * @see Report for the underlying persistence mechanism
23390
+ */
23391
+ class StrategyReportService {
23392
+ constructor() {
23393
+ this.loggerService = inject(TYPES.loggerService);
23394
+ this.executionContextService = inject(TYPES.executionContextService);
23395
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
23396
+ /**
23397
+ * Logs a cancel-scheduled event when a scheduled signal is cancelled.
23398
+ *
23399
+ * Retrieves the scheduled signal from StrategyCoreService and writes
23400
+ * the cancellation event to the report file.
23401
+ *
23402
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23403
+ * @param isBacktest - Whether this is a backtest or live trading event
23404
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23405
+ * @param cancelId - Optional identifier for the cancellation reason
23406
+ */
23407
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
23408
+ this.loggerService.log("strategyReportService cancelScheduled", {
23409
+ symbol,
23410
+ isBacktest,
23411
+ cancelId,
23412
+ });
23413
+ if (!this.subscribe.hasValue()) {
23414
+ return;
23415
+ }
23416
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23417
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
23418
+ exchangeName: context.exchangeName,
23419
+ strategyName: context.strategyName,
23420
+ frameName: context.frameName,
23421
+ });
23422
+ if (!scheduledRow) {
23423
+ return;
23424
+ }
23425
+ await Report.writeData("strategy", {
23426
+ action: "cancel-scheduled",
23427
+ cancelId,
23428
+ symbol,
23429
+ createdAt,
23430
+ }, {
23431
+ signalId: scheduledRow.id,
23432
+ exchangeName: context.exchangeName,
23433
+ frameName: context.frameName,
23434
+ strategyName: context.strategyName,
23435
+ symbol,
23436
+ walkerName: "",
23437
+ });
23438
+ };
23439
+ /**
23440
+ * Logs a close-pending event when a pending signal is closed.
23441
+ *
23442
+ * Retrieves the pending signal from StrategyCoreService and writes
23443
+ * the close event to the report file.
23444
+ *
23445
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23446
+ * @param isBacktest - Whether this is a backtest or live trading event
23447
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23448
+ * @param closeId - Optional identifier for the close reason
23449
+ */
23450
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
23451
+ this.loggerService.log("strategyReportService closePending", {
23452
+ symbol,
23453
+ isBacktest,
23454
+ closeId,
23455
+ });
23456
+ if (!this.subscribe.hasValue()) {
23457
+ return;
23458
+ }
23459
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23460
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23461
+ exchangeName: context.exchangeName,
23462
+ strategyName: context.strategyName,
23463
+ frameName: context.frameName,
23464
+ });
23465
+ if (!pendingRow) {
23466
+ return;
23467
+ }
23468
+ await Report.writeData("strategy", {
23469
+ action: "close-pending",
23470
+ closeId,
23471
+ symbol,
23472
+ createdAt,
23473
+ }, {
23474
+ signalId: pendingRow.id,
23475
+ exchangeName: context.exchangeName,
23476
+ frameName: context.frameName,
23477
+ strategyName: context.strategyName,
23478
+ symbol,
23479
+ walkerName: "",
23480
+ });
23481
+ };
23482
+ /**
23483
+ * Logs a partial-profit event when a portion of the position is closed at profit.
23484
+ *
23485
+ * Records the percentage closed and current price when partial profit-taking occurs.
23486
+ *
23487
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23488
+ * @param percentToClose - Percentage of position to close (0-100)
23489
+ * @param currentPrice - Current market price at time of partial close
23490
+ * @param isBacktest - Whether this is a backtest or live trading event
23491
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23492
+ */
23493
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23494
+ this.loggerService.log("strategyReportService partialProfit", {
23495
+ symbol,
23496
+ percentToClose,
23497
+ currentPrice,
23498
+ isBacktest,
23499
+ });
23500
+ if (!this.subscribe.hasValue()) {
23501
+ return;
23502
+ }
23503
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23504
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23505
+ exchangeName: context.exchangeName,
23506
+ strategyName: context.strategyName,
23507
+ frameName: context.frameName,
23508
+ });
23509
+ if (!pendingRow) {
23510
+ return;
23511
+ }
23512
+ await Report.writeData("strategy", {
23513
+ action: "partial-profit",
23514
+ percentToClose,
23515
+ currentPrice,
23516
+ symbol,
23517
+ createdAt,
23518
+ }, {
23519
+ signalId: pendingRow.id,
23520
+ exchangeName: context.exchangeName,
23521
+ frameName: context.frameName,
23522
+ strategyName: context.strategyName,
23523
+ symbol,
23524
+ walkerName: "",
23525
+ });
23526
+ };
23527
+ /**
23528
+ * Logs a partial-loss event when a portion of the position is closed at loss.
23529
+ *
23530
+ * Records the percentage closed and current price when partial loss-cutting occurs.
23531
+ *
23532
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23533
+ * @param percentToClose - Percentage of position to close (0-100)
23534
+ * @param currentPrice - Current market price at time of partial close
23535
+ * @param isBacktest - Whether this is a backtest or live trading event
23536
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23537
+ */
23538
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23539
+ this.loggerService.log("strategyReportService partialLoss", {
23540
+ symbol,
23541
+ percentToClose,
23542
+ currentPrice,
23543
+ isBacktest,
23544
+ });
23545
+ if (!this.subscribe.hasValue()) {
23546
+ return;
23547
+ }
23548
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23549
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23550
+ exchangeName: context.exchangeName,
23551
+ strategyName: context.strategyName,
23552
+ frameName: context.frameName,
23553
+ });
23554
+ if (!pendingRow) {
23555
+ return;
23556
+ }
23557
+ await Report.writeData("strategy", {
23558
+ action: "partial-loss",
23559
+ percentToClose,
23560
+ currentPrice,
23561
+ symbol,
23562
+ createdAt,
23563
+ }, {
23564
+ signalId: pendingRow.id,
23565
+ exchangeName: context.exchangeName,
23566
+ frameName: context.frameName,
23567
+ strategyName: context.strategyName,
23568
+ symbol,
23569
+ walkerName: "",
23570
+ });
23571
+ };
23572
+ /**
23573
+ * Logs a trailing-stop event when the stop-loss is adjusted.
23574
+ *
23575
+ * Records the percentage shift and current price when trailing stop moves.
23576
+ *
23577
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23578
+ * @param percentShift - Percentage the stop-loss was shifted
23579
+ * @param currentPrice - Current market price at time of adjustment
23580
+ * @param isBacktest - Whether this is a backtest or live trading event
23581
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23582
+ */
23583
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23584
+ this.loggerService.log("strategyReportService trailingStop", {
23585
+ symbol,
23586
+ percentShift,
23587
+ currentPrice,
23588
+ isBacktest,
23589
+ });
23590
+ if (!this.subscribe.hasValue()) {
23591
+ return;
23592
+ }
23593
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23594
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23595
+ exchangeName: context.exchangeName,
23596
+ strategyName: context.strategyName,
23597
+ frameName: context.frameName,
23598
+ });
23599
+ if (!pendingRow) {
23600
+ return;
23601
+ }
23602
+ await Report.writeData("strategy", {
23603
+ action: "trailing-stop",
23604
+ percentShift,
23605
+ currentPrice,
23606
+ symbol,
23607
+ createdAt,
23608
+ }, {
23609
+ signalId: pendingRow.id,
23610
+ exchangeName: context.exchangeName,
23611
+ frameName: context.frameName,
23612
+ strategyName: context.strategyName,
23613
+ symbol,
23614
+ walkerName: "",
23615
+ });
23616
+ };
23617
+ /**
23618
+ * Logs a trailing-take event when the take-profit is adjusted.
23619
+ *
23620
+ * Records the percentage shift and current price when trailing take-profit moves.
23621
+ *
23622
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23623
+ * @param percentShift - Percentage the take-profit was shifted
23624
+ * @param currentPrice - Current market price at time of adjustment
23625
+ * @param isBacktest - Whether this is a backtest or live trading event
23626
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23627
+ */
23628
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23629
+ this.loggerService.log("strategyReportService trailingTake", {
23630
+ symbol,
23631
+ percentShift,
23632
+ currentPrice,
23633
+ isBacktest,
23634
+ });
23635
+ if (!this.subscribe.hasValue()) {
23636
+ return;
23637
+ }
23638
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23639
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23640
+ exchangeName: context.exchangeName,
23641
+ strategyName: context.strategyName,
23642
+ frameName: context.frameName,
23643
+ });
23644
+ if (!pendingRow) {
23645
+ return;
23646
+ }
23647
+ await Report.writeData("strategy", {
23648
+ action: "trailing-take",
23649
+ percentShift,
23650
+ currentPrice,
23651
+ symbol,
23652
+ createdAt,
23653
+ }, {
23654
+ signalId: pendingRow.id,
23655
+ exchangeName: context.exchangeName,
23656
+ frameName: context.frameName,
23657
+ strategyName: context.strategyName,
23658
+ symbol,
23659
+ walkerName: "",
23660
+ });
23661
+ };
23662
+ /**
23663
+ * Logs a breakeven event when the stop-loss is moved to entry price.
23664
+ *
23665
+ * Records the current price when breakeven protection is activated.
23666
+ *
23667
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23668
+ * @param currentPrice - Current market price at time of breakeven activation
23669
+ * @param isBacktest - Whether this is a backtest or live trading event
23670
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23671
+ */
23672
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
23673
+ this.loggerService.log("strategyReportService breakeven", {
23674
+ symbol,
23675
+ currentPrice,
23676
+ isBacktest,
23677
+ });
23678
+ if (!this.subscribe.hasValue()) {
23679
+ return;
23680
+ }
23681
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23682
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23683
+ exchangeName: context.exchangeName,
23684
+ strategyName: context.strategyName,
23685
+ frameName: context.frameName,
23686
+ });
23687
+ if (!pendingRow) {
23688
+ return;
23689
+ }
23690
+ await Report.writeData("strategy", {
23691
+ action: "breakeven",
23692
+ currentPrice,
23693
+ symbol,
23694
+ createdAt,
23695
+ }, {
23696
+ signalId: pendingRow.id,
23697
+ exchangeName: context.exchangeName,
23698
+ frameName: context.frameName,
23699
+ strategyName: context.strategyName,
23700
+ symbol,
23701
+ walkerName: "",
23702
+ });
23703
+ };
23704
+ /**
23705
+ * Initializes the service for event logging.
23706
+ *
23707
+ * Must be called before any events can be logged. Uses singleshot pattern
23708
+ * to ensure only one subscription exists at a time.
23709
+ *
23710
+ * @returns Cleanup function that clears the subscription when called
23711
+ */
23712
+ this.subscribe = singleshot(() => {
23713
+ this.loggerService.log("strategyReportService subscribe");
23714
+ const unCancelSchedule = strategyCommitSubject
23715
+ .filter(({ action }) => action === "cancel-scheduled")
23716
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
23717
+ exchangeName: event.exchangeName,
23718
+ frameName: event.frameName,
23719
+ strategyName: event.strategyName,
23720
+ }, event.cancelId));
23721
+ const unClosePending = strategyCommitSubject
23722
+ .filter(({ action }) => action === "close-pending")
23723
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
23724
+ exchangeName: event.exchangeName,
23725
+ frameName: event.frameName,
23726
+ strategyName: event.strategyName,
23727
+ }, event.closeId));
23728
+ const unPartialProfit = strategyCommitSubject
23729
+ .filter(({ action }) => action === "partial-profit")
23730
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23731
+ exchangeName: event.exchangeName,
23732
+ frameName: event.frameName,
23733
+ strategyName: event.strategyName,
23734
+ }));
23735
+ const unPartialLoss = strategyCommitSubject
23736
+ .filter(({ action }) => action === "partial-loss")
23737
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23738
+ exchangeName: event.exchangeName,
23739
+ frameName: event.frameName,
23740
+ strategyName: event.strategyName,
23741
+ }));
23742
+ const unTrailingStop = strategyCommitSubject
23743
+ .filter(({ action }) => action === "trailing-stop")
23744
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23745
+ exchangeName: event.exchangeName,
23746
+ frameName: event.frameName,
23747
+ strategyName: event.strategyName,
23748
+ }));
23749
+ const unTrailingTake = strategyCommitSubject
23750
+ .filter(({ action }) => action === "trailing-take")
23751
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23752
+ exchangeName: event.exchangeName,
23753
+ frameName: event.frameName,
23754
+ strategyName: event.strategyName,
23755
+ }));
23756
+ const unBreakeven = strategyCommitSubject
23757
+ .filter(({ action }) => action === "breakeven")
23758
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
23759
+ exchangeName: event.exchangeName,
23760
+ frameName: event.frameName,
23761
+ strategyName: event.strategyName,
23762
+ }));
23763
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
23764
+ return () => {
23765
+ disposeFn();
23766
+ this.subscribe.clear();
23767
+ };
23768
+ });
23769
+ /**
23770
+ * Stops event logging and cleans up the subscription.
23771
+ *
23772
+ * Safe to call multiple times - only clears if subscription exists.
23773
+ */
23774
+ this.unsubscribe = async () => {
23775
+ this.loggerService.log("strategyReportService unsubscribe");
23776
+ if (this.subscribe.hasValue()) {
23777
+ const lastSubscription = this.subscribe();
23778
+ lastSubscription();
23779
+ }
23780
+ };
23781
+ }
23782
+ }
23783
+
23784
+ /**
23785
+ * Extracts execution context timestamp for strategy event logging.
23786
+ *
23787
+ * @param self - The StrategyMarkdownService instance to extract context from
23788
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23789
+ * @internal
23790
+ */
23791
+ const GET_EXECUTION_CONTEXT_FN = (self) => {
23792
+ if (ExecutionContextService.hasContext()) {
23793
+ const { when } = self.executionContextService.context;
23794
+ return { when: when.toISOString() };
23795
+ }
23796
+ return {
23797
+ when: "",
23798
+ };
23799
+ };
23800
+ /**
23801
+ * Creates a unique key for memoizing ReportStorage instances.
23802
+ *
23803
+ * Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
23804
+ *
23805
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23806
+ * @param strategyName - Name of the trading strategy
23807
+ * @param exchangeName - Name of the exchange
23808
+ * @param frameName - Timeframe name (optional, included if present)
23809
+ * @param backtest - Whether this is backtest or live mode
23810
+ * @returns Colon-separated key string for memoization
23811
+ * @internal
23812
+ */
23813
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23814
+ const parts = [symbol, strategyName, exchangeName];
23815
+ if (frameName)
23816
+ parts.push(frameName);
23817
+ parts.push(backtest ? "backtest" : "live");
23818
+ return parts.join(":");
23819
+ };
23820
+ /**
23821
+ * Creates a filename for markdown report output.
23822
+ *
23823
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
23824
+ *
23825
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23826
+ * @param strategyName - Name of the trading strategy
23827
+ * @param exchangeName - Name of the exchange
23828
+ * @param frameName - Timeframe name (indicates backtest mode if present)
23829
+ * @param timestamp - Unix timestamp in milliseconds for uniqueness
23830
+ * @returns Underscore-separated filename with .md extension
23831
+ * @internal
23832
+ */
23833
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23834
+ const parts = [symbol, strategyName, exchangeName];
23835
+ if (frameName) {
23836
+ parts.push(frameName);
23837
+ parts.push("backtest");
23838
+ }
23839
+ else
23840
+ parts.push("live");
23841
+ return `${parts.join("_")}-${timestamp}.md`;
23842
+ };
23843
+ /**
23844
+ * Maximum number of events to store per symbol-strategy pair.
23845
+ * Older events are discarded when this limit is exceeded.
23846
+ * @internal
23847
+ */
23848
+ const MAX_EVENTS = 250;
23849
+ /**
23850
+ * In-memory storage for accumulating strategy events per symbol-strategy pair.
23851
+ *
23852
+ * Maintains a rolling window of the most recent events (up to MAX_EVENTS),
23853
+ * with newer events added to the front of the list. Provides methods to:
23854
+ * - Add new events (FIFO queue with max size)
23855
+ * - Retrieve aggregated statistics
23856
+ * - Generate markdown reports
23857
+ * - Dump reports to disk
23858
+ *
23859
+ * @internal
23860
+ */
23861
+ class ReportStorage {
23862
+ /**
23863
+ * Creates a new ReportStorage instance.
23864
+ *
23865
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23866
+ * @param strategyName - Name of the trading strategy
23867
+ * @param exchangeName - Name of the exchange
23868
+ * @param frameName - Timeframe name for backtest identification
23869
+ */
23870
+ constructor(symbol, strategyName, exchangeName, frameName) {
23871
+ this.symbol = symbol;
23872
+ this.strategyName = strategyName;
23873
+ this.exchangeName = exchangeName;
23874
+ this.frameName = frameName;
23875
+ this._eventList = [];
23876
+ }
23877
+ /**
23878
+ * Adds a new event to the storage.
23879
+ *
23880
+ * Events are added to the front of the list (most recent first).
23881
+ * If the list exceeds MAX_EVENTS, the oldest event is removed.
23882
+ *
23883
+ * @param event - The strategy event to store
23884
+ */
23885
+ addEvent(event) {
23886
+ this._eventList.unshift(event);
23887
+ if (this._eventList.length > MAX_EVENTS) {
23888
+ this._eventList.pop();
23889
+ }
23890
+ }
23891
+ /**
23892
+ * Retrieves aggregated statistics from stored events.
23893
+ *
23894
+ * Calculates counts for each action type from the event list.
23895
+ *
23896
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
23897
+ */
23898
+ async getData() {
23899
+ if (this._eventList.length === 0) {
23900
+ return {
23901
+ eventList: [],
23902
+ totalEvents: 0,
23903
+ cancelScheduledCount: 0,
23904
+ closePendingCount: 0,
23905
+ partialProfitCount: 0,
23906
+ partialLossCount: 0,
23907
+ trailingStopCount: 0,
23908
+ trailingTakeCount: 0,
23909
+ breakevenCount: 0,
23910
+ };
23911
+ }
23912
+ return {
23913
+ eventList: this._eventList,
23914
+ totalEvents: this._eventList.length,
23915
+ cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
23916
+ closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
23917
+ partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
23918
+ partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
23919
+ trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
23920
+ trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
23921
+ breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
23922
+ };
23923
+ }
23924
+ /**
23925
+ * Generates a markdown report from stored events.
23926
+ *
23927
+ * Creates a formatted markdown document containing:
23928
+ * - Header with symbol and strategy name
23929
+ * - Table of all events with configurable columns
23930
+ * - Summary statistics with counts by action type
23931
+ *
23932
+ * @param symbol - Trading pair symbol for report header
23933
+ * @param strategyName - Strategy name for report header
23934
+ * @param columns - Column configuration for the event table
23935
+ * @returns Promise resolving to formatted markdown string
23936
+ */
23937
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
23938
+ const stats = await this.getData();
23939
+ if (stats.totalEvents === 0) {
23940
+ return [
23941
+ `# Strategy Report: ${symbol}:${strategyName}`,
23942
+ "",
23943
+ "No strategy events recorded yet."
23944
+ ].join("\n");
23945
+ }
23946
+ const visibleColumns = [];
23947
+ for (const col of columns) {
23948
+ if (await col.isVisible()) {
23949
+ visibleColumns.push(col);
23950
+ }
23951
+ }
23952
+ const header = visibleColumns.map((col) => col.label);
23953
+ const separator = visibleColumns.map(() => "---");
23954
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
23955
+ const tableData = [header, separator, ...rows];
23956
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
23957
+ return [
23958
+ `# Strategy Report: ${symbol}:${strategyName}`,
23959
+ "",
23960
+ table,
23961
+ "",
23962
+ `**Total events:** ${stats.totalEvents}`,
23963
+ `- Cancel scheduled: ${stats.cancelScheduledCount}`,
23964
+ `- Close pending: ${stats.closePendingCount}`,
23965
+ `- Partial profit: ${stats.partialProfitCount}`,
23966
+ `- Partial loss: ${stats.partialLossCount}`,
23967
+ `- Trailing stop: ${stats.trailingStopCount}`,
23968
+ `- Trailing take: ${stats.trailingTakeCount}`,
23969
+ `- Breakeven: ${stats.breakevenCount}`,
23970
+ ].join("\n");
23971
+ }
23972
+ /**
23973
+ * Generates and saves a markdown report to disk.
23974
+ *
23975
+ * Creates the output directory if it doesn't exist and writes
23976
+ * the report with a timestamped filename.
23977
+ *
23978
+ * @param symbol - Trading pair symbol for report
23979
+ * @param strategyName - Strategy name for report
23980
+ * @param path - Output directory path (default: "./dump/strategy")
23981
+ * @param columns - Column configuration for the event table
23982
+ */
23983
+ async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
23984
+ const markdown = await this.getReport(symbol, strategyName, columns);
23985
+ const timestamp = Date.now();
23986
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23987
+ await Markdown.writeData("strategy", markdown, {
23988
+ path,
23989
+ file: filename,
23990
+ symbol: this.symbol,
23991
+ strategyName: this.strategyName,
23992
+ exchangeName: this.exchangeName,
23993
+ signalId: "",
23994
+ frameName: this.frameName
23995
+ });
23996
+ }
23997
+ }
23998
+ /**
23999
+ * Service for accumulating strategy management events and generating markdown reports.
24000
+ *
24001
+ * Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
24002
+ * partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
24003
+ * methods to retrieve statistics, generate reports, and export to files.
24004
+ *
24005
+ * Unlike StrategyReportService which writes each event to disk immediately,
24006
+ * this service accumulates events in ReportStorage instances (max 250 per
24007
+ * symbol-strategy pair) for batch reporting.
24008
+ *
24009
+ * Features:
24010
+ * - In-memory event accumulation with memoized storage per symbol-strategy pair
24011
+ * - Statistical data extraction (event counts by action type)
24012
+ * - Markdown report generation with configurable columns
24013
+ * - File export with timestamped filenames
24014
+ * - Selective or full cache clearing
24015
+ *
24016
+ * Lifecycle:
24017
+ * - Call subscribe() to enable event collection
24018
+ * - Events are collected automatically via cancelScheduled, closePending, etc.
24019
+ * - Use getData(), getReport(), or dump() to retrieve accumulated data
24020
+ * - Call unsubscribe() to disable collection and clear all data
24021
+ *
24022
+ * @example
24023
+ * ```typescript
24024
+ * strategyMarkdownService.subscribe();
24025
+ *
24026
+ * // Events are collected automatically during strategy execution
24027
+ * // ...
24028
+ *
24029
+ * // Get statistics
24030
+ * const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
24031
+ *
24032
+ * // Generate markdown report
24033
+ * const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
24034
+ *
24035
+ * // Export to file
24036
+ * await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
24037
+ *
24038
+ * strategyMarkdownService.unsubscribe();
24039
+ * ```
24040
+ *
24041
+ * @see StrategyReportService for immediate event persistence to JSON files
24042
+ * @see Strategy for the high-level utility class that wraps this service
24043
+ */
24044
+ class StrategyMarkdownService {
24045
+ constructor() {
24046
+ this.loggerService = inject(TYPES.loggerService);
24047
+ this.executionContextService = inject(TYPES.executionContextService);
24048
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
24049
+ /**
24050
+ * Memoized factory for ReportStorage instances.
24051
+ *
24052
+ * Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
24053
+ * Uses CREATE_KEY_FN for cache key generation.
24054
+ *
24055
+ * @internal
24056
+ */
24057
+ this.getStorage = 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));
24058
+ /**
24059
+ * Records a cancel-scheduled event when a scheduled signal is cancelled.
24060
+ *
24061
+ * Retrieves the scheduled signal from StrategyCoreService and stores
24062
+ * the cancellation event in the appropriate ReportStorage.
24063
+ *
24064
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24065
+ * @param isBacktest - Whether this is a backtest or live trading event
24066
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24067
+ * @param cancelId - Optional identifier for the cancellation reason
24068
+ */
24069
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
24070
+ this.loggerService.log("strategyMarkdownService cancelScheduled", {
24071
+ symbol,
24072
+ isBacktest,
24073
+ cancelId,
24074
+ });
24075
+ if (!this.subscribe.hasValue()) {
24076
+ return;
24077
+ }
24078
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24079
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24080
+ exchangeName: context.exchangeName,
24081
+ strategyName: context.strategyName,
24082
+ frameName: context.frameName,
24083
+ });
24084
+ if (!scheduledRow) {
24085
+ return;
24086
+ }
24087
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24088
+ storage.addEvent({
24089
+ timestamp: Date.now(),
24090
+ symbol,
24091
+ strategyName: context.strategyName,
24092
+ exchangeName: context.exchangeName,
24093
+ frameName: context.frameName,
24094
+ signalId: scheduledRow.id,
24095
+ action: "cancel-scheduled",
24096
+ cancelId,
24097
+ createdAt,
24098
+ backtest: isBacktest,
24099
+ });
24100
+ };
24101
+ /**
24102
+ * Records a close-pending event when a pending signal is closed.
24103
+ *
24104
+ * Retrieves the pending signal from StrategyCoreService and stores
24105
+ * the close event in the appropriate ReportStorage.
24106
+ *
24107
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24108
+ * @param isBacktest - Whether this is a backtest or live trading event
24109
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24110
+ * @param closeId - Optional identifier for the close reason
24111
+ */
24112
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
24113
+ this.loggerService.log("strategyMarkdownService closePending", {
24114
+ symbol,
24115
+ isBacktest,
24116
+ closeId,
24117
+ });
24118
+ if (!this.subscribe.hasValue()) {
24119
+ return;
24120
+ }
24121
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24122
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24123
+ exchangeName: context.exchangeName,
24124
+ strategyName: context.strategyName,
24125
+ frameName: context.frameName,
24126
+ });
24127
+ if (!pendingRow) {
24128
+ return;
24129
+ }
24130
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24131
+ storage.addEvent({
24132
+ timestamp: Date.now(),
24133
+ symbol,
24134
+ strategyName: context.strategyName,
24135
+ exchangeName: context.exchangeName,
24136
+ frameName: context.frameName,
24137
+ signalId: pendingRow.id,
24138
+ action: "close-pending",
24139
+ closeId,
24140
+ createdAt,
24141
+ backtest: isBacktest,
24142
+ });
24143
+ };
24144
+ /**
24145
+ * Records a partial-profit event when a portion of the position is closed at profit.
24146
+ *
24147
+ * Stores the percentage closed and current price when partial profit-taking occurs.
24148
+ *
24149
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24150
+ * @param percentToClose - Percentage of position to close (0-100)
24151
+ * @param currentPrice - Current market price at time of partial close
24152
+ * @param isBacktest - Whether this is a backtest or live trading event
24153
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24154
+ */
24155
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24156
+ this.loggerService.log("strategyMarkdownService partialProfit", {
24157
+ symbol,
24158
+ percentToClose,
24159
+ currentPrice,
24160
+ isBacktest,
24161
+ });
24162
+ if (!this.subscribe.hasValue()) {
24163
+ return;
24164
+ }
24165
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24166
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24167
+ exchangeName: context.exchangeName,
24168
+ strategyName: context.strategyName,
24169
+ frameName: context.frameName,
24170
+ });
24171
+ if (!pendingRow) {
24172
+ return;
24173
+ }
24174
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24175
+ storage.addEvent({
24176
+ timestamp: Date.now(),
24177
+ symbol,
24178
+ strategyName: context.strategyName,
24179
+ exchangeName: context.exchangeName,
24180
+ frameName: context.frameName,
24181
+ signalId: pendingRow.id,
24182
+ action: "partial-profit",
24183
+ percentToClose,
24184
+ currentPrice,
24185
+ createdAt,
24186
+ backtest: isBacktest,
24187
+ });
24188
+ };
24189
+ /**
24190
+ * Records a partial-loss event when a portion of the position is closed at loss.
24191
+ *
24192
+ * Stores the percentage closed and current price when partial loss-cutting occurs.
24193
+ *
24194
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24195
+ * @param percentToClose - Percentage of position to close (0-100)
24196
+ * @param currentPrice - Current market price at time of partial close
24197
+ * @param isBacktest - Whether this is a backtest or live trading event
24198
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24199
+ */
24200
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24201
+ this.loggerService.log("strategyMarkdownService partialLoss", {
24202
+ symbol,
24203
+ percentToClose,
24204
+ currentPrice,
24205
+ isBacktest,
24206
+ });
24207
+ if (!this.subscribe.hasValue()) {
24208
+ return;
24209
+ }
24210
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24211
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24212
+ exchangeName: context.exchangeName,
24213
+ strategyName: context.strategyName,
24214
+ frameName: context.frameName,
24215
+ });
24216
+ if (!pendingRow) {
24217
+ return;
24218
+ }
24219
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24220
+ storage.addEvent({
24221
+ timestamp: Date.now(),
24222
+ symbol,
24223
+ strategyName: context.strategyName,
24224
+ exchangeName: context.exchangeName,
24225
+ frameName: context.frameName,
24226
+ signalId: pendingRow.id,
24227
+ action: "partial-loss",
24228
+ percentToClose,
24229
+ currentPrice,
24230
+ createdAt,
24231
+ backtest: isBacktest,
24232
+ });
24233
+ };
24234
+ /**
24235
+ * Records a trailing-stop event when the stop-loss is adjusted.
24236
+ *
24237
+ * Stores the percentage shift and current price when trailing stop moves.
24238
+ *
24239
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24240
+ * @param percentShift - Percentage the stop-loss was shifted
24241
+ * @param currentPrice - Current market price at time of adjustment
24242
+ * @param isBacktest - Whether this is a backtest or live trading event
24243
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24244
+ */
24245
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24246
+ this.loggerService.log("strategyMarkdownService trailingStop", {
24247
+ symbol,
24248
+ percentShift,
24249
+ currentPrice,
24250
+ isBacktest,
24251
+ });
24252
+ if (!this.subscribe.hasValue()) {
24253
+ return;
24254
+ }
24255
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24256
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24257
+ exchangeName: context.exchangeName,
24258
+ strategyName: context.strategyName,
24259
+ frameName: context.frameName,
24260
+ });
24261
+ if (!pendingRow) {
24262
+ return;
24263
+ }
24264
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24265
+ storage.addEvent({
24266
+ timestamp: Date.now(),
24267
+ symbol,
24268
+ strategyName: context.strategyName,
24269
+ exchangeName: context.exchangeName,
24270
+ frameName: context.frameName,
24271
+ signalId: pendingRow.id,
24272
+ action: "trailing-stop",
24273
+ percentShift,
24274
+ currentPrice,
24275
+ createdAt,
24276
+ backtest: isBacktest,
24277
+ });
24278
+ };
24279
+ /**
24280
+ * Records a trailing-take event when the take-profit is adjusted.
24281
+ *
24282
+ * Stores the percentage shift and current price when trailing take-profit moves.
24283
+ *
24284
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24285
+ * @param percentShift - Percentage the take-profit was shifted
24286
+ * @param currentPrice - Current market price at time of adjustment
24287
+ * @param isBacktest - Whether this is a backtest or live trading event
24288
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24289
+ */
24290
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24291
+ this.loggerService.log("strategyMarkdownService trailingTake", {
24292
+ symbol,
24293
+ percentShift,
24294
+ currentPrice,
24295
+ isBacktest,
24296
+ });
24297
+ if (!this.subscribe.hasValue()) {
24298
+ return;
24299
+ }
24300
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24301
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24302
+ exchangeName: context.exchangeName,
24303
+ strategyName: context.strategyName,
24304
+ frameName: context.frameName,
24305
+ });
24306
+ if (!pendingRow) {
24307
+ return;
24308
+ }
24309
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24310
+ storage.addEvent({
24311
+ timestamp: Date.now(),
24312
+ symbol,
24313
+ strategyName: context.strategyName,
24314
+ exchangeName: context.exchangeName,
24315
+ frameName: context.frameName,
24316
+ signalId: pendingRow.id,
24317
+ action: "trailing-take",
24318
+ percentShift,
24319
+ currentPrice,
24320
+ createdAt,
24321
+ backtest: isBacktest,
24322
+ });
24323
+ };
24324
+ /**
24325
+ * Records a breakeven event when the stop-loss is moved to entry price.
24326
+ *
24327
+ * Stores the current price when breakeven protection is activated.
24328
+ *
24329
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24330
+ * @param currentPrice - Current market price at time of breakeven activation
24331
+ * @param isBacktest - Whether this is a backtest or live trading event
24332
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24333
+ */
24334
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
24335
+ this.loggerService.log("strategyMarkdownService breakeven", {
24336
+ symbol,
24337
+ currentPrice,
24338
+ isBacktest,
24339
+ });
24340
+ if (!this.subscribe.hasValue()) {
24341
+ return;
24342
+ }
24343
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24344
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24345
+ exchangeName: context.exchangeName,
24346
+ strategyName: context.strategyName,
24347
+ frameName: context.frameName,
24348
+ });
24349
+ if (!pendingRow) {
24350
+ return;
24351
+ }
24352
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24353
+ storage.addEvent({
24354
+ timestamp: Date.now(),
24355
+ symbol,
24356
+ strategyName: context.strategyName,
24357
+ exchangeName: context.exchangeName,
24358
+ frameName: context.frameName,
24359
+ signalId: pendingRow.id,
24360
+ action: "breakeven",
24361
+ currentPrice,
24362
+ createdAt,
24363
+ backtest: isBacktest,
24364
+ });
24365
+ };
24366
+ /**
24367
+ * Retrieves aggregated statistics from accumulated strategy events.
24368
+ *
24369
+ * Returns counts for each action type and the full event list from the
24370
+ * ReportStorage for the specified symbol-strategy pair.
24371
+ *
24372
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24373
+ * @param strategyName - Name of the trading strategy
24374
+ * @param exchangeName - Name of the exchange
24375
+ * @param frameName - Timeframe name for backtest identification
24376
+ * @param backtest - Whether to get backtest or live data
24377
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
24378
+ * @throws Error if service not initialized (subscribe() not called)
24379
+ */
24380
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
24381
+ this.loggerService.log("strategyMarkdownService getData", {
24382
+ symbol,
24383
+ strategyName,
24384
+ exchangeName,
24385
+ frameName,
24386
+ backtest,
24387
+ });
24388
+ if (!this.subscribe.hasValue()) {
24389
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
24390
+ }
24391
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24392
+ return storage.getData();
24393
+ };
24394
+ /**
24395
+ * Generates a markdown report from accumulated strategy events.
24396
+ *
24397
+ * Creates a formatted markdown document containing:
24398
+ * - Header with symbol and strategy name
24399
+ * - Table of all events with configurable columns
24400
+ * - Summary statistics with counts by action type
24401
+ *
24402
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24403
+ * @param strategyName - Name of the trading strategy
24404
+ * @param exchangeName - Name of the exchange
24405
+ * @param frameName - Timeframe name for backtest identification
24406
+ * @param backtest - Whether to get backtest or live data
24407
+ * @param columns - Column configuration for the event table
24408
+ * @returns Promise resolving to formatted markdown string
24409
+ * @throws Error if service not initialized (subscribe() not called)
24410
+ */
24411
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
24412
+ this.loggerService.log("strategyMarkdownService getReport", {
24413
+ symbol,
24414
+ strategyName,
24415
+ exchangeName,
24416
+ frameName,
24417
+ backtest,
24418
+ });
24419
+ if (!this.subscribe.hasValue()) {
24420
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
24421
+ }
24422
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24423
+ return storage.getReport(symbol, strategyName, columns);
24424
+ };
24425
+ /**
24426
+ * Generates and saves a markdown report to disk.
24427
+ *
24428
+ * Creates the output directory if it doesn't exist and writes
24429
+ * the report with a timestamped filename via Markdown.writeData().
24430
+ *
24431
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
24432
+ *
24433
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24434
+ * @param strategyName - Name of the trading strategy
24435
+ * @param exchangeName - Name of the exchange
24436
+ * @param frameName - Timeframe name for backtest identification
24437
+ * @param backtest - Whether to dump backtest or live data
24438
+ * @param path - Output directory path (default: "./dump/strategy")
24439
+ * @param columns - Column configuration for the event table
24440
+ * @returns Promise that resolves when file is written
24441
+ * @throws Error if service not initialized (subscribe() not called)
24442
+ */
24443
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
24444
+ this.loggerService.log("strategyMarkdownService dump", {
24445
+ symbol,
24446
+ strategyName,
24447
+ exchangeName,
24448
+ frameName,
24449
+ backtest,
24450
+ path,
24451
+ });
24452
+ if (!this.subscribe.hasValue()) {
24453
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
24454
+ }
24455
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24456
+ await storage.dump(symbol, strategyName, path, columns);
24457
+ };
24458
+ /**
24459
+ * Clears accumulated events from storage.
24460
+ *
24461
+ * Can clear either a specific symbol-strategy pair or all stored data.
24462
+ *
24463
+ * @param payload - Optional filter to clear specific storage. If omitted, clears all.
24464
+ * @param payload.symbol - Trading pair symbol
24465
+ * @param payload.strategyName - Strategy name
24466
+ * @param payload.exchangeName - Exchange name
24467
+ * @param payload.frameName - Frame name
24468
+ * @param payload.backtest - Backtest mode flag
24469
+ */
24470
+ this.clear = async (payload) => {
24471
+ this.loggerService.log("strategyMarkdownService clear", { payload });
24472
+ if (payload) {
24473
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24474
+ this.getStorage.clear(key);
24475
+ }
24476
+ else {
24477
+ this.getStorage.clear();
24478
+ }
24479
+ };
24480
+ /**
24481
+ * Initializes the service for event collection.
24482
+ *
24483
+ * Must be called before any events can be collected or reports generated.
24484
+ * Uses singleshot pattern to ensure only one subscription exists at a time.
24485
+ *
24486
+ * @returns Cleanup function that clears the subscription and all accumulated data
24487
+ */
24488
+ this.subscribe = singleshot(() => {
24489
+ this.loggerService.log("strategyMarkdownService subscribe");
24490
+ const unCancelSchedule = strategyCommitSubject
24491
+ .filter(({ action }) => action === "cancel-scheduled")
24492
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
24493
+ exchangeName: event.exchangeName,
24494
+ frameName: event.frameName,
24495
+ strategyName: event.strategyName,
24496
+ }, event.cancelId));
24497
+ const unClosePending = strategyCommitSubject
24498
+ .filter(({ action }) => action === "close-pending")
24499
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
24500
+ exchangeName: event.exchangeName,
24501
+ frameName: event.frameName,
24502
+ strategyName: event.strategyName,
24503
+ }, event.closeId));
24504
+ const unPartialProfit = strategyCommitSubject
24505
+ .filter(({ action }) => action === "partial-profit")
24506
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24507
+ exchangeName: event.exchangeName,
24508
+ frameName: event.frameName,
24509
+ strategyName: event.strategyName,
24510
+ }));
24511
+ const unPartialLoss = strategyCommitSubject
24512
+ .filter(({ action }) => action === "partial-loss")
24513
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24514
+ exchangeName: event.exchangeName,
24515
+ frameName: event.frameName,
24516
+ strategyName: event.strategyName,
24517
+ }));
24518
+ const unTrailingStop = strategyCommitSubject
24519
+ .filter(({ action }) => action === "trailing-stop")
24520
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24521
+ exchangeName: event.exchangeName,
24522
+ frameName: event.frameName,
24523
+ strategyName: event.strategyName,
24524
+ }));
24525
+ const unTrailingTake = strategyCommitSubject
24526
+ .filter(({ action }) => action === "trailing-take")
24527
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24528
+ exchangeName: event.exchangeName,
24529
+ frameName: event.frameName,
24530
+ strategyName: event.strategyName,
24531
+ }));
24532
+ const unBreakeven = strategyCommitSubject
24533
+ .filter(({ action }) => action === "breakeven")
24534
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24535
+ exchangeName: event.exchangeName,
24536
+ frameName: event.frameName,
24537
+ strategyName: event.strategyName,
24538
+ }));
24539
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24540
+ return () => {
24541
+ disposeFn();
24542
+ this.subscribe.clear();
24543
+ this.clear();
24544
+ };
24545
+ });
24546
+ /**
24547
+ * Stops event collection and clears all accumulated data.
24548
+ *
24549
+ * Invokes the cleanup function returned by subscribe(), which clears
24550
+ * both the subscription and all ReportStorage instances.
24551
+ * Safe to call multiple times - only acts if subscription exists.
24552
+ */
24553
+ this.unsubscribe = async () => {
24554
+ this.loggerService.log("strategyMarkdownService unsubscribe");
24555
+ if (this.subscribe.hasValue()) {
24556
+ const lastSubscription = this.subscribe();
24557
+ lastSubscription();
24558
+ }
24559
+ };
24560
+ }
24561
+ }
24562
+
24563
+ {
24564
+ provide(TYPES.loggerService, () => new LoggerService());
24565
+ }
24566
+ {
24567
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
24568
+ provide(TYPES.methodContextService, () => new MethodContextService());
24569
+ }
24570
+ {
24571
+ provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
24572
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
24573
+ provide(TYPES.frameConnectionService, () => new FrameConnectionService());
24574
+ provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
24575
+ provide(TYPES.riskConnectionService, () => new RiskConnectionService());
24576
+ provide(TYPES.actionConnectionService, () => new ActionConnectionService());
24577
+ provide(TYPES.partialConnectionService, () => new PartialConnectionService());
24578
+ provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23081
24579
  }
23082
24580
  {
23083
24581
  provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
@@ -23125,6 +24623,7 @@ class RiskReportService {
23125
24623
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
23126
24624
  provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
23127
24625
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
24626
+ provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
23128
24627
  }
23129
24628
  {
23130
24629
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -23136,6 +24635,7 @@ class RiskReportService {
23136
24635
  provide(TYPES.partialReportService, () => new PartialReportService());
23137
24636
  provide(TYPES.breakevenReportService, () => new BreakevenReportService());
23138
24637
  provide(TYPES.riskReportService, () => new RiskReportService());
24638
+ provide(TYPES.strategyReportService, () => new StrategyReportService());
23139
24639
  }
23140
24640
  {
23141
24641
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -23212,6 +24712,7 @@ const markdownServices = {
23212
24712
  partialMarkdownService: inject(TYPES.partialMarkdownService),
23213
24713
  breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
23214
24714
  riskMarkdownService: inject(TYPES.riskMarkdownService),
24715
+ strategyMarkdownService: inject(TYPES.strategyMarkdownService),
23215
24716
  };
23216
24717
  const reportServices = {
23217
24718
  backtestReportService: inject(TYPES.backtestReportService),
@@ -23223,6 +24724,7 @@ const reportServices = {
23223
24724
  partialReportService: inject(TYPES.partialReportService),
23224
24725
  breakevenReportService: inject(TYPES.breakevenReportService),
23225
24726
  riskReportService: inject(TYPES.riskReportService),
24727
+ strategyReportService: inject(TYPES.strategyReportService),
23226
24728
  };
23227
24729
  const validationServices = {
23228
24730
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -25301,6 +26803,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
25301
26803
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
25302
26804
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
25303
26805
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
26806
+ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
26807
+ const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
25304
26808
  /**
25305
26809
  * Subscribes to all signal events with queued async processing.
25306
26810
  *
@@ -26330,6 +27834,78 @@ function listenActivePingOnce(filterFn, fn) {
26330
27834
  bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
26331
27835
  return activePingSubject.filter(filterFn).once(fn);
26332
27836
  }
27837
+ /**
27838
+ * Subscribes to strategy management events with queued async processing.
27839
+ *
27840
+ * Emits when strategy management actions are executed:
27841
+ * - cancel-scheduled: Scheduled signal cancelled
27842
+ * - close-pending: Pending signal closed
27843
+ * - partial-profit: Partial close at profit level
27844
+ * - partial-loss: Partial close at loss level
27845
+ * - trailing-stop: Stop-loss adjusted
27846
+ * - trailing-take: Take-profit adjusted
27847
+ * - breakeven: Stop-loss moved to entry price
27848
+ *
27849
+ * Events are processed sequentially in order received, even if callback is async.
27850
+ * Uses queued wrapper to prevent concurrent execution of the callback.
27851
+ *
27852
+ * @param fn - Callback function to handle strategy commit events
27853
+ * @returns Unsubscribe function to stop listening
27854
+ *
27855
+ * @example
27856
+ * ```typescript
27857
+ * import { listenStrategyCommit } from "./function/event";
27858
+ *
27859
+ * const unsubscribe = listenStrategyCommit((event) => {
27860
+ * console.log(`[${event.action}] ${event.symbol}`);
27861
+ * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
27862
+ * if (event.action === "partial-profit") {
27863
+ * console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
27864
+ * }
27865
+ * });
27866
+ *
27867
+ * // Later: stop listening
27868
+ * unsubscribe();
27869
+ * ```
27870
+ */
27871
+ function listenStrategyCommit(fn) {
27872
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
27873
+ return strategyCommitSubject.subscribe(queued(async (event) => fn(event)));
27874
+ }
27875
+ /**
27876
+ * Subscribes to filtered strategy management events with one-time execution.
27877
+ *
27878
+ * Listens for events matching the filter predicate, then executes callback once
27879
+ * and automatically unsubscribes. Useful for waiting for specific strategy actions.
27880
+ *
27881
+ * @param filterFn - Predicate to filter which events trigger the callback
27882
+ * @param fn - Callback function to handle the filtered event (called only once)
27883
+ * @returns Unsubscribe function to cancel the listener before it fires
27884
+ *
27885
+ * @example
27886
+ * ```typescript
27887
+ * import { listenStrategyCommitOnce } from "./function/event";
27888
+ *
27889
+ * // Wait for first trailing stop adjustment
27890
+ * listenStrategyCommitOnce(
27891
+ * (event) => event.action === "trailing-stop",
27892
+ * (event) => console.log("Trailing stop adjusted:", event.symbol)
27893
+ * );
27894
+ *
27895
+ * // Wait for breakeven on BTCUSDT
27896
+ * const cancel = listenStrategyCommitOnce(
27897
+ * (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
27898
+ * (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
27899
+ * );
27900
+ *
27901
+ * // Cancel if needed before event fires
27902
+ * cancel();
27903
+ * ```
27904
+ */
27905
+ function listenStrategyCommitOnce(filterFn, fn) {
27906
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
27907
+ return strategyCommitSubject.filter(filterFn).once(fn);
27908
+ }
26333
27909
 
26334
27910
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
26335
27911
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -30775,6 +32351,7 @@ class NotificationInstance {
30775
32351
  priceTakeProfit: data.signal.priceTakeProfit,
30776
32352
  priceStopLoss: data.signal.priceStopLoss,
30777
32353
  note: data.signal.note,
32354
+ createdAt: data.createdAt,
30778
32355
  });
30779
32356
  }
30780
32357
  else if (data.action === "closed") {
@@ -30796,6 +32373,7 @@ class NotificationInstance {
30796
32373
  closeReason: data.closeReason,
30797
32374
  duration: durationMin,
30798
32375
  note: data.signal.note,
32376
+ createdAt: data.createdAt,
30799
32377
  });
30800
32378
  }
30801
32379
  else if (data.action === "scheduled") {
@@ -30812,6 +32390,7 @@ class NotificationInstance {
30812
32390
  priceOpen: data.signal.priceOpen,
30813
32391
  scheduledAt: data.signal.scheduledAt,
30814
32392
  currentPrice: data.currentPrice,
32393
+ createdAt: data.createdAt,
30815
32394
  });
30816
32395
  }
30817
32396
  else if (data.action === "cancelled") {
@@ -30830,6 +32409,7 @@ class NotificationInstance {
30830
32409
  cancelReason: data.reason,
30831
32410
  cancelId: data.cancelId,
30832
32411
  duration: durationMin,
32412
+ createdAt: data.createdAt,
30833
32413
  });
30834
32414
  }
30835
32415
  };
@@ -30838,7 +32418,7 @@ class NotificationInstance {
30838
32418
  */
30839
32419
  this._handlePartialProfit = async (data) => {
30840
32420
  this._addNotification({
30841
- type: "partial.profit",
32421
+ type: "partial_profit.available",
30842
32422
  id: CREATE_KEY_FN(),
30843
32423
  timestamp: data.timestamp,
30844
32424
  backtest: data.backtest,
@@ -30857,7 +32437,7 @@ class NotificationInstance {
30857
32437
  */
30858
32438
  this._handlePartialLoss = async (data) => {
30859
32439
  this._addNotification({
30860
- type: "partial.loss",
32440
+ type: "partial_loss.available",
30861
32441
  id: CREATE_KEY_FN(),
30862
32442
  timestamp: data.timestamp,
30863
32443
  backtest: data.backtest,
@@ -30872,50 +32452,109 @@ class NotificationInstance {
30872
32452
  });
30873
32453
  };
30874
32454
  /**
30875
- * Processes risk rejection events.
32455
+ * Processes breakeven events.
30876
32456
  */
30877
- this._handleRisk = async (data) => {
32457
+ this._handleBreakeven = async (data) => {
30878
32458
  this._addNotification({
30879
- type: "risk.rejection",
32459
+ type: "breakeven.available",
30880
32460
  id: CREATE_KEY_FN(),
30881
32461
  timestamp: data.timestamp,
30882
32462
  backtest: data.backtest,
30883
32463
  symbol: data.symbol,
30884
32464
  strategyName: data.strategyName,
30885
32465
  exchangeName: data.exchangeName,
30886
- rejectionNote: data.rejectionNote,
30887
- rejectionId: data.rejectionId,
30888
- activePositionCount: data.activePositionCount,
32466
+ signalId: data.data.id,
30889
32467
  currentPrice: data.currentPrice,
30890
- pendingSignal: data.pendingSignal,
32468
+ priceOpen: data.data.priceOpen,
32469
+ position: data.data.position,
30891
32470
  });
30892
32471
  };
30893
32472
  /**
30894
- * Processes done events (live/backtest).
32473
+ * Processes strategy commit events.
30895
32474
  */
30896
- this._handleDoneLive = async (data) => {
30897
- this._addNotification({
30898
- type: "live.done",
30899
- id: CREATE_KEY_FN(),
30900
- timestamp: Date.now(),
30901
- backtest: false,
30902
- symbol: data.symbol,
30903
- strategyName: data.strategyName,
30904
- exchangeName: data.exchangeName,
30905
- });
32475
+ this._handleStrategyCommit = async (data) => {
32476
+ if (data.action === "partial-profit") {
32477
+ this._addNotification({
32478
+ type: "partial_profit.commit",
32479
+ id: CREATE_KEY_FN(),
32480
+ timestamp: Date.now(),
32481
+ backtest: data.backtest,
32482
+ symbol: data.symbol,
32483
+ strategyName: data.strategyName,
32484
+ exchangeName: data.exchangeName,
32485
+ percentToClose: data.percentToClose,
32486
+ currentPrice: data.currentPrice,
32487
+ });
32488
+ }
32489
+ else if (data.action === "partial-loss") {
32490
+ this._addNotification({
32491
+ type: "partial_loss.commit",
32492
+ id: CREATE_KEY_FN(),
32493
+ timestamp: Date.now(),
32494
+ backtest: data.backtest,
32495
+ symbol: data.symbol,
32496
+ strategyName: data.strategyName,
32497
+ exchangeName: data.exchangeName,
32498
+ percentToClose: data.percentToClose,
32499
+ currentPrice: data.currentPrice,
32500
+ });
32501
+ }
32502
+ else if (data.action === "breakeven") {
32503
+ this._addNotification({
32504
+ type: "breakeven.commit",
32505
+ id: CREATE_KEY_FN(),
32506
+ timestamp: Date.now(),
32507
+ backtest: data.backtest,
32508
+ symbol: data.symbol,
32509
+ strategyName: data.strategyName,
32510
+ exchangeName: data.exchangeName,
32511
+ currentPrice: data.currentPrice,
32512
+ });
32513
+ }
32514
+ else if (data.action === "trailing-stop") {
32515
+ this._addNotification({
32516
+ type: "trailing_stop.commit",
32517
+ id: CREATE_KEY_FN(),
32518
+ timestamp: Date.now(),
32519
+ backtest: data.backtest,
32520
+ symbol: data.symbol,
32521
+ strategyName: data.strategyName,
32522
+ exchangeName: data.exchangeName,
32523
+ percentShift: data.percentShift,
32524
+ currentPrice: data.currentPrice,
32525
+ });
32526
+ }
32527
+ else if (data.action === "trailing-take") {
32528
+ this._addNotification({
32529
+ type: "trailing_take.commit",
32530
+ id: CREATE_KEY_FN(),
32531
+ timestamp: Date.now(),
32532
+ backtest: data.backtest,
32533
+ symbol: data.symbol,
32534
+ strategyName: data.strategyName,
32535
+ exchangeName: data.exchangeName,
32536
+ percentShift: data.percentShift,
32537
+ currentPrice: data.currentPrice,
32538
+ });
32539
+ }
30906
32540
  };
30907
32541
  /**
30908
- * Processes done events (backtest).
32542
+ * Processes risk rejection events.
30909
32543
  */
30910
- this._handleDoneBacktest = async (data) => {
32544
+ this._handleRisk = async (data) => {
30911
32545
  this._addNotification({
30912
- type: "backtest.done",
32546
+ type: "risk.rejection",
30913
32547
  id: CREATE_KEY_FN(),
30914
- timestamp: Date.now(),
30915
- backtest: true,
32548
+ timestamp: data.timestamp,
32549
+ backtest: data.backtest,
30916
32550
  symbol: data.symbol,
30917
32551
  strategyName: data.strategyName,
30918
32552
  exchangeName: data.exchangeName,
32553
+ rejectionNote: data.rejectionNote,
32554
+ rejectionId: data.rejectionId,
32555
+ activePositionCount: data.activePositionCount,
32556
+ currentPrice: data.currentPrice,
32557
+ pendingSignal: data.pendingSignal,
30919
32558
  });
30920
32559
  };
30921
32560
  /**
@@ -30957,23 +32596,6 @@ class NotificationInstance {
30957
32596
  backtest: false,
30958
32597
  });
30959
32598
  };
30960
- /**
30961
- * Processes progress events.
30962
- */
30963
- this._handleProgressBacktest = async (data) => {
30964
- this._addNotification({
30965
- type: "progress.backtest",
30966
- id: CREATE_KEY_FN(),
30967
- timestamp: Date.now(),
30968
- backtest: true,
30969
- exchangeName: data.exchangeName,
30970
- strategyName: data.strategyName,
30971
- symbol: data.symbol,
30972
- totalFrames: data.totalFrames,
30973
- processedFrames: data.processedFrames,
30974
- progress: data.progress,
30975
- });
30976
- };
30977
32599
  /**
30978
32600
  * Subscribes to all notification emitters and returns an unsubscribe function.
30979
32601
  * Protected against multiple subscriptions using singleshot.
@@ -30992,14 +32614,13 @@ class NotificationInstance {
30992
32614
  const unSignal = signalEmitter.subscribe(this._handleSignal);
30993
32615
  const unProfit = partialProfitSubject.subscribe(this._handlePartialProfit);
30994
32616
  const unLoss = partialLossSubject.subscribe(this._handlePartialLoss);
32617
+ const unBreakeven = breakevenSubject.subscribe(this._handleBreakeven);
32618
+ const unStrategyCommit = strategyCommitSubject.subscribe(this._handleStrategyCommit);
30995
32619
  const unRisk = riskSubject.subscribe(this._handleRisk);
30996
- const unDoneLine = doneLiveSubject.subscribe(this._handleDoneLive);
30997
- const unDoneBacktest = doneBacktestSubject.subscribe(this._handleDoneBacktest);
30998
32620
  const unError = errorEmitter.subscribe(this._handleError);
30999
32621
  const unExit = exitEmitter.subscribe(this._handleCriticalError);
31000
32622
  const unValidation = validationSubject.subscribe(this._handleValidationError);
31001
- const unProgressBacktest = progressBacktestEmitter.subscribe(this._handleProgressBacktest);
31002
- const disposeFn = compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unRisk(), () => unDoneLine(), () => unDoneBacktest(), () => unError(), () => unExit(), () => unValidation(), () => unProgressBacktest());
32623
+ const disposeFn = compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unBreakeven(), () => unStrategyCommit(), () => unRisk(), () => unError(), () => unExit(), () => unValidation());
31003
32624
  return () => {
31004
32625
  disposeFn();
31005
32626
  this.enable.clear();
@@ -31389,6 +33010,201 @@ class BreakevenUtils {
31389
33010
  */
31390
33011
  const Breakeven = new BreakevenUtils();
31391
33012
 
33013
+ const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
33014
+ const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
33015
+ const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
33016
+ /**
33017
+ * Utility class for accessing strategy management reports and statistics.
33018
+ *
33019
+ * Provides static-like methods (via singleton instance) to retrieve data
33020
+ * accumulated by StrategyMarkdownService from strategy management events.
33021
+ *
33022
+ * Features:
33023
+ * - Statistical data extraction (event counts by action type)
33024
+ * - Markdown report generation with event tables
33025
+ * - File export to disk
33026
+ *
33027
+ * Data source:
33028
+ * - StrategyMarkdownService receives events via direct method calls
33029
+ * - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
33030
+ * - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
33031
+ * trailing-stop, trailing-take, breakeven
33032
+ *
33033
+ * @example
33034
+ * ```typescript
33035
+ * import { Strategy } from "./classes/Strategy";
33036
+ *
33037
+ * // Get statistical data for BTCUSDT:my-strategy
33038
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33039
+ * console.log(`Total events: ${stats.totalEvents}`);
33040
+ * console.log(`Partial profit events: ${stats.partialProfitCount}`);
33041
+ *
33042
+ * // Generate markdown report
33043
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33044
+ * console.log(markdown); // Formatted table with all events
33045
+ *
33046
+ * // Export report to file
33047
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
33048
+ * ```
33049
+ */
33050
+ class StrategyUtils {
33051
+ constructor() {
33052
+ /**
33053
+ * Retrieves statistical data from accumulated strategy events.
33054
+ *
33055
+ * Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
33056
+ * Returns aggregated metrics calculated from all strategy events.
33057
+ *
33058
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33059
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33060
+ * @param backtest - Whether to get backtest data (default: false)
33061
+ * @returns Promise resolving to StrategyStatisticsModel object with counts and event list
33062
+ *
33063
+ * @example
33064
+ * ```typescript
33065
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33066
+ *
33067
+ * console.log(`Total events: ${stats.totalEvents}`);
33068
+ * console.log(`Partial profit: ${stats.partialProfitCount}`);
33069
+ * console.log(`Trailing stop: ${stats.trailingStopCount}`);
33070
+ *
33071
+ * // Iterate through all events
33072
+ * for (const event of stats.eventList) {
33073
+ * console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
33074
+ * }
33075
+ * ```
33076
+ */
33077
+ this.getData = async (symbol, context, backtest = false) => {
33078
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
33079
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
33080
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
33081
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
33082
+ {
33083
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33084
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
33085
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
33086
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
33087
+ }
33088
+ return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
33089
+ };
33090
+ /**
33091
+ * Generates markdown report with all strategy events for a symbol-strategy pair.
33092
+ *
33093
+ * Creates formatted table containing:
33094
+ * - Symbol
33095
+ * - Strategy
33096
+ * - Signal ID
33097
+ * - Action (cancel-scheduled, close-pending, partial-profit, etc.)
33098
+ * - Price
33099
+ * - Percent values (% To Close, % Shift)
33100
+ * - Cancel/Close IDs
33101
+ * - Timestamp (ISO 8601)
33102
+ * - Mode (Backtest/Live)
33103
+ *
33104
+ * Also includes summary statistics at the end with counts by action type.
33105
+ *
33106
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33107
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33108
+ * @param backtest - Whether to get backtest data (default: false)
33109
+ * @param columns - Optional columns configuration for the report
33110
+ * @returns Promise resolving to markdown formatted report string
33111
+ *
33112
+ * @example
33113
+ * ```typescript
33114
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33115
+ * console.log(markdown);
33116
+ *
33117
+ * // Output:
33118
+ * // # Strategy Report: BTCUSDT:my-strategy
33119
+ * //
33120
+ * // | Symbol | Strategy | Signal ID | Action | Price | ... |
33121
+ * // | --- | --- | --- | --- | --- | ... |
33122
+ * // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
33123
+ * //
33124
+ * // **Total events:** 5
33125
+ * // - Cancel scheduled: 0
33126
+ * // - Close pending: 1
33127
+ * // - Partial profit: 2
33128
+ * // ...
33129
+ * ```
33130
+ */
33131
+ this.getReport = async (symbol, context, backtest = false, columns) => {
33132
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
33133
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
33134
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
33135
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
33136
+ {
33137
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33138
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
33139
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
33140
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
33141
+ }
33142
+ return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
33143
+ };
33144
+ /**
33145
+ * Generates and saves markdown report to file.
33146
+ *
33147
+ * Creates directory if it doesn't exist.
33148
+ * Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
33149
+ *
33150
+ * Delegates to StrategyMarkdownService.dump() which:
33151
+ * 1. Generates markdown report via getReport()
33152
+ * 2. Creates output directory (recursive mkdir)
33153
+ * 3. Writes file with UTF-8 encoding
33154
+ * 4. Logs success/failure to console
33155
+ *
33156
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33157
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33158
+ * @param backtest - Whether to dump backtest data (default: false)
33159
+ * @param path - Output directory path (default: "./dump/strategy")
33160
+ * @param columns - Optional columns configuration for the report
33161
+ * @returns Promise that resolves when file is written
33162
+ *
33163
+ * @example
33164
+ * ```typescript
33165
+ * // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
33166
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
33167
+ *
33168
+ * // Save to custom path
33169
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
33170
+ *
33171
+ * // After multiple symbols backtested, export all reports
33172
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
33173
+ * await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
33174
+ * }
33175
+ * ```
33176
+ */
33177
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
33178
+ bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
33179
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
33180
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
33181
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
33182
+ {
33183
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33184
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
33185
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
33186
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
33187
+ }
33188
+ await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
33189
+ };
33190
+ }
33191
+ }
33192
+ /**
33193
+ * Global singleton instance of StrategyUtils.
33194
+ * Provides static-like access to strategy management reporting methods.
33195
+ *
33196
+ * @example
33197
+ * ```typescript
33198
+ * import { Strategy } from "backtest-kit";
33199
+ *
33200
+ * // Usage same as StrategyUtils methods
33201
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33202
+ * const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33203
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33204
+ * ```
33205
+ */
33206
+ const Strategy = new StrategyUtils();
33207
+
31392
33208
  /**
31393
33209
  * Rounds a price to the appropriate precision based on the tick size.
31394
33210
  *
@@ -31521,4 +33337,4 @@ const set = (object, path, value) => {
31521
33337
  }
31522
33338
  };
31523
33339
 
31524
- export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
33340
+ export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };