backtest-kit 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +32 -10
  2. package/build/index.cjs +1891 -132
  3. package/build/index.mjs +1890 -134
  4. package/package.json +2 -1
  5. package/types.d.ts +1709 -865
package/build/index.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
  }
@@ -6513,7 +6537,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
6513
6537
  * @param backtest - Whether running in backtest mode
6514
6538
  * @returns Unique string key for memoization
6515
6539
  */
6516
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
6540
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
6517
6541
  const parts = [symbol, strategyName, exchangeName];
6518
6542
  if (frameName)
6519
6543
  parts.push(frameName);
@@ -6678,7 +6702,7 @@ class StrategyConnectionService {
6678
6702
  * @param backtest - Whether running in backtest mode
6679
6703
  * @returns Configured ClientStrategy instance
6680
6704
  */
6681
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6705
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
6682
6706
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
6683
6707
  return new ClientStrategy({
6684
6708
  symbol,
@@ -6933,7 +6957,7 @@ class StrategyConnectionService {
6933
6957
  }
6934
6958
  return;
6935
6959
  }
6936
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6960
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
6937
6961
  if (!this.getStrategy.has(key)) {
6938
6962
  return;
6939
6963
  }
@@ -7902,7 +7926,7 @@ class ClientRisk {
7902
7926
  * @param backtest - Whether running in backtest mode
7903
7927
  * @returns Unique string key for memoization
7904
7928
  */
7905
- const CREATE_KEY_FN$j = (riskName, exchangeName, frameName, backtest) => {
7929
+ const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
7906
7930
  const parts = [riskName, exchangeName];
7907
7931
  if (frameName)
7908
7932
  parts.push(frameName);
@@ -8001,7 +8025,7 @@ class RiskConnectionService {
8001
8025
  * @param backtest - True if backtest mode, false if live mode
8002
8026
  * @returns Configured ClientRisk instance
8003
8027
  */
8004
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8028
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
8005
8029
  const schema = this.riskSchemaService.get(riskName);
8006
8030
  return new ClientRisk({
8007
8031
  ...schema,
@@ -8069,7 +8093,7 @@ class RiskConnectionService {
8069
8093
  payload,
8070
8094
  });
8071
8095
  if (payload) {
8072
- const key = CREATE_KEY_FN$j(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8096
+ const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
8073
8097
  this.getRisk.clear(key);
8074
8098
  }
8075
8099
  else {
@@ -9492,7 +9516,7 @@ class ClientAction {
9492
9516
  * @param backtest - Whether running in backtest mode
9493
9517
  * @returns Unique string key for memoization
9494
9518
  */
9495
- const CREATE_KEY_FN$i = (actionName, strategyName, exchangeName, frameName, backtest) => {
9519
+ const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
9496
9520
  const parts = [actionName, strategyName, exchangeName];
9497
9521
  if (frameName)
9498
9522
  parts.push(frameName);
@@ -9543,7 +9567,7 @@ class ActionConnectionService {
9543
9567
  * @param backtest - True if backtest mode, false if live mode
9544
9568
  * @returns Configured ClientAction instance
9545
9569
  */
9546
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9570
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
9547
9571
  const schema = this.actionSchemaService.get(actionName);
9548
9572
  return new ClientAction({
9549
9573
  ...schema,
@@ -9736,7 +9760,7 @@ class ActionConnectionService {
9736
9760
  await Promise.all(actions.map(async (action) => await action.dispose()));
9737
9761
  return;
9738
9762
  }
9739
- const key = CREATE_KEY_FN$i(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9763
+ const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9740
9764
  if (!this.getAction.has(key)) {
9741
9765
  return;
9742
9766
  }
@@ -9754,7 +9778,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
9754
9778
  * @param exchangeName - Exchange name
9755
9779
  * @returns Unique string key for memoization
9756
9780
  */
9757
- const CREATE_KEY_FN$h = (exchangeName) => {
9781
+ const CREATE_KEY_FN$i = (exchangeName) => {
9758
9782
  return exchangeName;
9759
9783
  };
9760
9784
  /**
@@ -9778,7 +9802,7 @@ class ExchangeCoreService {
9778
9802
  * @param exchangeName - Name of the exchange to validate
9779
9803
  * @returns Promise that resolves when validation is complete
9780
9804
  */
9781
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$h(exchangeName), async (exchangeName) => {
9805
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
9782
9806
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
9783
9807
  exchangeName,
9784
9808
  });
@@ -10002,12 +10026,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
10002
10026
  * @param context - Execution context with strategyName, exchangeName, frameName
10003
10027
  * @returns Unique string key for memoization
10004
10028
  */
10005
- const CREATE_KEY_FN$g = (context) => {
10029
+ const CREATE_KEY_FN$h = (context) => {
10006
10030
  const parts = [context.strategyName, context.exchangeName];
10007
10031
  if (context.frameName)
10008
10032
  parts.push(context.frameName);
10009
10033
  return parts.join(":");
10010
10034
  };
10035
+ /**
10036
+ * Broadcasts StrategyCommitContract event to strategyCommitSubject.
10037
+ *
10038
+ * @param event - The signal commit event to broadcast
10039
+ */
10040
+ const CALL_STRATEGY_COMMIT_FN = trycatch(async (event) => {
10041
+ await strategyCommitSubject.next(event);
10042
+ }, {
10043
+ fallback: (error) => {
10044
+ const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
10045
+ const payload = {
10046
+ error: errorData(error),
10047
+ message: getErrorMessage(error),
10048
+ };
10049
+ bt.loggerService.warn(message, payload);
10050
+ console.warn(message, payload);
10051
+ errorEmitter.next(error);
10052
+ },
10053
+ defaultValue: null,
10054
+ });
10011
10055
  /**
10012
10056
  * Global service for strategy operations with execution context injection.
10013
10057
  *
@@ -10034,7 +10078,7 @@ class StrategyCoreService {
10034
10078
  * @param context - Execution context with strategyName, exchangeName, frameName
10035
10079
  * @returns Promise that resolves when validation is complete
10036
10080
  */
10037
- this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10081
+ this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
10038
10082
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
10039
10083
  context,
10040
10084
  });
@@ -10240,7 +10284,19 @@ class StrategyCoreService {
10240
10284
  cancelId,
10241
10285
  });
10242
10286
  await this.validate(context);
10243
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10287
+ const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
10288
+ {
10289
+ await CALL_STRATEGY_COMMIT_FN({
10290
+ action: "cancel-scheduled",
10291
+ symbol,
10292
+ strategyName: context.strategyName,
10293
+ exchangeName: context.exchangeName,
10294
+ frameName: context.frameName,
10295
+ backtest,
10296
+ cancelId,
10297
+ });
10298
+ }
10299
+ return result;
10244
10300
  };
10245
10301
  /**
10246
10302
  * Closes the pending signal without stopping the strategy.
@@ -10267,7 +10323,19 @@ class StrategyCoreService {
10267
10323
  closeId,
10268
10324
  });
10269
10325
  await this.validate(context);
10270
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10326
+ const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
10327
+ {
10328
+ await CALL_STRATEGY_COMMIT_FN({
10329
+ action: "close-pending",
10330
+ symbol,
10331
+ strategyName: context.strategyName,
10332
+ exchangeName: context.exchangeName,
10333
+ frameName: context.frameName,
10334
+ backtest,
10335
+ closeId,
10336
+ });
10337
+ }
10338
+ return result;
10271
10339
  };
10272
10340
  /**
10273
10341
  * Disposes the ClientStrategy instance for the given context.
@@ -10348,7 +10416,20 @@ class StrategyCoreService {
10348
10416
  backtest,
10349
10417
  });
10350
10418
  await this.validate(context);
10351
- return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10419
+ const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
10420
+ if (result) {
10421
+ await CALL_STRATEGY_COMMIT_FN({
10422
+ action: "partial-profit",
10423
+ symbol,
10424
+ strategyName: context.strategyName,
10425
+ exchangeName: context.exchangeName,
10426
+ frameName: context.frameName,
10427
+ backtest,
10428
+ percentToClose,
10429
+ currentPrice,
10430
+ });
10431
+ }
10432
+ return result;
10352
10433
  };
10353
10434
  /**
10354
10435
  * Executes partial close at loss level (moving toward SL).
@@ -10389,7 +10470,20 @@ class StrategyCoreService {
10389
10470
  backtest,
10390
10471
  });
10391
10472
  await this.validate(context);
10392
- return await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10473
+ const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
10474
+ if (result) {
10475
+ await CALL_STRATEGY_COMMIT_FN({
10476
+ action: "partial-loss",
10477
+ symbol,
10478
+ strategyName: context.strategyName,
10479
+ exchangeName: context.exchangeName,
10480
+ frameName: context.frameName,
10481
+ backtest,
10482
+ percentToClose,
10483
+ currentPrice,
10484
+ });
10485
+ }
10486
+ return result;
10393
10487
  };
10394
10488
  /**
10395
10489
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -10428,7 +10522,20 @@ class StrategyCoreService {
10428
10522
  backtest,
10429
10523
  });
10430
10524
  await this.validate(context);
10431
- return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10525
+ const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
10526
+ if (result) {
10527
+ await CALL_STRATEGY_COMMIT_FN({
10528
+ action: "trailing-stop",
10529
+ symbol,
10530
+ strategyName: context.strategyName,
10531
+ exchangeName: context.exchangeName,
10532
+ frameName: context.frameName,
10533
+ backtest,
10534
+ percentShift,
10535
+ currentPrice,
10536
+ });
10537
+ }
10538
+ return result;
10432
10539
  };
10433
10540
  /**
10434
10541
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -10463,7 +10570,20 @@ class StrategyCoreService {
10463
10570
  backtest,
10464
10571
  });
10465
10572
  await this.validate(context);
10466
- return await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10573
+ const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
10574
+ if (result) {
10575
+ await CALL_STRATEGY_COMMIT_FN({
10576
+ action: "trailing-take",
10577
+ symbol,
10578
+ strategyName: context.strategyName,
10579
+ exchangeName: context.exchangeName,
10580
+ frameName: context.frameName,
10581
+ backtest,
10582
+ percentShift,
10583
+ currentPrice,
10584
+ });
10585
+ }
10586
+ return result;
10467
10587
  };
10468
10588
  /**
10469
10589
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -10493,7 +10613,19 @@ class StrategyCoreService {
10493
10613
  backtest,
10494
10614
  });
10495
10615
  await this.validate(context);
10496
- return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10616
+ const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
10617
+ if (result) {
10618
+ await CALL_STRATEGY_COMMIT_FN({
10619
+ action: "breakeven",
10620
+ symbol,
10621
+ strategyName: context.strategyName,
10622
+ exchangeName: context.exchangeName,
10623
+ frameName: context.frameName,
10624
+ backtest,
10625
+ currentPrice,
10626
+ });
10627
+ }
10628
+ return result;
10497
10629
  };
10498
10630
  }
10499
10631
  }
@@ -10567,7 +10699,7 @@ class SizingGlobalService {
10567
10699
  * @param context - Context with riskName, exchangeName, frameName
10568
10700
  * @returns Unique string key for memoization
10569
10701
  */
10570
- const CREATE_KEY_FN$f = (context) => {
10702
+ const CREATE_KEY_FN$g = (context) => {
10571
10703
  const parts = [context.riskName, context.exchangeName];
10572
10704
  if (context.frameName)
10573
10705
  parts.push(context.frameName);
@@ -10593,7 +10725,7 @@ class RiskGlobalService {
10593
10725
  * @param payload - Payload with riskName, exchangeName and frameName
10594
10726
  * @returns Promise that resolves when validation is complete
10595
10727
  */
10596
- this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10728
+ this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
10597
10729
  this.loggerService.log("riskGlobalService validate", {
10598
10730
  context,
10599
10731
  });
@@ -10671,7 +10803,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
10671
10803
  * @param context - Execution context with strategyName, exchangeName, frameName
10672
10804
  * @returns Unique string key for memoization
10673
10805
  */
10674
- const CREATE_KEY_FN$e = (context) => {
10806
+ const CREATE_KEY_FN$f = (context) => {
10675
10807
  const parts = [context.strategyName, context.exchangeName];
10676
10808
  if (context.frameName)
10677
10809
  parts.push(context.frameName);
@@ -10715,7 +10847,7 @@ class ActionCoreService {
10715
10847
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
10716
10848
  * @returns Promise that resolves when all validations complete
10717
10849
  */
10718
- this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), async (context) => {
10850
+ this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
10719
10851
  this.loggerService.log(METHOD_NAME_VALIDATE, {
10720
10852
  context,
10721
10853
  });
@@ -13965,6 +14097,116 @@ const schedule_columns = [
13965
14097
  },
13966
14098
  ];
13967
14099
 
14100
+ /**
14101
+ * Column configuration for strategy markdown reports.
14102
+ *
14103
+ * Defines the table structure for displaying strategy management events in trading reports.
14104
+ * Each column specifies how to format and display strategy actions like partial profit/loss,
14105
+ * trailing stop/take, breakeven, cancel scheduled, and close pending.
14106
+ *
14107
+ * Used by {@link StrategyMarkdownService} to generate markdown tables showing:
14108
+ * - Signal identification (symbol, strategy name, signal ID)
14109
+ * - Action information (action type, percent values, prices)
14110
+ * - Timing information (timestamp, mode: backtest or live)
14111
+ *
14112
+ * @remarks
14113
+ * This configuration tracks all strategy management events - when signals are
14114
+ * modified, cancelled, or closed during their lifecycle.
14115
+ *
14116
+ * @example
14117
+ * ```typescript
14118
+ * import { strategy_columns } from "./assets/strategy.columns";
14119
+ *
14120
+ * // Use with StrategyMarkdownService
14121
+ * const service = new StrategyMarkdownService();
14122
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
14123
+ *
14124
+ * // Or customize to show only key fields
14125
+ * const customColumns = strategy_columns.filter(col =>
14126
+ * ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
14127
+ * );
14128
+ * await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
14129
+ * ```
14130
+ *
14131
+ * @see {@link StrategyMarkdownService} for usage in report generation
14132
+ * @see {@link ColumnModel} for column interface definition
14133
+ * @see {@link StrategyEvent} for data structure
14134
+ */
14135
+ const strategy_columns = [
14136
+ {
14137
+ key: "symbol",
14138
+ label: "Symbol",
14139
+ format: (data) => data.symbol,
14140
+ isVisible: () => true,
14141
+ },
14142
+ {
14143
+ key: "strategyName",
14144
+ label: "Strategy",
14145
+ format: (data) => data.strategyName,
14146
+ isVisible: () => true,
14147
+ },
14148
+ {
14149
+ key: "signalId",
14150
+ label: "Signal ID",
14151
+ format: (data) => data.signalId,
14152
+ isVisible: () => true,
14153
+ },
14154
+ {
14155
+ key: "action",
14156
+ label: "Action",
14157
+ format: (data) => data.action,
14158
+ isVisible: () => true,
14159
+ },
14160
+ {
14161
+ key: "currentPrice",
14162
+ label: "Price",
14163
+ format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
14164
+ isVisible: () => true,
14165
+ },
14166
+ {
14167
+ key: "percentToClose",
14168
+ label: "% To Close",
14169
+ format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
14170
+ isVisible: () => true,
14171
+ },
14172
+ {
14173
+ key: "percentShift",
14174
+ label: "% Shift",
14175
+ format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
14176
+ isVisible: () => true,
14177
+ },
14178
+ {
14179
+ key: "cancelId",
14180
+ label: "Cancel ID",
14181
+ format: (data) => data.cancelId || "N/A",
14182
+ isVisible: () => true,
14183
+ },
14184
+ {
14185
+ key: "closeId",
14186
+ label: "Close ID",
14187
+ format: (data) => data.closeId || "N/A",
14188
+ isVisible: () => true,
14189
+ },
14190
+ {
14191
+ key: "createdAt",
14192
+ label: "Created At",
14193
+ format: (data) => data.createdAt || "N/A",
14194
+ isVisible: () => true,
14195
+ },
14196
+ {
14197
+ key: "timestamp",
14198
+ label: "Timestamp",
14199
+ format: (data) => new Date(data.timestamp).toISOString(),
14200
+ isVisible: () => true,
14201
+ },
14202
+ {
14203
+ key: "mode",
14204
+ label: "Mode",
14205
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
14206
+ isVisible: () => true,
14207
+ },
14208
+ ];
14209
+
13968
14210
  /**
13969
14211
  * Column configuration for walker strategy comparison table in markdown reports.
13970
14212
  *
@@ -14178,6 +14420,8 @@ const COLUMN_CONFIG = {
14178
14420
  risk_columns,
14179
14421
  /** Columns for scheduled report output */
14180
14422
  schedule_columns,
14423
+ /** Columns for strategy management events */
14424
+ strategy_columns,
14181
14425
  /** Walker: PnL summary columns */
14182
14426
  walker_pnl_columns,
14183
14427
  /** Walker: strategy-level summary columns */
@@ -14214,6 +14458,7 @@ const WILDCARD_TARGET$1 = {
14214
14458
  partial: true,
14215
14459
  performance: true,
14216
14460
  risk: true,
14461
+ strategy: true,
14217
14462
  schedule: true,
14218
14463
  walker: true,
14219
14464
  };
@@ -14443,7 +14688,7 @@ class MarkdownUtils {
14443
14688
  *
14444
14689
  * @returns Cleanup function that unsubscribes from all enabled services
14445
14690
  */
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) => {
14691
+ 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
14692
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
14448
14693
  backtest: bt$1,
14449
14694
  breakeven,
@@ -14452,6 +14697,7 @@ class MarkdownUtils {
14452
14697
  partial,
14453
14698
  performance,
14454
14699
  risk,
14700
+ strategy,
14455
14701
  schedule,
14456
14702
  walker,
14457
14703
  });
@@ -14477,6 +14723,9 @@ class MarkdownUtils {
14477
14723
  if (risk) {
14478
14724
  unList.push(bt.riskMarkdownService.subscribe());
14479
14725
  }
14726
+ if (strategy) {
14727
+ unList.push(bt.strategyMarkdownService.subscribe());
14728
+ }
14480
14729
  if (schedule) {
14481
14730
  unList.push(bt.scheduleMarkdownService.subscribe());
14482
14731
  }
@@ -14522,7 +14771,7 @@ class MarkdownUtils {
14522
14771
  * Markdown.disable();
14523
14772
  * ```
14524
14773
  */
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) => {
14774
+ 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
14775
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
14527
14776
  backtest: bt$1,
14528
14777
  breakeven,
@@ -14531,6 +14780,7 @@ class MarkdownUtils {
14531
14780
  partial,
14532
14781
  performance,
14533
14782
  risk,
14783
+ strategy,
14534
14784
  schedule,
14535
14785
  walker,
14536
14786
  });
@@ -14555,6 +14805,9 @@ class MarkdownUtils {
14555
14805
  if (risk) {
14556
14806
  bt.riskMarkdownService.unsubscribe();
14557
14807
  }
14808
+ if (strategy) {
14809
+ bt.strategyMarkdownService.unsubscribe();
14810
+ }
14558
14811
  if (schedule) {
14559
14812
  bt.scheduleMarkdownService.unsubscribe();
14560
14813
  }
@@ -14667,7 +14920,7 @@ const Markdown = new MarkdownAdapter();
14667
14920
  * @param backtest - Whether running in backtest mode
14668
14921
  * @returns Unique string key for memoization
14669
14922
  */
14670
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
14923
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
14671
14924
  const parts = [symbol, strategyName, exchangeName];
14672
14925
  if (frameName)
14673
14926
  parts.push(frameName);
@@ -14684,7 +14937,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
14684
14937
  * @param timestamp - Unix timestamp in milliseconds
14685
14938
  * @returns Filename string
14686
14939
  */
14687
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14940
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
14688
14941
  const parts = [symbol, strategyName, exchangeName];
14689
14942
  if (frameName) {
14690
14943
  parts.push(frameName);
@@ -14713,12 +14966,12 @@ function isUnsafe$3(value) {
14713
14966
  return false;
14714
14967
  }
14715
14968
  /** Maximum number of signals to store in backtest reports */
14716
- const MAX_EVENTS$7 = 250;
14969
+ const MAX_EVENTS$8 = 250;
14717
14970
  /**
14718
14971
  * Storage class for accumulating closed signals per strategy.
14719
14972
  * Maintains a list of all closed signals and provides methods to generate reports.
14720
14973
  */
14721
- let ReportStorage$6 = class ReportStorage {
14974
+ let ReportStorage$7 = class ReportStorage {
14722
14975
  constructor(symbol, strategyName, exchangeName, frameName) {
14723
14976
  this.symbol = symbol;
14724
14977
  this.strategyName = strategyName;
@@ -14735,7 +14988,7 @@ let ReportStorage$6 = class ReportStorage {
14735
14988
  addSignal(data) {
14736
14989
  this._signalList.unshift(data);
14737
14990
  // Trim queue if exceeded MAX_EVENTS
14738
- if (this._signalList.length > MAX_EVENTS$7) {
14991
+ if (this._signalList.length > MAX_EVENTS$8) {
14739
14992
  this._signalList.pop();
14740
14993
  }
14741
14994
  }
@@ -14859,7 +15112,7 @@ let ReportStorage$6 = class ReportStorage {
14859
15112
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
14860
15113
  const markdown = await this.getReport(strategyName, columns);
14861
15114
  const timestamp = Date.now();
14862
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15115
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
14863
15116
  await Markdown.writeData("backtest", markdown, {
14864
15117
  path,
14865
15118
  file: filename,
@@ -14906,7 +15159,7 @@ class BacktestMarkdownService {
14906
15159
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
14907
15160
  * Each combination gets its own isolated storage instance.
14908
15161
  */
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));
15162
+ 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
15163
  /**
14911
15164
  * Processes tick events and accumulates closed signals.
14912
15165
  * Should be called from IStrategyCallbacks.onTick.
@@ -15063,7 +15316,7 @@ class BacktestMarkdownService {
15063
15316
  payload,
15064
15317
  });
15065
15318
  if (payload) {
15066
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15319
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15067
15320
  this.getStorage.clear(key);
15068
15321
  }
15069
15322
  else {
@@ -15125,7 +15378,7 @@ class BacktestMarkdownService {
15125
15378
  * @param backtest - Whether running in backtest mode
15126
15379
  * @returns Unique string key for memoization
15127
15380
  */
15128
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15381
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
15129
15382
  const parts = [symbol, strategyName, exchangeName];
15130
15383
  if (frameName)
15131
15384
  parts.push(frameName);
@@ -15142,7 +15395,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
15142
15395
  * @param timestamp - Unix timestamp in milliseconds
15143
15396
  * @returns Filename string
15144
15397
  */
15145
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15398
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15146
15399
  const parts = [symbol, strategyName, exchangeName];
15147
15400
  if (frameName) {
15148
15401
  parts.push(frameName);
@@ -15171,12 +15424,12 @@ function isUnsafe$2(value) {
15171
15424
  return false;
15172
15425
  }
15173
15426
  /** Maximum number of events to store in live trading reports */
15174
- const MAX_EVENTS$6 = 250;
15427
+ const MAX_EVENTS$7 = 250;
15175
15428
  /**
15176
15429
  * Storage class for accumulating all tick events per strategy.
15177
15430
  * Maintains a chronological list of all events (idle, opened, active, closed).
15178
15431
  */
15179
- let ReportStorage$5 = class ReportStorage {
15432
+ let ReportStorage$6 = class ReportStorage {
15180
15433
  constructor(symbol, strategyName, exchangeName, frameName) {
15181
15434
  this.symbol = symbol;
15182
15435
  this.strategyName = strategyName;
@@ -15208,7 +15461,7 @@ let ReportStorage$5 = class ReportStorage {
15208
15461
  }
15209
15462
  {
15210
15463
  this._eventList.unshift(newEvent);
15211
- if (this._eventList.length > MAX_EVENTS$6) {
15464
+ if (this._eventList.length > MAX_EVENTS$7) {
15212
15465
  this._eventList.pop();
15213
15466
  }
15214
15467
  }
@@ -15235,7 +15488,7 @@ let ReportStorage$5 = class ReportStorage {
15235
15488
  totalExecuted: data.signal.totalExecuted,
15236
15489
  });
15237
15490
  // Trim queue if exceeded MAX_EVENTS
15238
- if (this._eventList.length > MAX_EVENTS$6) {
15491
+ if (this._eventList.length > MAX_EVENTS$7) {
15239
15492
  this._eventList.pop();
15240
15493
  }
15241
15494
  }
@@ -15274,7 +15527,7 @@ let ReportStorage$5 = class ReportStorage {
15274
15527
  // If no previous active event found, add new event
15275
15528
  this._eventList.unshift(newEvent);
15276
15529
  // Trim queue if exceeded MAX_EVENTS
15277
- if (this._eventList.length > MAX_EVENTS$6) {
15530
+ if (this._eventList.length > MAX_EVENTS$7) {
15278
15531
  this._eventList.pop();
15279
15532
  }
15280
15533
  }
@@ -15306,7 +15559,7 @@ let ReportStorage$5 = class ReportStorage {
15306
15559
  };
15307
15560
  this._eventList.unshift(newEvent);
15308
15561
  // Trim queue if exceeded MAX_EVENTS
15309
- if (this._eventList.length > MAX_EVENTS$6) {
15562
+ if (this._eventList.length > MAX_EVENTS$7) {
15310
15563
  this._eventList.pop();
15311
15564
  }
15312
15565
  }
@@ -15332,7 +15585,7 @@ let ReportStorage$5 = class ReportStorage {
15332
15585
  totalExecuted: data.signal.totalExecuted,
15333
15586
  });
15334
15587
  // Trim queue if exceeded MAX_EVENTS
15335
- if (this._eventList.length > MAX_EVENTS$6) {
15588
+ if (this._eventList.length > MAX_EVENTS$7) {
15336
15589
  this._eventList.pop();
15337
15590
  }
15338
15591
  }
@@ -15371,7 +15624,7 @@ let ReportStorage$5 = class ReportStorage {
15371
15624
  // If no previous waiting event found, add new event
15372
15625
  this._eventList.unshift(newEvent);
15373
15626
  // Trim queue if exceeded MAX_EVENTS
15374
- if (this._eventList.length > MAX_EVENTS$6) {
15627
+ if (this._eventList.length > MAX_EVENTS$7) {
15375
15628
  this._eventList.pop();
15376
15629
  }
15377
15630
  }
@@ -15398,7 +15651,7 @@ let ReportStorage$5 = class ReportStorage {
15398
15651
  cancelReason: data.reason,
15399
15652
  });
15400
15653
  // Trim queue if exceeded MAX_EVENTS
15401
- if (this._eventList.length > MAX_EVENTS$6) {
15654
+ if (this._eventList.length > MAX_EVENTS$7) {
15402
15655
  this._eventList.pop();
15403
15656
  }
15404
15657
  }
@@ -15537,7 +15790,7 @@ let ReportStorage$5 = class ReportStorage {
15537
15790
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
15538
15791
  const markdown = await this.getReport(strategyName, columns);
15539
15792
  const timestamp = Date.now();
15540
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15793
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
15541
15794
  await Markdown.writeData("live", markdown, {
15542
15795
  path,
15543
15796
  signalId: "",
@@ -15587,7 +15840,7 @@ class LiveMarkdownService {
15587
15840
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
15588
15841
  * Each combination gets its own isolated storage instance.
15589
15842
  */
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));
15843
+ 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
15844
  /**
15592
15845
  * Subscribes to live signal emitter to receive tick events.
15593
15846
  * Protected against multiple subscriptions.
@@ -15805,7 +16058,7 @@ class LiveMarkdownService {
15805
16058
  payload,
15806
16059
  });
15807
16060
  if (payload) {
15808
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16061
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
15809
16062
  this.getStorage.clear(key);
15810
16063
  }
15811
16064
  else {
@@ -15825,7 +16078,7 @@ class LiveMarkdownService {
15825
16078
  * @param backtest - Whether running in backtest mode
15826
16079
  * @returns Unique string key for memoization
15827
16080
  */
15828
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16081
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
15829
16082
  const parts = [symbol, strategyName, exchangeName];
15830
16083
  if (frameName)
15831
16084
  parts.push(frameName);
@@ -15842,7 +16095,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
15842
16095
  * @param timestamp - Unix timestamp in milliseconds
15843
16096
  * @returns Filename string
15844
16097
  */
15845
- const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16098
+ const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
15846
16099
  const parts = [symbol, strategyName, exchangeName];
15847
16100
  if (frameName) {
15848
16101
  parts.push(frameName);
@@ -15853,12 +16106,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
15853
16106
  return `${parts.join("_")}-${timestamp}.md`;
15854
16107
  };
15855
16108
  /** Maximum number of events to store in schedule reports */
15856
- const MAX_EVENTS$5 = 250;
16109
+ const MAX_EVENTS$6 = 250;
15857
16110
  /**
15858
16111
  * Storage class for accumulating scheduled signal events per strategy.
15859
16112
  * Maintains a chronological list of scheduled and cancelled events.
15860
16113
  */
15861
- let ReportStorage$4 = class ReportStorage {
16114
+ let ReportStorage$5 = class ReportStorage {
15862
16115
  constructor(symbol, strategyName, exchangeName, frameName) {
15863
16116
  this.symbol = symbol;
15864
16117
  this.strategyName = strategyName;
@@ -15889,7 +16142,7 @@ let ReportStorage$4 = class ReportStorage {
15889
16142
  totalExecuted: data.signal.totalExecuted,
15890
16143
  });
15891
16144
  // Trim queue if exceeded MAX_EVENTS
15892
- if (this._eventList.length > MAX_EVENTS$5) {
16145
+ if (this._eventList.length > MAX_EVENTS$6) {
15893
16146
  this._eventList.pop();
15894
16147
  }
15895
16148
  }
@@ -15919,7 +16172,7 @@ let ReportStorage$4 = class ReportStorage {
15919
16172
  };
15920
16173
  this._eventList.unshift(newEvent);
15921
16174
  // Trim queue if exceeded MAX_EVENTS
15922
- if (this._eventList.length > MAX_EVENTS$5) {
16175
+ if (this._eventList.length > MAX_EVENTS$6) {
15923
16176
  this._eventList.pop();
15924
16177
  }
15925
16178
  }
@@ -15952,7 +16205,7 @@ let ReportStorage$4 = class ReportStorage {
15952
16205
  };
15953
16206
  this._eventList.unshift(newEvent);
15954
16207
  // Trim queue if exceeded MAX_EVENTS
15955
- if (this._eventList.length > MAX_EVENTS$5) {
16208
+ if (this._eventList.length > MAX_EVENTS$6) {
15956
16209
  this._eventList.pop();
15957
16210
  }
15958
16211
  }
@@ -16059,7 +16312,7 @@ let ReportStorage$4 = class ReportStorage {
16059
16312
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
16060
16313
  const markdown = await this.getReport(strategyName, columns);
16061
16314
  const timestamp = Date.now();
16062
- const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16315
+ const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16063
16316
  await Markdown.writeData("schedule", markdown, {
16064
16317
  path,
16065
16318
  file: filename,
@@ -16100,7 +16353,7 @@ class ScheduleMarkdownService {
16100
16353
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
16101
16354
  * Each combination gets its own isolated storage instance.
16102
16355
  */
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));
16356
+ 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
16357
  /**
16105
16358
  * Subscribes to signal emitter to receive scheduled signal events.
16106
16359
  * Protected against multiple subscriptions.
@@ -16303,7 +16556,7 @@ class ScheduleMarkdownService {
16303
16556
  payload,
16304
16557
  });
16305
16558
  if (payload) {
16306
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16559
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16307
16560
  this.getStorage.clear(key);
16308
16561
  }
16309
16562
  else {
@@ -16323,7 +16576,7 @@ class ScheduleMarkdownService {
16323
16576
  * @param backtest - Whether running in backtest mode
16324
16577
  * @returns Unique string key for memoization
16325
16578
  */
16326
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
16579
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
16327
16580
  const parts = [symbol, strategyName, exchangeName];
16328
16581
  if (frameName)
16329
16582
  parts.push(frameName);
@@ -16340,7 +16593,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
16340
16593
  * @param timestamp - Unix timestamp in milliseconds
16341
16594
  * @returns Filename string
16342
16595
  */
16343
- const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16596
+ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
16344
16597
  const parts = [symbol, strategyName, exchangeName];
16345
16598
  if (frameName) {
16346
16599
  parts.push(frameName);
@@ -16360,7 +16613,7 @@ function percentile(sortedArray, p) {
16360
16613
  return sortedArray[Math.max(0, index)];
16361
16614
  }
16362
16615
  /** Maximum number of performance events to store per strategy */
16363
- const MAX_EVENTS$4 = 10000;
16616
+ const MAX_EVENTS$5 = 10000;
16364
16617
  /**
16365
16618
  * Storage class for accumulating performance metrics per strategy.
16366
16619
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -16382,7 +16635,7 @@ class PerformanceStorage {
16382
16635
  addEvent(event) {
16383
16636
  this._events.unshift(event);
16384
16637
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
16385
- if (this._events.length > MAX_EVENTS$4) {
16638
+ if (this._events.length > MAX_EVENTS$5) {
16386
16639
  this._events.pop();
16387
16640
  }
16388
16641
  }
@@ -16524,7 +16777,7 @@ class PerformanceStorage {
16524
16777
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
16525
16778
  const markdown = await this.getReport(strategyName, columns);
16526
16779
  const timestamp = Date.now();
16527
- const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16780
+ const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16528
16781
  await Markdown.writeData("performance", markdown, {
16529
16782
  path,
16530
16783
  file: filename,
@@ -16571,7 +16824,7 @@ class PerformanceMarkdownService {
16571
16824
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
16572
16825
  * Each combination gets its own isolated storage instance.
16573
16826
  */
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));
16827
+ 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
16828
  /**
16576
16829
  * Subscribes to performance emitter to receive performance events.
16577
16830
  * Protected against multiple subscriptions.
@@ -16738,7 +16991,7 @@ class PerformanceMarkdownService {
16738
16991
  payload,
16739
16992
  });
16740
16993
  if (payload) {
16741
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16994
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
16742
16995
  this.getStorage.clear(key);
16743
16996
  }
16744
16997
  else {
@@ -16752,7 +17005,7 @@ class PerformanceMarkdownService {
16752
17005
  * Creates a filename for markdown report based on walker name.
16753
17006
  * Filename format: "walkerName-timestamp.md"
16754
17007
  */
16755
- const CREATE_FILE_NAME_FN$4 = (walkerName, timestamp) => {
17008
+ const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
16756
17009
  return `${walkerName}-${timestamp}.md`;
16757
17010
  };
16758
17011
  /**
@@ -16787,7 +17040,7 @@ function formatMetric(value) {
16787
17040
  * Storage class for accumulating walker results.
16788
17041
  * Maintains a list of all strategy results and provides methods to generate reports.
16789
17042
  */
16790
- let ReportStorage$3 = class ReportStorage {
17043
+ let ReportStorage$4 = class ReportStorage {
16791
17044
  constructor(walkerName) {
16792
17045
  this.walkerName = walkerName;
16793
17046
  /** Walker metadata (set from first addResult call) */
@@ -16975,7 +17228,7 @@ let ReportStorage$3 = class ReportStorage {
16975
17228
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
16976
17229
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
16977
17230
  const timestamp = Date.now();
16978
- const filename = CREATE_FILE_NAME_FN$4(this.walkerName, timestamp);
17231
+ const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
16979
17232
  await Markdown.writeData("walker", markdown, {
16980
17233
  path,
16981
17234
  file: filename,
@@ -17011,7 +17264,7 @@ class WalkerMarkdownService {
17011
17264
  * Memoized function to get or create ReportStorage for a walker.
17012
17265
  * Each walker gets its own isolated storage instance.
17013
17266
  */
17014
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$3(walkerName));
17267
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
17015
17268
  /**
17016
17269
  * Subscribes to walker emitter to receive walker progress events.
17017
17270
  * Protected against multiple subscriptions.
@@ -17208,7 +17461,7 @@ class WalkerMarkdownService {
17208
17461
  * @param backtest - Whether running in backtest mode
17209
17462
  * @returns Unique string key for memoization
17210
17463
  */
17211
- const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17464
+ const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
17212
17465
  const parts = [exchangeName];
17213
17466
  if (frameName)
17214
17467
  parts.push(frameName);
@@ -17219,7 +17472,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
17219
17472
  * Creates a filename for markdown report based on memoization key components.
17220
17473
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
17221
17474
  */
17222
- const CREATE_FILE_NAME_FN$3 = (strategyName, exchangeName, frameName, timestamp) => {
17475
+ const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
17223
17476
  const parts = [strategyName, exchangeName];
17224
17477
  if (frameName) {
17225
17478
  parts.push(frameName);
@@ -17252,7 +17505,7 @@ function isUnsafe(value) {
17252
17505
  return false;
17253
17506
  }
17254
17507
  /** Maximum number of signals to store per symbol in heatmap reports */
17255
- const MAX_EVENTS$3 = 250;
17508
+ const MAX_EVENTS$4 = 250;
17256
17509
  /**
17257
17510
  * Storage class for accumulating closed signals per strategy and generating heatmap.
17258
17511
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -17278,7 +17531,7 @@ class HeatmapStorage {
17278
17531
  const signals = this.symbolData.get(symbol);
17279
17532
  signals.unshift(data);
17280
17533
  // Trim queue if exceeded MAX_EVENTS per symbol
17281
- if (signals.length > MAX_EVENTS$3) {
17534
+ if (signals.length > MAX_EVENTS$4) {
17282
17535
  signals.pop();
17283
17536
  }
17284
17537
  }
@@ -17529,7 +17782,7 @@ class HeatmapStorage {
17529
17782
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
17530
17783
  const markdown = await this.getReport(strategyName, columns);
17531
17784
  const timestamp = Date.now();
17532
- const filename = CREATE_FILE_NAME_FN$3(strategyName, this.exchangeName, this.frameName, timestamp);
17785
+ const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
17533
17786
  await Markdown.writeData("heat", markdown, {
17534
17787
  path,
17535
17788
  file: filename,
@@ -17575,7 +17828,7 @@ class HeatMarkdownService {
17575
17828
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
17576
17829
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
17577
17830
  */
17578
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17831
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
17579
17832
  /**
17580
17833
  * Subscribes to signal emitter to receive tick events.
17581
17834
  * Protected against multiple subscriptions.
@@ -17770,7 +18023,7 @@ class HeatMarkdownService {
17770
18023
  payload,
17771
18024
  });
17772
18025
  if (payload) {
17773
- const key = CREATE_KEY_FN$9(payload.exchangeName, payload.frameName, payload.backtest);
18026
+ const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
17774
18027
  this.getStorage.clear(key);
17775
18028
  }
17776
18029
  else {
@@ -18801,7 +19054,7 @@ class ClientPartial {
18801
19054
  * @param backtest - Whether running in backtest mode
18802
19055
  * @returns Unique string key for memoization
18803
19056
  */
18804
- const CREATE_KEY_FN$8 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
19057
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
18805
19058
  /**
18806
19059
  * Creates a callback function for emitting profit events to partialProfitSubject.
18807
19060
  *
@@ -18923,7 +19176,7 @@ class PartialConnectionService {
18923
19176
  * Key format: "signalId:backtest" or "signalId:live"
18924
19177
  * Value: ClientPartial instance with logger and event emitters
18925
19178
  */
18926
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$8(signalId, backtest), (signalId, backtest) => {
19179
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
18927
19180
  return new ClientPartial({
18928
19181
  signalId,
18929
19182
  logger: this.loggerService,
@@ -19013,7 +19266,7 @@ class PartialConnectionService {
19013
19266
  const partial = this.getPartial(data.id, backtest);
19014
19267
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
19015
19268
  await partial.clear(symbol, data, priceClose, backtest);
19016
- const key = CREATE_KEY_FN$8(data.id, backtest);
19269
+ const key = CREATE_KEY_FN$9(data.id, backtest);
19017
19270
  this.getPartial.clear(key);
19018
19271
  };
19019
19272
  }
@@ -19029,7 +19282,7 @@ class PartialConnectionService {
19029
19282
  * @param backtest - Whether running in backtest mode
19030
19283
  * @returns Unique string key for memoization
19031
19284
  */
19032
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19285
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
19033
19286
  const parts = [symbol, strategyName, exchangeName];
19034
19287
  if (frameName)
19035
19288
  parts.push(frameName);
@@ -19040,7 +19293,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
19040
19293
  * Creates a filename for markdown report based on memoization key components.
19041
19294
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
19042
19295
  */
19043
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19296
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19044
19297
  const parts = [symbol, strategyName, exchangeName];
19045
19298
  if (frameName) {
19046
19299
  parts.push(frameName);
@@ -19051,12 +19304,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
19051
19304
  return `${parts.join("_")}-${timestamp}.md`;
19052
19305
  };
19053
19306
  /** Maximum number of events to store in partial reports */
19054
- const MAX_EVENTS$2 = 250;
19307
+ const MAX_EVENTS$3 = 250;
19055
19308
  /**
19056
19309
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
19057
19310
  * Maintains a chronological list of profit and loss level events.
19058
19311
  */
19059
- let ReportStorage$2 = class ReportStorage {
19312
+ let ReportStorage$3 = class ReportStorage {
19060
19313
  constructor(symbol, strategyName, exchangeName, frameName) {
19061
19314
  this.symbol = symbol;
19062
19315
  this.strategyName = strategyName;
@@ -19093,7 +19346,7 @@ let ReportStorage$2 = class ReportStorage {
19093
19346
  backtest,
19094
19347
  });
19095
19348
  // Trim queue if exceeded MAX_EVENTS
19096
- if (this._eventList.length > MAX_EVENTS$2) {
19349
+ if (this._eventList.length > MAX_EVENTS$3) {
19097
19350
  this._eventList.pop();
19098
19351
  }
19099
19352
  }
@@ -19125,7 +19378,7 @@ let ReportStorage$2 = class ReportStorage {
19125
19378
  backtest,
19126
19379
  });
19127
19380
  // Trim queue if exceeded MAX_EVENTS
19128
- if (this._eventList.length > MAX_EVENTS$2) {
19381
+ if (this._eventList.length > MAX_EVENTS$3) {
19129
19382
  this._eventList.pop();
19130
19383
  }
19131
19384
  }
@@ -19201,7 +19454,7 @@ let ReportStorage$2 = class ReportStorage {
19201
19454
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
19202
19455
  const markdown = await this.getReport(symbol, strategyName, columns);
19203
19456
  const timestamp = Date.now();
19204
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19457
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19205
19458
  await Markdown.writeData("partial", markdown, {
19206
19459
  path,
19207
19460
  file: filename,
@@ -19242,7 +19495,7 @@ class PartialMarkdownService {
19242
19495
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19243
19496
  * Each combination gets its own isolated storage instance.
19244
19497
  */
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));
19498
+ 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
19499
  /**
19247
19500
  * Subscribes to partial profit/loss signal emitters to receive events.
19248
19501
  * Protected against multiple subscriptions.
@@ -19452,7 +19705,7 @@ class PartialMarkdownService {
19452
19705
  payload,
19453
19706
  });
19454
19707
  if (payload) {
19455
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19708
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19456
19709
  this.getStorage.clear(key);
19457
19710
  }
19458
19711
  else {
@@ -19468,7 +19721,7 @@ class PartialMarkdownService {
19468
19721
  * @param context - Context with strategyName, exchangeName, frameName
19469
19722
  * @returns Unique string key for memoization
19470
19723
  */
19471
- const CREATE_KEY_FN$6 = (context) => {
19724
+ const CREATE_KEY_FN$7 = (context) => {
19472
19725
  const parts = [context.strategyName, context.exchangeName];
19473
19726
  if (context.frameName)
19474
19727
  parts.push(context.frameName);
@@ -19542,7 +19795,7 @@ class PartialGlobalService {
19542
19795
  * @param context - Context with strategyName, exchangeName and frameName
19543
19796
  * @param methodName - Name of the calling method for error tracking
19544
19797
  */
19545
- this.validate = memoize(([context]) => CREATE_KEY_FN$6(context), (context, methodName) => {
19798
+ this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
19546
19799
  this.loggerService.log("partialGlobalService validate", {
19547
19800
  context,
19548
19801
  methodName,
@@ -19997,7 +20250,7 @@ class ClientBreakeven {
19997
20250
  * @param backtest - Whether running in backtest mode
19998
20251
  * @returns Unique string key for memoization
19999
20252
  */
20000
- const CREATE_KEY_FN$5 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20253
+ const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
20001
20254
  /**
20002
20255
  * Creates a callback function for emitting breakeven events to breakevenSubject.
20003
20256
  *
@@ -20083,7 +20336,7 @@ class BreakevenConnectionService {
20083
20336
  * Key format: "signalId:backtest" or "signalId:live"
20084
20337
  * Value: ClientBreakeven instance with logger and event emitter
20085
20338
  */
20086
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$5(signalId, backtest), (signalId, backtest) => {
20339
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
20087
20340
  return new ClientBreakeven({
20088
20341
  signalId,
20089
20342
  logger: this.loggerService,
@@ -20144,7 +20397,7 @@ class BreakevenConnectionService {
20144
20397
  const breakeven = this.getBreakeven(data.id, backtest);
20145
20398
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
20146
20399
  await breakeven.clear(symbol, data, priceClose, backtest);
20147
- const key = CREATE_KEY_FN$5(data.id, backtest);
20400
+ const key = CREATE_KEY_FN$6(data.id, backtest);
20148
20401
  this.getBreakeven.clear(key);
20149
20402
  };
20150
20403
  }
@@ -20160,7 +20413,7 @@ class BreakevenConnectionService {
20160
20413
  * @param backtest - Whether running in backtest mode
20161
20414
  * @returns Unique string key for memoization
20162
20415
  */
20163
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20416
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20164
20417
  const parts = [symbol, strategyName, exchangeName];
20165
20418
  if (frameName)
20166
20419
  parts.push(frameName);
@@ -20171,7 +20424,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
20171
20424
  * Creates a filename for markdown report based on memoization key components.
20172
20425
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20173
20426
  */
20174
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20427
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20175
20428
  const parts = [symbol, strategyName, exchangeName];
20176
20429
  if (frameName) {
20177
20430
  parts.push(frameName);
@@ -20182,12 +20435,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
20182
20435
  return `${parts.join("_")}-${timestamp}.md`;
20183
20436
  };
20184
20437
  /** Maximum number of events to store in breakeven reports */
20185
- const MAX_EVENTS$1 = 250;
20438
+ const MAX_EVENTS$2 = 250;
20186
20439
  /**
20187
20440
  * Storage class for accumulating breakeven events per symbol-strategy pair.
20188
20441
  * Maintains a chronological list of breakeven events.
20189
20442
  */
20190
- let ReportStorage$1 = class ReportStorage {
20443
+ let ReportStorage$2 = class ReportStorage {
20191
20444
  constructor(symbol, strategyName, exchangeName, frameName) {
20192
20445
  this.symbol = symbol;
20193
20446
  this.strategyName = strategyName;
@@ -20222,7 +20475,7 @@ let ReportStorage$1 = class ReportStorage {
20222
20475
  backtest,
20223
20476
  });
20224
20477
  // Trim queue if exceeded MAX_EVENTS
20225
- if (this._eventList.length > MAX_EVENTS$1) {
20478
+ if (this._eventList.length > MAX_EVENTS$2) {
20226
20479
  this._eventList.pop();
20227
20480
  }
20228
20481
  }
@@ -20290,7 +20543,7 @@ let ReportStorage$1 = class ReportStorage {
20290
20543
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
20291
20544
  const markdown = await this.getReport(symbol, strategyName, columns);
20292
20545
  const timestamp = Date.now();
20293
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20546
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20294
20547
  await Markdown.writeData("breakeven", markdown, {
20295
20548
  path,
20296
20549
  file: filename,
@@ -20331,7 +20584,7 @@ class BreakevenMarkdownService {
20331
20584
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20332
20585
  * Each combination gets its own isolated storage instance.
20333
20586
  */
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));
20587
+ 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
20588
  /**
20336
20589
  * Subscribes to breakeven signal emitter to receive events.
20337
20590
  * Protected against multiple subscriptions.
@@ -20520,7 +20773,7 @@ class BreakevenMarkdownService {
20520
20773
  payload,
20521
20774
  });
20522
20775
  if (payload) {
20523
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20776
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20524
20777
  this.getStorage.clear(key);
20525
20778
  }
20526
20779
  else {
@@ -20536,7 +20789,7 @@ class BreakevenMarkdownService {
20536
20789
  * @param context - Context with strategyName, exchangeName, frameName
20537
20790
  * @returns Unique string key for memoization
20538
20791
  */
20539
- const CREATE_KEY_FN$3 = (context) => {
20792
+ const CREATE_KEY_FN$4 = (context) => {
20540
20793
  const parts = [context.strategyName, context.exchangeName];
20541
20794
  if (context.frameName)
20542
20795
  parts.push(context.frameName);
@@ -20610,7 +20863,7 @@ class BreakevenGlobalService {
20610
20863
  * @param context - Context with strategyName, exchangeName and frameName
20611
20864
  * @param methodName - Name of the calling method for error tracking
20612
20865
  */
20613
- this.validate = memoize(([context]) => CREATE_KEY_FN$3(context), (context, methodName) => {
20866
+ this.validate = memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
20614
20867
  this.loggerService.log("breakevenGlobalService validate", {
20615
20868
  context,
20616
20869
  methodName,
@@ -20823,7 +21076,7 @@ class ConfigValidationService {
20823
21076
  * @param backtest - Whether running in backtest mode
20824
21077
  * @returns Unique string key for memoization
20825
21078
  */
20826
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
21079
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
20827
21080
  const parts = [symbol, strategyName, exchangeName];
20828
21081
  if (frameName)
20829
21082
  parts.push(frameName);
@@ -20834,7 +21087,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
20834
21087
  * Creates a filename for markdown report based on memoization key components.
20835
21088
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
20836
21089
  */
20837
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
21090
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20838
21091
  const parts = [symbol, strategyName, exchangeName];
20839
21092
  if (frameName) {
20840
21093
  parts.push(frameName);
@@ -20845,12 +21098,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
20845
21098
  return `${parts.join("_")}-${timestamp}.md`;
20846
21099
  };
20847
21100
  /** Maximum number of events to store in risk reports */
20848
- const MAX_EVENTS = 250;
21101
+ const MAX_EVENTS$1 = 250;
20849
21102
  /**
20850
21103
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
20851
21104
  * Maintains a chronological list of rejected signals due to risk limits.
20852
21105
  */
20853
- class ReportStorage {
21106
+ let ReportStorage$1 = class ReportStorage {
20854
21107
  constructor(symbol, strategyName, exchangeName, frameName) {
20855
21108
  this.symbol = symbol;
20856
21109
  this.strategyName = strategyName;
@@ -20867,7 +21120,7 @@ class ReportStorage {
20867
21120
  addRejectionEvent(event) {
20868
21121
  this._eventList.unshift(event);
20869
21122
  // Trim queue if exceeded MAX_EVENTS
20870
- if (this._eventList.length > MAX_EVENTS) {
21123
+ if (this._eventList.length > MAX_EVENTS$1) {
20871
21124
  this._eventList.pop();
20872
21125
  }
20873
21126
  }
@@ -20951,7 +21204,7 @@ class ReportStorage {
20951
21204
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
20952
21205
  const markdown = await this.getReport(symbol, strategyName, columns);
20953
21206
  const timestamp = Date.now();
20954
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21207
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20955
21208
  await Markdown.writeData("risk", markdown, {
20956
21209
  path,
20957
21210
  file: filename,
@@ -20962,7 +21215,7 @@ class ReportStorage {
20962
21215
  frameName: this.frameName
20963
21216
  });
20964
21217
  }
20965
- }
21218
+ };
20966
21219
  /**
20967
21220
  * Service for generating and saving risk rejection markdown reports.
20968
21221
  *
@@ -20992,7 +21245,7 @@ class RiskMarkdownService {
20992
21245
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20993
21246
  * Each combination gets its own isolated storage instance.
20994
21247
  */
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));
21248
+ 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
21249
  /**
20997
21250
  * Subscribes to risk rejection emitter to receive rejection events.
20998
21251
  * Protected against multiple subscriptions.
@@ -21181,7 +21434,7 @@ class RiskMarkdownService {
21181
21434
  payload,
21182
21435
  });
21183
21436
  if (payload) {
21184
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21437
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21185
21438
  this.getStorage.clear(key);
21186
21439
  }
21187
21440
  else {
@@ -21465,6 +21718,7 @@ class ReportDummy {
21465
21718
  */
21466
21719
  const WILDCARD_TARGET = {
21467
21720
  backtest: true,
21721
+ strategy: true,
21468
21722
  breakeven: true,
21469
21723
  heat: true,
21470
21724
  live: true,
@@ -21510,7 +21764,7 @@ class ReportUtils {
21510
21764
  *
21511
21765
  * @returns Cleanup function that unsubscribes from all enabled services
21512
21766
  */
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) => {
21767
+ 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
21768
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
21515
21769
  backtest: bt$1,
21516
21770
  breakeven,
@@ -21521,6 +21775,7 @@ class ReportUtils {
21521
21775
  risk,
21522
21776
  schedule,
21523
21777
  walker,
21778
+ strategy,
21524
21779
  });
21525
21780
  const unList = [];
21526
21781
  if (bt$1) {
@@ -21550,6 +21805,9 @@ class ReportUtils {
21550
21805
  if (walker) {
21551
21806
  unList.push(bt.walkerReportService.subscribe());
21552
21807
  }
21808
+ if (strategy) {
21809
+ unList.push(bt.scheduleReportService.subscribe());
21810
+ }
21553
21811
  return compose(...unList.map((un) => () => void un()));
21554
21812
  };
21555
21813
  /**
@@ -21588,7 +21846,7 @@ class ReportUtils {
21588
21846
  * Report.disable();
21589
21847
  * ```
21590
21848
  */
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) => {
21849
+ 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
21850
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
21593
21851
  backtest: bt$1,
21594
21852
  breakeven,
@@ -21599,6 +21857,7 @@ class ReportUtils {
21599
21857
  risk,
21600
21858
  schedule,
21601
21859
  walker,
21860
+ strategy,
21602
21861
  });
21603
21862
  if (bt$1) {
21604
21863
  bt.backtestReportService.unsubscribe();
@@ -21627,6 +21886,9 @@ class ReportUtils {
21627
21886
  if (walker) {
21628
21887
  bt.walkerReportService.unsubscribe();
21629
21888
  }
21889
+ if (strategy) {
21890
+ bt.strategyReportService.unsubscribe();
21891
+ }
21630
21892
  };
21631
21893
  }
21632
21894
  }
@@ -23062,22 +23324,1243 @@ class RiskReportService {
23062
23324
  }
23063
23325
  }
23064
23326
 
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());
23327
+ /**
23328
+ * Extracts execution context timestamp for strategy event logging.
23329
+ *
23330
+ * @param self - The StrategyReportService instance to extract context from
23331
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23332
+ * @internal
23333
+ */
23334
+ const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
23335
+ if (ExecutionContextService.hasContext()) {
23336
+ const { when } = self.executionContextService.context;
23337
+ return { when: when.toISOString() };
23338
+ }
23339
+ return {
23340
+ when: "",
23341
+ };
23342
+ };
23343
+ /**
23344
+ * Service for persisting strategy management events to JSON report files.
23345
+ *
23346
+ * Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
23347
+ * partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
23348
+ * the Report class. Each event is written as a separate JSON record.
23349
+ *
23350
+ * Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
23351
+ * this service writes each event immediately to disk for audit trail purposes.
23352
+ *
23353
+ * Lifecycle:
23354
+ * - Call subscribe() to enable event logging
23355
+ * - Events are written via Report.writeData() with "strategy" category
23356
+ * - Call unsubscribe() to disable event logging
23357
+ *
23358
+ * @example
23359
+ * ```typescript
23360
+ * // Service is typically used internally by strategy management classes
23361
+ * strategyReportService.subscribe();
23362
+ *
23363
+ * // Events are logged automatically when strategy actions occur
23364
+ * await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
23365
+ * strategyName: "my-strategy",
23366
+ * exchangeName: "binance",
23367
+ * frameName: "1h"
23368
+ * });
23369
+ *
23370
+ * strategyReportService.unsubscribe();
23371
+ * ```
23372
+ *
23373
+ * @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
23374
+ * @see Report for the underlying persistence mechanism
23375
+ */
23376
+ class StrategyReportService {
23377
+ constructor() {
23378
+ this.loggerService = inject(TYPES.loggerService);
23379
+ this.executionContextService = inject(TYPES.executionContextService);
23380
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
23381
+ /**
23382
+ * Logs a cancel-scheduled event when a scheduled signal is cancelled.
23383
+ *
23384
+ * Retrieves the scheduled signal from StrategyCoreService and writes
23385
+ * the cancellation event to the report file.
23386
+ *
23387
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23388
+ * @param isBacktest - Whether this is a backtest or live trading event
23389
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23390
+ * @param cancelId - Optional identifier for the cancellation reason
23391
+ */
23392
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
23393
+ this.loggerService.log("strategyReportService cancelScheduled", {
23394
+ symbol,
23395
+ isBacktest,
23396
+ cancelId,
23397
+ });
23398
+ if (!this.subscribe.hasValue()) {
23399
+ return;
23400
+ }
23401
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23402
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
23403
+ exchangeName: context.exchangeName,
23404
+ strategyName: context.strategyName,
23405
+ frameName: context.frameName,
23406
+ });
23407
+ if (!scheduledRow) {
23408
+ return;
23409
+ }
23410
+ await Report.writeData("strategy", {
23411
+ action: "cancel-scheduled",
23412
+ cancelId,
23413
+ symbol,
23414
+ createdAt,
23415
+ }, {
23416
+ signalId: scheduledRow.id,
23417
+ exchangeName: context.exchangeName,
23418
+ frameName: context.frameName,
23419
+ strategyName: context.strategyName,
23420
+ symbol,
23421
+ walkerName: "",
23422
+ });
23423
+ };
23424
+ /**
23425
+ * Logs a close-pending event when a pending signal is closed.
23426
+ *
23427
+ * Retrieves the pending signal from StrategyCoreService and writes
23428
+ * the close event to the report file.
23429
+ *
23430
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23431
+ * @param isBacktest - Whether this is a backtest or live trading event
23432
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23433
+ * @param closeId - Optional identifier for the close reason
23434
+ */
23435
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
23436
+ this.loggerService.log("strategyReportService closePending", {
23437
+ symbol,
23438
+ isBacktest,
23439
+ closeId,
23440
+ });
23441
+ if (!this.subscribe.hasValue()) {
23442
+ return;
23443
+ }
23444
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23445
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23446
+ exchangeName: context.exchangeName,
23447
+ strategyName: context.strategyName,
23448
+ frameName: context.frameName,
23449
+ });
23450
+ if (!pendingRow) {
23451
+ return;
23452
+ }
23453
+ await Report.writeData("strategy", {
23454
+ action: "close-pending",
23455
+ closeId,
23456
+ symbol,
23457
+ createdAt,
23458
+ }, {
23459
+ signalId: pendingRow.id,
23460
+ exchangeName: context.exchangeName,
23461
+ frameName: context.frameName,
23462
+ strategyName: context.strategyName,
23463
+ symbol,
23464
+ walkerName: "",
23465
+ });
23466
+ };
23467
+ /**
23468
+ * Logs a partial-profit event when a portion of the position is closed at profit.
23469
+ *
23470
+ * Records the percentage closed and current price when partial profit-taking occurs.
23471
+ *
23472
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23473
+ * @param percentToClose - Percentage of position to close (0-100)
23474
+ * @param currentPrice - Current market price at time of partial close
23475
+ * @param isBacktest - Whether this is a backtest or live trading event
23476
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23477
+ */
23478
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23479
+ this.loggerService.log("strategyReportService partialProfit", {
23480
+ symbol,
23481
+ percentToClose,
23482
+ currentPrice,
23483
+ isBacktest,
23484
+ });
23485
+ if (!this.subscribe.hasValue()) {
23486
+ return;
23487
+ }
23488
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23489
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23490
+ exchangeName: context.exchangeName,
23491
+ strategyName: context.strategyName,
23492
+ frameName: context.frameName,
23493
+ });
23494
+ if (!pendingRow) {
23495
+ return;
23496
+ }
23497
+ await Report.writeData("strategy", {
23498
+ action: "partial-profit",
23499
+ percentToClose,
23500
+ currentPrice,
23501
+ symbol,
23502
+ createdAt,
23503
+ }, {
23504
+ signalId: pendingRow.id,
23505
+ exchangeName: context.exchangeName,
23506
+ frameName: context.frameName,
23507
+ strategyName: context.strategyName,
23508
+ symbol,
23509
+ walkerName: "",
23510
+ });
23511
+ };
23512
+ /**
23513
+ * Logs a partial-loss event when a portion of the position is closed at loss.
23514
+ *
23515
+ * Records the percentage closed and current price when partial loss-cutting occurs.
23516
+ *
23517
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23518
+ * @param percentToClose - Percentage of position to close (0-100)
23519
+ * @param currentPrice - Current market price at time of partial close
23520
+ * @param isBacktest - Whether this is a backtest or live trading event
23521
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23522
+ */
23523
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
23524
+ this.loggerService.log("strategyReportService partialLoss", {
23525
+ symbol,
23526
+ percentToClose,
23527
+ currentPrice,
23528
+ isBacktest,
23529
+ });
23530
+ if (!this.subscribe.hasValue()) {
23531
+ return;
23532
+ }
23533
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23534
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23535
+ exchangeName: context.exchangeName,
23536
+ strategyName: context.strategyName,
23537
+ frameName: context.frameName,
23538
+ });
23539
+ if (!pendingRow) {
23540
+ return;
23541
+ }
23542
+ await Report.writeData("strategy", {
23543
+ action: "partial-loss",
23544
+ percentToClose,
23545
+ currentPrice,
23546
+ symbol,
23547
+ createdAt,
23548
+ }, {
23549
+ signalId: pendingRow.id,
23550
+ exchangeName: context.exchangeName,
23551
+ frameName: context.frameName,
23552
+ strategyName: context.strategyName,
23553
+ symbol,
23554
+ walkerName: "",
23555
+ });
23556
+ };
23557
+ /**
23558
+ * Logs a trailing-stop event when the stop-loss is adjusted.
23559
+ *
23560
+ * Records the percentage shift and current price when trailing stop moves.
23561
+ *
23562
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23563
+ * @param percentShift - Percentage the stop-loss was shifted
23564
+ * @param currentPrice - Current market price at time of adjustment
23565
+ * @param isBacktest - Whether this is a backtest or live trading event
23566
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23567
+ */
23568
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23569
+ this.loggerService.log("strategyReportService trailingStop", {
23570
+ symbol,
23571
+ percentShift,
23572
+ currentPrice,
23573
+ isBacktest,
23574
+ });
23575
+ if (!this.subscribe.hasValue()) {
23576
+ return;
23577
+ }
23578
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23579
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23580
+ exchangeName: context.exchangeName,
23581
+ strategyName: context.strategyName,
23582
+ frameName: context.frameName,
23583
+ });
23584
+ if (!pendingRow) {
23585
+ return;
23586
+ }
23587
+ await Report.writeData("strategy", {
23588
+ action: "trailing-stop",
23589
+ percentShift,
23590
+ currentPrice,
23591
+ symbol,
23592
+ createdAt,
23593
+ }, {
23594
+ signalId: pendingRow.id,
23595
+ exchangeName: context.exchangeName,
23596
+ frameName: context.frameName,
23597
+ strategyName: context.strategyName,
23598
+ symbol,
23599
+ walkerName: "",
23600
+ });
23601
+ };
23602
+ /**
23603
+ * Logs a trailing-take event when the take-profit is adjusted.
23604
+ *
23605
+ * Records the percentage shift and current price when trailing take-profit moves.
23606
+ *
23607
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23608
+ * @param percentShift - Percentage the take-profit was shifted
23609
+ * @param currentPrice - Current market price at time of adjustment
23610
+ * @param isBacktest - Whether this is a backtest or live trading event
23611
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23612
+ */
23613
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
23614
+ this.loggerService.log("strategyReportService trailingTake", {
23615
+ symbol,
23616
+ percentShift,
23617
+ currentPrice,
23618
+ isBacktest,
23619
+ });
23620
+ if (!this.subscribe.hasValue()) {
23621
+ return;
23622
+ }
23623
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23624
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23625
+ exchangeName: context.exchangeName,
23626
+ strategyName: context.strategyName,
23627
+ frameName: context.frameName,
23628
+ });
23629
+ if (!pendingRow) {
23630
+ return;
23631
+ }
23632
+ await Report.writeData("strategy", {
23633
+ action: "trailing-take",
23634
+ percentShift,
23635
+ currentPrice,
23636
+ symbol,
23637
+ createdAt,
23638
+ }, {
23639
+ signalId: pendingRow.id,
23640
+ exchangeName: context.exchangeName,
23641
+ frameName: context.frameName,
23642
+ strategyName: context.strategyName,
23643
+ symbol,
23644
+ walkerName: "",
23645
+ });
23646
+ };
23647
+ /**
23648
+ * Logs a breakeven event when the stop-loss is moved to entry price.
23649
+ *
23650
+ * Records the current price when breakeven protection is activated.
23651
+ *
23652
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23653
+ * @param currentPrice - Current market price at time of breakeven activation
23654
+ * @param isBacktest - Whether this is a backtest or live trading event
23655
+ * @param context - Strategy context with strategyName, exchangeName, frameName
23656
+ */
23657
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
23658
+ this.loggerService.log("strategyReportService breakeven", {
23659
+ symbol,
23660
+ currentPrice,
23661
+ isBacktest,
23662
+ });
23663
+ if (!this.subscribe.hasValue()) {
23664
+ return;
23665
+ }
23666
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
23667
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
23668
+ exchangeName: context.exchangeName,
23669
+ strategyName: context.strategyName,
23670
+ frameName: context.frameName,
23671
+ });
23672
+ if (!pendingRow) {
23673
+ return;
23674
+ }
23675
+ await Report.writeData("strategy", {
23676
+ action: "breakeven",
23677
+ currentPrice,
23678
+ symbol,
23679
+ createdAt,
23680
+ }, {
23681
+ signalId: pendingRow.id,
23682
+ exchangeName: context.exchangeName,
23683
+ frameName: context.frameName,
23684
+ strategyName: context.strategyName,
23685
+ symbol,
23686
+ walkerName: "",
23687
+ });
23688
+ };
23689
+ /**
23690
+ * Initializes the service for event logging.
23691
+ *
23692
+ * Must be called before any events can be logged. Uses singleshot pattern
23693
+ * to ensure only one subscription exists at a time.
23694
+ *
23695
+ * @returns Cleanup function that clears the subscription when called
23696
+ */
23697
+ this.subscribe = singleshot(() => {
23698
+ this.loggerService.log("strategyReportService subscribe");
23699
+ const unCancelSchedule = strategyCommitSubject
23700
+ .filter(({ action }) => action === "cancel-scheduled")
23701
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
23702
+ exchangeName: event.exchangeName,
23703
+ frameName: event.frameName,
23704
+ strategyName: event.strategyName,
23705
+ }, event.cancelId));
23706
+ const unClosePending = strategyCommitSubject
23707
+ .filter(({ action }) => action === "close-pending")
23708
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
23709
+ exchangeName: event.exchangeName,
23710
+ frameName: event.frameName,
23711
+ strategyName: event.strategyName,
23712
+ }, event.closeId));
23713
+ const unPartialProfit = strategyCommitSubject
23714
+ .filter(({ action }) => action === "partial-profit")
23715
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23716
+ exchangeName: event.exchangeName,
23717
+ frameName: event.frameName,
23718
+ strategyName: event.strategyName,
23719
+ }));
23720
+ const unPartialLoss = strategyCommitSubject
23721
+ .filter(({ action }) => action === "partial-loss")
23722
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
23723
+ exchangeName: event.exchangeName,
23724
+ frameName: event.frameName,
23725
+ strategyName: event.strategyName,
23726
+ }));
23727
+ const unTrailingStop = strategyCommitSubject
23728
+ .filter(({ action }) => action === "trailing-stop")
23729
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23730
+ exchangeName: event.exchangeName,
23731
+ frameName: event.frameName,
23732
+ strategyName: event.strategyName,
23733
+ }));
23734
+ const unTrailingTake = strategyCommitSubject
23735
+ .filter(({ action }) => action === "trailing-take")
23736
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
23737
+ exchangeName: event.exchangeName,
23738
+ frameName: event.frameName,
23739
+ strategyName: event.strategyName,
23740
+ }));
23741
+ const unBreakeven = strategyCommitSubject
23742
+ .filter(({ action }) => action === "breakeven")
23743
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
23744
+ exchangeName: event.exchangeName,
23745
+ frameName: event.frameName,
23746
+ strategyName: event.strategyName,
23747
+ }));
23748
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
23749
+ return () => {
23750
+ disposeFn();
23751
+ this.subscribe.clear();
23752
+ };
23753
+ });
23754
+ /**
23755
+ * Stops event logging and cleans up the subscription.
23756
+ *
23757
+ * Safe to call multiple times - only clears if subscription exists.
23758
+ */
23759
+ this.unsubscribe = async () => {
23760
+ this.loggerService.log("strategyReportService unsubscribe");
23761
+ if (this.subscribe.hasValue()) {
23762
+ const lastSubscription = this.subscribe();
23763
+ lastSubscription();
23764
+ }
23765
+ };
23766
+ }
23767
+ }
23768
+
23769
+ /**
23770
+ * Extracts execution context timestamp for strategy event logging.
23771
+ *
23772
+ * @param self - The StrategyMarkdownService instance to extract context from
23773
+ * @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
23774
+ * @internal
23775
+ */
23776
+ const GET_EXECUTION_CONTEXT_FN = (self) => {
23777
+ if (ExecutionContextService.hasContext()) {
23778
+ const { when } = self.executionContextService.context;
23779
+ return { when: when.toISOString() };
23780
+ }
23781
+ return {
23782
+ when: "",
23783
+ };
23784
+ };
23785
+ /**
23786
+ * Creates a unique key for memoizing ReportStorage instances.
23787
+ *
23788
+ * Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
23789
+ *
23790
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23791
+ * @param strategyName - Name of the trading strategy
23792
+ * @param exchangeName - Name of the exchange
23793
+ * @param frameName - Timeframe name (optional, included if present)
23794
+ * @param backtest - Whether this is backtest or live mode
23795
+ * @returns Colon-separated key string for memoization
23796
+ * @internal
23797
+ */
23798
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23799
+ const parts = [symbol, strategyName, exchangeName];
23800
+ if (frameName)
23801
+ parts.push(frameName);
23802
+ parts.push(backtest ? "backtest" : "live");
23803
+ return parts.join(":");
23804
+ };
23805
+ /**
23806
+ * Creates a filename for markdown report output.
23807
+ *
23808
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
23809
+ *
23810
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23811
+ * @param strategyName - Name of the trading strategy
23812
+ * @param exchangeName - Name of the exchange
23813
+ * @param frameName - Timeframe name (indicates backtest mode if present)
23814
+ * @param timestamp - Unix timestamp in milliseconds for uniqueness
23815
+ * @returns Underscore-separated filename with .md extension
23816
+ * @internal
23817
+ */
23818
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23819
+ const parts = [symbol, strategyName, exchangeName];
23820
+ if (frameName) {
23821
+ parts.push(frameName);
23822
+ parts.push("backtest");
23823
+ }
23824
+ else
23825
+ parts.push("live");
23826
+ return `${parts.join("_")}-${timestamp}.md`;
23827
+ };
23828
+ /**
23829
+ * Maximum number of events to store per symbol-strategy pair.
23830
+ * Older events are discarded when this limit is exceeded.
23831
+ * @internal
23832
+ */
23833
+ const MAX_EVENTS = 250;
23834
+ /**
23835
+ * In-memory storage for accumulating strategy events per symbol-strategy pair.
23836
+ *
23837
+ * Maintains a rolling window of the most recent events (up to MAX_EVENTS),
23838
+ * with newer events added to the front of the list. Provides methods to:
23839
+ * - Add new events (FIFO queue with max size)
23840
+ * - Retrieve aggregated statistics
23841
+ * - Generate markdown reports
23842
+ * - Dump reports to disk
23843
+ *
23844
+ * @internal
23845
+ */
23846
+ class ReportStorage {
23847
+ /**
23848
+ * Creates a new ReportStorage instance.
23849
+ *
23850
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
23851
+ * @param strategyName - Name of the trading strategy
23852
+ * @param exchangeName - Name of the exchange
23853
+ * @param frameName - Timeframe name for backtest identification
23854
+ */
23855
+ constructor(symbol, strategyName, exchangeName, frameName) {
23856
+ this.symbol = symbol;
23857
+ this.strategyName = strategyName;
23858
+ this.exchangeName = exchangeName;
23859
+ this.frameName = frameName;
23860
+ this._eventList = [];
23861
+ }
23862
+ /**
23863
+ * Adds a new event to the storage.
23864
+ *
23865
+ * Events are added to the front of the list (most recent first).
23866
+ * If the list exceeds MAX_EVENTS, the oldest event is removed.
23867
+ *
23868
+ * @param event - The strategy event to store
23869
+ */
23870
+ addEvent(event) {
23871
+ this._eventList.unshift(event);
23872
+ if (this._eventList.length > MAX_EVENTS) {
23873
+ this._eventList.pop();
23874
+ }
23875
+ }
23876
+ /**
23877
+ * Retrieves aggregated statistics from stored events.
23878
+ *
23879
+ * Calculates counts for each action type from the event list.
23880
+ *
23881
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
23882
+ */
23883
+ async getData() {
23884
+ if (this._eventList.length === 0) {
23885
+ return {
23886
+ eventList: [],
23887
+ totalEvents: 0,
23888
+ cancelScheduledCount: 0,
23889
+ closePendingCount: 0,
23890
+ partialProfitCount: 0,
23891
+ partialLossCount: 0,
23892
+ trailingStopCount: 0,
23893
+ trailingTakeCount: 0,
23894
+ breakevenCount: 0,
23895
+ };
23896
+ }
23897
+ return {
23898
+ eventList: this._eventList,
23899
+ totalEvents: this._eventList.length,
23900
+ cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
23901
+ closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
23902
+ partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
23903
+ partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
23904
+ trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
23905
+ trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
23906
+ breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
23907
+ };
23908
+ }
23909
+ /**
23910
+ * Generates a markdown report from stored events.
23911
+ *
23912
+ * Creates a formatted markdown document containing:
23913
+ * - Header with symbol and strategy name
23914
+ * - Table of all events with configurable columns
23915
+ * - Summary statistics with counts by action type
23916
+ *
23917
+ * @param symbol - Trading pair symbol for report header
23918
+ * @param strategyName - Strategy name for report header
23919
+ * @param columns - Column configuration for the event table
23920
+ * @returns Promise resolving to formatted markdown string
23921
+ */
23922
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
23923
+ const stats = await this.getData();
23924
+ if (stats.totalEvents === 0) {
23925
+ return [
23926
+ `# Strategy Report: ${symbol}:${strategyName}`,
23927
+ "",
23928
+ "No strategy events recorded yet."
23929
+ ].join("\n");
23930
+ }
23931
+ const visibleColumns = [];
23932
+ for (const col of columns) {
23933
+ if (await col.isVisible()) {
23934
+ visibleColumns.push(col);
23935
+ }
23936
+ }
23937
+ const header = visibleColumns.map((col) => col.label);
23938
+ const separator = visibleColumns.map(() => "---");
23939
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
23940
+ const tableData = [header, separator, ...rows];
23941
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
23942
+ return [
23943
+ `# Strategy Report: ${symbol}:${strategyName}`,
23944
+ "",
23945
+ table,
23946
+ "",
23947
+ `**Total events:** ${stats.totalEvents}`,
23948
+ `- Cancel scheduled: ${stats.cancelScheduledCount}`,
23949
+ `- Close pending: ${stats.closePendingCount}`,
23950
+ `- Partial profit: ${stats.partialProfitCount}`,
23951
+ `- Partial loss: ${stats.partialLossCount}`,
23952
+ `- Trailing stop: ${stats.trailingStopCount}`,
23953
+ `- Trailing take: ${stats.trailingTakeCount}`,
23954
+ `- Breakeven: ${stats.breakevenCount}`,
23955
+ ].join("\n");
23956
+ }
23957
+ /**
23958
+ * Generates and saves a markdown report to disk.
23959
+ *
23960
+ * Creates the output directory if it doesn't exist and writes
23961
+ * the report with a timestamped filename.
23962
+ *
23963
+ * @param symbol - Trading pair symbol for report
23964
+ * @param strategyName - Strategy name for report
23965
+ * @param path - Output directory path (default: "./dump/strategy")
23966
+ * @param columns - Column configuration for the event table
23967
+ */
23968
+ async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
23969
+ const markdown = await this.getReport(symbol, strategyName, columns);
23970
+ const timestamp = Date.now();
23971
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23972
+ await Markdown.writeData("strategy", markdown, {
23973
+ path,
23974
+ file: filename,
23975
+ symbol: this.symbol,
23976
+ strategyName: this.strategyName,
23977
+ exchangeName: this.exchangeName,
23978
+ signalId: "",
23979
+ frameName: this.frameName
23980
+ });
23981
+ }
23982
+ }
23983
+ /**
23984
+ * Service for accumulating strategy management events and generating markdown reports.
23985
+ *
23986
+ * Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
23987
+ * partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
23988
+ * methods to retrieve statistics, generate reports, and export to files.
23989
+ *
23990
+ * Unlike StrategyReportService which writes each event to disk immediately,
23991
+ * this service accumulates events in ReportStorage instances (max 250 per
23992
+ * symbol-strategy pair) for batch reporting.
23993
+ *
23994
+ * Features:
23995
+ * - In-memory event accumulation with memoized storage per symbol-strategy pair
23996
+ * - Statistical data extraction (event counts by action type)
23997
+ * - Markdown report generation with configurable columns
23998
+ * - File export with timestamped filenames
23999
+ * - Selective or full cache clearing
24000
+ *
24001
+ * Lifecycle:
24002
+ * - Call subscribe() to enable event collection
24003
+ * - Events are collected automatically via cancelScheduled, closePending, etc.
24004
+ * - Use getData(), getReport(), or dump() to retrieve accumulated data
24005
+ * - Call unsubscribe() to disable collection and clear all data
24006
+ *
24007
+ * @example
24008
+ * ```typescript
24009
+ * strategyMarkdownService.subscribe();
24010
+ *
24011
+ * // Events are collected automatically during strategy execution
24012
+ * // ...
24013
+ *
24014
+ * // Get statistics
24015
+ * const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
24016
+ *
24017
+ * // Generate markdown report
24018
+ * const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
24019
+ *
24020
+ * // Export to file
24021
+ * await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
24022
+ *
24023
+ * strategyMarkdownService.unsubscribe();
24024
+ * ```
24025
+ *
24026
+ * @see StrategyReportService for immediate event persistence to JSON files
24027
+ * @see Strategy for the high-level utility class that wraps this service
24028
+ */
24029
+ class StrategyMarkdownService {
24030
+ constructor() {
24031
+ this.loggerService = inject(TYPES.loggerService);
24032
+ this.executionContextService = inject(TYPES.executionContextService);
24033
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
24034
+ /**
24035
+ * Memoized factory for ReportStorage instances.
24036
+ *
24037
+ * Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
24038
+ * Uses CREATE_KEY_FN for cache key generation.
24039
+ *
24040
+ * @internal
24041
+ */
24042
+ 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));
24043
+ /**
24044
+ * Records a cancel-scheduled event when a scheduled signal is cancelled.
24045
+ *
24046
+ * Retrieves the scheduled signal from StrategyCoreService and stores
24047
+ * the cancellation event in the appropriate ReportStorage.
24048
+ *
24049
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24050
+ * @param isBacktest - Whether this is a backtest or live trading event
24051
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24052
+ * @param cancelId - Optional identifier for the cancellation reason
24053
+ */
24054
+ this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
24055
+ this.loggerService.log("strategyMarkdownService cancelScheduled", {
24056
+ symbol,
24057
+ isBacktest,
24058
+ cancelId,
24059
+ });
24060
+ if (!this.subscribe.hasValue()) {
24061
+ return;
24062
+ }
24063
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24064
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24065
+ exchangeName: context.exchangeName,
24066
+ strategyName: context.strategyName,
24067
+ frameName: context.frameName,
24068
+ });
24069
+ if (!scheduledRow) {
24070
+ return;
24071
+ }
24072
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24073
+ storage.addEvent({
24074
+ timestamp: Date.now(),
24075
+ symbol,
24076
+ strategyName: context.strategyName,
24077
+ exchangeName: context.exchangeName,
24078
+ frameName: context.frameName,
24079
+ signalId: scheduledRow.id,
24080
+ action: "cancel-scheduled",
24081
+ cancelId,
24082
+ createdAt,
24083
+ backtest: isBacktest,
24084
+ });
24085
+ };
24086
+ /**
24087
+ * Records a close-pending event when a pending signal is closed.
24088
+ *
24089
+ * Retrieves the pending signal from StrategyCoreService and stores
24090
+ * the close event in the appropriate ReportStorage.
24091
+ *
24092
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24093
+ * @param isBacktest - Whether this is a backtest or live trading event
24094
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24095
+ * @param closeId - Optional identifier for the close reason
24096
+ */
24097
+ this.closePending = async (symbol, isBacktest, context, closeId) => {
24098
+ this.loggerService.log("strategyMarkdownService closePending", {
24099
+ symbol,
24100
+ isBacktest,
24101
+ closeId,
24102
+ });
24103
+ if (!this.subscribe.hasValue()) {
24104
+ return;
24105
+ }
24106
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24107
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24108
+ exchangeName: context.exchangeName,
24109
+ strategyName: context.strategyName,
24110
+ frameName: context.frameName,
24111
+ });
24112
+ if (!pendingRow) {
24113
+ return;
24114
+ }
24115
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24116
+ storage.addEvent({
24117
+ timestamp: Date.now(),
24118
+ symbol,
24119
+ strategyName: context.strategyName,
24120
+ exchangeName: context.exchangeName,
24121
+ frameName: context.frameName,
24122
+ signalId: pendingRow.id,
24123
+ action: "close-pending",
24124
+ closeId,
24125
+ createdAt,
24126
+ backtest: isBacktest,
24127
+ });
24128
+ };
24129
+ /**
24130
+ * Records a partial-profit event when a portion of the position is closed at profit.
24131
+ *
24132
+ * Stores the percentage closed and current price when partial profit-taking occurs.
24133
+ *
24134
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24135
+ * @param percentToClose - Percentage of position to close (0-100)
24136
+ * @param currentPrice - Current market price at time of partial close
24137
+ * @param isBacktest - Whether this is a backtest or live trading event
24138
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24139
+ */
24140
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24141
+ this.loggerService.log("strategyMarkdownService partialProfit", {
24142
+ symbol,
24143
+ percentToClose,
24144
+ currentPrice,
24145
+ isBacktest,
24146
+ });
24147
+ if (!this.subscribe.hasValue()) {
24148
+ return;
24149
+ }
24150
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24151
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24152
+ exchangeName: context.exchangeName,
24153
+ strategyName: context.strategyName,
24154
+ frameName: context.frameName,
24155
+ });
24156
+ if (!pendingRow) {
24157
+ return;
24158
+ }
24159
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24160
+ storage.addEvent({
24161
+ timestamp: Date.now(),
24162
+ symbol,
24163
+ strategyName: context.strategyName,
24164
+ exchangeName: context.exchangeName,
24165
+ frameName: context.frameName,
24166
+ signalId: pendingRow.id,
24167
+ action: "partial-profit",
24168
+ percentToClose,
24169
+ currentPrice,
24170
+ createdAt,
24171
+ backtest: isBacktest,
24172
+ });
24173
+ };
24174
+ /**
24175
+ * Records a partial-loss event when a portion of the position is closed at loss.
24176
+ *
24177
+ * Stores the percentage closed and current price when partial loss-cutting occurs.
24178
+ *
24179
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24180
+ * @param percentToClose - Percentage of position to close (0-100)
24181
+ * @param currentPrice - Current market price at time of partial close
24182
+ * @param isBacktest - Whether this is a backtest or live trading event
24183
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24184
+ */
24185
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
24186
+ this.loggerService.log("strategyMarkdownService partialLoss", {
24187
+ symbol,
24188
+ percentToClose,
24189
+ currentPrice,
24190
+ isBacktest,
24191
+ });
24192
+ if (!this.subscribe.hasValue()) {
24193
+ return;
24194
+ }
24195
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24196
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24197
+ exchangeName: context.exchangeName,
24198
+ strategyName: context.strategyName,
24199
+ frameName: context.frameName,
24200
+ });
24201
+ if (!pendingRow) {
24202
+ return;
24203
+ }
24204
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24205
+ storage.addEvent({
24206
+ timestamp: Date.now(),
24207
+ symbol,
24208
+ strategyName: context.strategyName,
24209
+ exchangeName: context.exchangeName,
24210
+ frameName: context.frameName,
24211
+ signalId: pendingRow.id,
24212
+ action: "partial-loss",
24213
+ percentToClose,
24214
+ currentPrice,
24215
+ createdAt,
24216
+ backtest: isBacktest,
24217
+ });
24218
+ };
24219
+ /**
24220
+ * Records a trailing-stop event when the stop-loss is adjusted.
24221
+ *
24222
+ * Stores the percentage shift and current price when trailing stop moves.
24223
+ *
24224
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24225
+ * @param percentShift - Percentage the stop-loss was shifted
24226
+ * @param currentPrice - Current market price at time of adjustment
24227
+ * @param isBacktest - Whether this is a backtest or live trading event
24228
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24229
+ */
24230
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24231
+ this.loggerService.log("strategyMarkdownService trailingStop", {
24232
+ symbol,
24233
+ percentShift,
24234
+ currentPrice,
24235
+ isBacktest,
24236
+ });
24237
+ if (!this.subscribe.hasValue()) {
24238
+ return;
24239
+ }
24240
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24241
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24242
+ exchangeName: context.exchangeName,
24243
+ strategyName: context.strategyName,
24244
+ frameName: context.frameName,
24245
+ });
24246
+ if (!pendingRow) {
24247
+ return;
24248
+ }
24249
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24250
+ storage.addEvent({
24251
+ timestamp: Date.now(),
24252
+ symbol,
24253
+ strategyName: context.strategyName,
24254
+ exchangeName: context.exchangeName,
24255
+ frameName: context.frameName,
24256
+ signalId: pendingRow.id,
24257
+ action: "trailing-stop",
24258
+ percentShift,
24259
+ currentPrice,
24260
+ createdAt,
24261
+ backtest: isBacktest,
24262
+ });
24263
+ };
24264
+ /**
24265
+ * Records a trailing-take event when the take-profit is adjusted.
24266
+ *
24267
+ * Stores the percentage shift and current price when trailing take-profit moves.
24268
+ *
24269
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24270
+ * @param percentShift - Percentage the take-profit was shifted
24271
+ * @param currentPrice - Current market price at time of adjustment
24272
+ * @param isBacktest - Whether this is a backtest or live trading event
24273
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24274
+ */
24275
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
24276
+ this.loggerService.log("strategyMarkdownService trailingTake", {
24277
+ symbol,
24278
+ percentShift,
24279
+ currentPrice,
24280
+ isBacktest,
24281
+ });
24282
+ if (!this.subscribe.hasValue()) {
24283
+ return;
24284
+ }
24285
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24286
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24287
+ exchangeName: context.exchangeName,
24288
+ strategyName: context.strategyName,
24289
+ frameName: context.frameName,
24290
+ });
24291
+ if (!pendingRow) {
24292
+ return;
24293
+ }
24294
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24295
+ storage.addEvent({
24296
+ timestamp: Date.now(),
24297
+ symbol,
24298
+ strategyName: context.strategyName,
24299
+ exchangeName: context.exchangeName,
24300
+ frameName: context.frameName,
24301
+ signalId: pendingRow.id,
24302
+ action: "trailing-take",
24303
+ percentShift,
24304
+ currentPrice,
24305
+ createdAt,
24306
+ backtest: isBacktest,
24307
+ });
24308
+ };
24309
+ /**
24310
+ * Records a breakeven event when the stop-loss is moved to entry price.
24311
+ *
24312
+ * Stores the current price when breakeven protection is activated.
24313
+ *
24314
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24315
+ * @param currentPrice - Current market price at time of breakeven activation
24316
+ * @param isBacktest - Whether this is a backtest or live trading event
24317
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24318
+ */
24319
+ this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
24320
+ this.loggerService.log("strategyMarkdownService breakeven", {
24321
+ symbol,
24322
+ currentPrice,
24323
+ isBacktest,
24324
+ });
24325
+ if (!this.subscribe.hasValue()) {
24326
+ return;
24327
+ }
24328
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
24329
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
24330
+ exchangeName: context.exchangeName,
24331
+ strategyName: context.strategyName,
24332
+ frameName: context.frameName,
24333
+ });
24334
+ if (!pendingRow) {
24335
+ return;
24336
+ }
24337
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
24338
+ storage.addEvent({
24339
+ timestamp: Date.now(),
24340
+ symbol,
24341
+ strategyName: context.strategyName,
24342
+ exchangeName: context.exchangeName,
24343
+ frameName: context.frameName,
24344
+ signalId: pendingRow.id,
24345
+ action: "breakeven",
24346
+ currentPrice,
24347
+ createdAt,
24348
+ backtest: isBacktest,
24349
+ });
24350
+ };
24351
+ /**
24352
+ * Retrieves aggregated statistics from accumulated strategy events.
24353
+ *
24354
+ * Returns counts for each action type and the full event list from the
24355
+ * ReportStorage for the specified symbol-strategy pair.
24356
+ *
24357
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24358
+ * @param strategyName - Name of the trading strategy
24359
+ * @param exchangeName - Name of the exchange
24360
+ * @param frameName - Timeframe name for backtest identification
24361
+ * @param backtest - Whether to get backtest or live data
24362
+ * @returns Promise resolving to StrategyStatisticsModel with event list and counts
24363
+ * @throws Error if service not initialized (subscribe() not called)
24364
+ */
24365
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
24366
+ this.loggerService.log("strategyMarkdownService getData", {
24367
+ symbol,
24368
+ strategyName,
24369
+ exchangeName,
24370
+ frameName,
24371
+ backtest,
24372
+ });
24373
+ if (!this.subscribe.hasValue()) {
24374
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
24375
+ }
24376
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24377
+ return storage.getData();
24378
+ };
24379
+ /**
24380
+ * Generates a markdown report from accumulated strategy events.
24381
+ *
24382
+ * Creates a formatted markdown document containing:
24383
+ * - Header with symbol and strategy name
24384
+ * - Table of all events with configurable columns
24385
+ * - Summary statistics with counts by action type
24386
+ *
24387
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24388
+ * @param strategyName - Name of the trading strategy
24389
+ * @param exchangeName - Name of the exchange
24390
+ * @param frameName - Timeframe name for backtest identification
24391
+ * @param backtest - Whether to get backtest or live data
24392
+ * @param columns - Column configuration for the event table
24393
+ * @returns Promise resolving to formatted markdown string
24394
+ * @throws Error if service not initialized (subscribe() not called)
24395
+ */
24396
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
24397
+ this.loggerService.log("strategyMarkdownService getReport", {
24398
+ symbol,
24399
+ strategyName,
24400
+ exchangeName,
24401
+ frameName,
24402
+ backtest,
24403
+ });
24404
+ if (!this.subscribe.hasValue()) {
24405
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
24406
+ }
24407
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24408
+ return storage.getReport(symbol, strategyName, columns);
24409
+ };
24410
+ /**
24411
+ * Generates and saves a markdown report to disk.
24412
+ *
24413
+ * Creates the output directory if it doesn't exist and writes
24414
+ * the report with a timestamped filename via Markdown.writeData().
24415
+ *
24416
+ * Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
24417
+ *
24418
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24419
+ * @param strategyName - Name of the trading strategy
24420
+ * @param exchangeName - Name of the exchange
24421
+ * @param frameName - Timeframe name for backtest identification
24422
+ * @param backtest - Whether to dump backtest or live data
24423
+ * @param path - Output directory path (default: "./dump/strategy")
24424
+ * @param columns - Column configuration for the event table
24425
+ * @returns Promise that resolves when file is written
24426
+ * @throws Error if service not initialized (subscribe() not called)
24427
+ */
24428
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
24429
+ this.loggerService.log("strategyMarkdownService dump", {
24430
+ symbol,
24431
+ strategyName,
24432
+ exchangeName,
24433
+ frameName,
24434
+ backtest,
24435
+ path,
24436
+ });
24437
+ if (!this.subscribe.hasValue()) {
24438
+ throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
24439
+ }
24440
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
24441
+ await storage.dump(symbol, strategyName, path, columns);
24442
+ };
24443
+ /**
24444
+ * Clears accumulated events from storage.
24445
+ *
24446
+ * Can clear either a specific symbol-strategy pair or all stored data.
24447
+ *
24448
+ * @param payload - Optional filter to clear specific storage. If omitted, clears all.
24449
+ * @param payload.symbol - Trading pair symbol
24450
+ * @param payload.strategyName - Strategy name
24451
+ * @param payload.exchangeName - Exchange name
24452
+ * @param payload.frameName - Frame name
24453
+ * @param payload.backtest - Backtest mode flag
24454
+ */
24455
+ this.clear = async (payload) => {
24456
+ this.loggerService.log("strategyMarkdownService clear", { payload });
24457
+ if (payload) {
24458
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24459
+ this.getStorage.clear(key);
24460
+ }
24461
+ else {
24462
+ this.getStorage.clear();
24463
+ }
24464
+ };
24465
+ /**
24466
+ * Initializes the service for event collection.
24467
+ *
24468
+ * Must be called before any events can be collected or reports generated.
24469
+ * Uses singleshot pattern to ensure only one subscription exists at a time.
24470
+ *
24471
+ * @returns Cleanup function that clears the subscription and all accumulated data
24472
+ */
24473
+ this.subscribe = singleshot(() => {
24474
+ this.loggerService.log("strategyMarkdownService subscribe");
24475
+ const unCancelSchedule = strategyCommitSubject
24476
+ .filter(({ action }) => action === "cancel-scheduled")
24477
+ .connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
24478
+ exchangeName: event.exchangeName,
24479
+ frameName: event.frameName,
24480
+ strategyName: event.strategyName,
24481
+ }, event.cancelId));
24482
+ const unClosePending = strategyCommitSubject
24483
+ .filter(({ action }) => action === "close-pending")
24484
+ .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
24485
+ exchangeName: event.exchangeName,
24486
+ frameName: event.frameName,
24487
+ strategyName: event.strategyName,
24488
+ }, event.closeId));
24489
+ const unPartialProfit = strategyCommitSubject
24490
+ .filter(({ action }) => action === "partial-profit")
24491
+ .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24492
+ exchangeName: event.exchangeName,
24493
+ frameName: event.frameName,
24494
+ strategyName: event.strategyName,
24495
+ }));
24496
+ const unPartialLoss = strategyCommitSubject
24497
+ .filter(({ action }) => action === "partial-loss")
24498
+ .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24499
+ exchangeName: event.exchangeName,
24500
+ frameName: event.frameName,
24501
+ strategyName: event.strategyName,
24502
+ }));
24503
+ const unTrailingStop = strategyCommitSubject
24504
+ .filter(({ action }) => action === "trailing-stop")
24505
+ .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24506
+ exchangeName: event.exchangeName,
24507
+ frameName: event.frameName,
24508
+ strategyName: event.strategyName,
24509
+ }));
24510
+ const unTrailingTake = strategyCommitSubject
24511
+ .filter(({ action }) => action === "trailing-take")
24512
+ .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24513
+ exchangeName: event.exchangeName,
24514
+ frameName: event.frameName,
24515
+ strategyName: event.strategyName,
24516
+ }));
24517
+ const unBreakeven = strategyCommitSubject
24518
+ .filter(({ action }) => action === "breakeven")
24519
+ .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24520
+ exchangeName: event.exchangeName,
24521
+ frameName: event.frameName,
24522
+ strategyName: event.strategyName,
24523
+ }));
24524
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24525
+ return () => {
24526
+ disposeFn();
24527
+ this.subscribe.clear();
24528
+ this.clear();
24529
+ };
24530
+ });
24531
+ /**
24532
+ * Stops event collection and clears all accumulated data.
24533
+ *
24534
+ * Invokes the cleanup function returned by subscribe(), which clears
24535
+ * both the subscription and all ReportStorage instances.
24536
+ * Safe to call multiple times - only acts if subscription exists.
24537
+ */
24538
+ this.unsubscribe = async () => {
24539
+ this.loggerService.log("strategyMarkdownService unsubscribe");
24540
+ if (this.subscribe.hasValue()) {
24541
+ const lastSubscription = this.subscribe();
24542
+ lastSubscription();
24543
+ }
24544
+ };
24545
+ }
24546
+ }
24547
+
24548
+ {
24549
+ provide(TYPES.loggerService, () => new LoggerService());
24550
+ }
24551
+ {
24552
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
24553
+ provide(TYPES.methodContextService, () => new MethodContextService());
24554
+ }
24555
+ {
24556
+ provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
24557
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
24558
+ provide(TYPES.frameConnectionService, () => new FrameConnectionService());
24559
+ provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
24560
+ provide(TYPES.riskConnectionService, () => new RiskConnectionService());
24561
+ provide(TYPES.actionConnectionService, () => new ActionConnectionService());
24562
+ provide(TYPES.partialConnectionService, () => new PartialConnectionService());
24563
+ provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
23081
24564
  }
23082
24565
  {
23083
24566
  provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
@@ -23125,6 +24608,7 @@ class RiskReportService {
23125
24608
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
23126
24609
  provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
23127
24610
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
24611
+ provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
23128
24612
  }
23129
24613
  {
23130
24614
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -23136,6 +24620,7 @@ class RiskReportService {
23136
24620
  provide(TYPES.partialReportService, () => new PartialReportService());
23137
24621
  provide(TYPES.breakevenReportService, () => new BreakevenReportService());
23138
24622
  provide(TYPES.riskReportService, () => new RiskReportService());
24623
+ provide(TYPES.strategyReportService, () => new StrategyReportService());
23139
24624
  }
23140
24625
  {
23141
24626
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -23212,6 +24697,7 @@ const markdownServices = {
23212
24697
  partialMarkdownService: inject(TYPES.partialMarkdownService),
23213
24698
  breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
23214
24699
  riskMarkdownService: inject(TYPES.riskMarkdownService),
24700
+ strategyMarkdownService: inject(TYPES.strategyMarkdownService),
23215
24701
  };
23216
24702
  const reportServices = {
23217
24703
  backtestReportService: inject(TYPES.backtestReportService),
@@ -23223,6 +24709,7 @@ const reportServices = {
23223
24709
  partialReportService: inject(TYPES.partialReportService),
23224
24710
  breakevenReportService: inject(TYPES.breakevenReportService),
23225
24711
  riskReportService: inject(TYPES.riskReportService),
24712
+ strategyReportService: inject(TYPES.strategyReportService),
23226
24713
  };
23227
24714
  const validationServices = {
23228
24715
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -25301,6 +26788,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
25301
26788
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
25302
26789
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
25303
26790
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
26791
+ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
26792
+ const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
25304
26793
  /**
25305
26794
  * Subscribes to all signal events with queued async processing.
25306
26795
  *
@@ -26330,6 +27819,78 @@ function listenActivePingOnce(filterFn, fn) {
26330
27819
  bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
26331
27820
  return activePingSubject.filter(filterFn).once(fn);
26332
27821
  }
27822
+ /**
27823
+ * Subscribes to strategy management events with queued async processing.
27824
+ *
27825
+ * Emits when strategy management actions are executed:
27826
+ * - cancel-scheduled: Scheduled signal cancelled
27827
+ * - close-pending: Pending signal closed
27828
+ * - partial-profit: Partial close at profit level
27829
+ * - partial-loss: Partial close at loss level
27830
+ * - trailing-stop: Stop-loss adjusted
27831
+ * - trailing-take: Take-profit adjusted
27832
+ * - breakeven: Stop-loss moved to entry price
27833
+ *
27834
+ * Events are processed sequentially in order received, even if callback is async.
27835
+ * Uses queued wrapper to prevent concurrent execution of the callback.
27836
+ *
27837
+ * @param fn - Callback function to handle strategy commit events
27838
+ * @returns Unsubscribe function to stop listening
27839
+ *
27840
+ * @example
27841
+ * ```typescript
27842
+ * import { listenStrategyCommit } from "./function/event";
27843
+ *
27844
+ * const unsubscribe = listenStrategyCommit((event) => {
27845
+ * console.log(`[${event.action}] ${event.symbol}`);
27846
+ * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
27847
+ * if (event.action === "partial-profit") {
27848
+ * console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
27849
+ * }
27850
+ * });
27851
+ *
27852
+ * // Later: stop listening
27853
+ * unsubscribe();
27854
+ * ```
27855
+ */
27856
+ function listenStrategyCommit(fn) {
27857
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
27858
+ return strategyCommitSubject.subscribe(queued(async (event) => fn(event)));
27859
+ }
27860
+ /**
27861
+ * Subscribes to filtered strategy management events with one-time execution.
27862
+ *
27863
+ * Listens for events matching the filter predicate, then executes callback once
27864
+ * and automatically unsubscribes. Useful for waiting for specific strategy actions.
27865
+ *
27866
+ * @param filterFn - Predicate to filter which events trigger the callback
27867
+ * @param fn - Callback function to handle the filtered event (called only once)
27868
+ * @returns Unsubscribe function to cancel the listener before it fires
27869
+ *
27870
+ * @example
27871
+ * ```typescript
27872
+ * import { listenStrategyCommitOnce } from "./function/event";
27873
+ *
27874
+ * // Wait for first trailing stop adjustment
27875
+ * listenStrategyCommitOnce(
27876
+ * (event) => event.action === "trailing-stop",
27877
+ * (event) => console.log("Trailing stop adjusted:", event.symbol)
27878
+ * );
27879
+ *
27880
+ * // Wait for breakeven on BTCUSDT
27881
+ * const cancel = listenStrategyCommitOnce(
27882
+ * (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
27883
+ * (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
27884
+ * );
27885
+ *
27886
+ * // Cancel if needed before event fires
27887
+ * cancel();
27888
+ * ```
27889
+ */
27890
+ function listenStrategyCommitOnce(filterFn, fn) {
27891
+ bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
27892
+ return strategyCommitSubject.filter(filterFn).once(fn);
27893
+ }
26333
27894
 
26334
27895
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
26335
27896
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -31389,6 +32950,201 @@ class BreakevenUtils {
31389
32950
  */
31390
32951
  const Breakeven = new BreakevenUtils();
31391
32952
 
32953
+ const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
32954
+ const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
32955
+ const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
32956
+ /**
32957
+ * Utility class for accessing strategy management reports and statistics.
32958
+ *
32959
+ * Provides static-like methods (via singleton instance) to retrieve data
32960
+ * accumulated by StrategyMarkdownService from strategy management events.
32961
+ *
32962
+ * Features:
32963
+ * - Statistical data extraction (event counts by action type)
32964
+ * - Markdown report generation with event tables
32965
+ * - File export to disk
32966
+ *
32967
+ * Data source:
32968
+ * - StrategyMarkdownService receives events via direct method calls
32969
+ * - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
32970
+ * - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
32971
+ * trailing-stop, trailing-take, breakeven
32972
+ *
32973
+ * @example
32974
+ * ```typescript
32975
+ * import { Strategy } from "./classes/Strategy";
32976
+ *
32977
+ * // Get statistical data for BTCUSDT:my-strategy
32978
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
32979
+ * console.log(`Total events: ${stats.totalEvents}`);
32980
+ * console.log(`Partial profit events: ${stats.partialProfitCount}`);
32981
+ *
32982
+ * // Generate markdown report
32983
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
32984
+ * console.log(markdown); // Formatted table with all events
32985
+ *
32986
+ * // Export report to file
32987
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
32988
+ * ```
32989
+ */
32990
+ class StrategyUtils {
32991
+ constructor() {
32992
+ /**
32993
+ * Retrieves statistical data from accumulated strategy events.
32994
+ *
32995
+ * Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
32996
+ * Returns aggregated metrics calculated from all strategy events.
32997
+ *
32998
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
32999
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33000
+ * @param backtest - Whether to get backtest data (default: false)
33001
+ * @returns Promise resolving to StrategyStatisticsModel object with counts and event list
33002
+ *
33003
+ * @example
33004
+ * ```typescript
33005
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33006
+ *
33007
+ * console.log(`Total events: ${stats.totalEvents}`);
33008
+ * console.log(`Partial profit: ${stats.partialProfitCount}`);
33009
+ * console.log(`Trailing stop: ${stats.trailingStopCount}`);
33010
+ *
33011
+ * // Iterate through all events
33012
+ * for (const event of stats.eventList) {
33013
+ * console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
33014
+ * }
33015
+ * ```
33016
+ */
33017
+ this.getData = async (symbol, context, backtest = false) => {
33018
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
33019
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
33020
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
33021
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
33022
+ {
33023
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33024
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
33025
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
33026
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
33027
+ }
33028
+ return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
33029
+ };
33030
+ /**
33031
+ * Generates markdown report with all strategy events for a symbol-strategy pair.
33032
+ *
33033
+ * Creates formatted table containing:
33034
+ * - Symbol
33035
+ * - Strategy
33036
+ * - Signal ID
33037
+ * - Action (cancel-scheduled, close-pending, partial-profit, etc.)
33038
+ * - Price
33039
+ * - Percent values (% To Close, % Shift)
33040
+ * - Cancel/Close IDs
33041
+ * - Timestamp (ISO 8601)
33042
+ * - Mode (Backtest/Live)
33043
+ *
33044
+ * Also includes summary statistics at the end with counts by action type.
33045
+ *
33046
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33047
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33048
+ * @param backtest - Whether to get backtest data (default: false)
33049
+ * @param columns - Optional columns configuration for the report
33050
+ * @returns Promise resolving to markdown formatted report string
33051
+ *
33052
+ * @example
33053
+ * ```typescript
33054
+ * const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33055
+ * console.log(markdown);
33056
+ *
33057
+ * // Output:
33058
+ * // # Strategy Report: BTCUSDT:my-strategy
33059
+ * //
33060
+ * // | Symbol | Strategy | Signal ID | Action | Price | ... |
33061
+ * // | --- | --- | --- | --- | --- | ... |
33062
+ * // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
33063
+ * //
33064
+ * // **Total events:** 5
33065
+ * // - Cancel scheduled: 0
33066
+ * // - Close pending: 1
33067
+ * // - Partial profit: 2
33068
+ * // ...
33069
+ * ```
33070
+ */
33071
+ this.getReport = async (symbol, context, backtest = false, columns) => {
33072
+ bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
33073
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
33074
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
33075
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
33076
+ {
33077
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33078
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
33079
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
33080
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
33081
+ }
33082
+ return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
33083
+ };
33084
+ /**
33085
+ * Generates and saves markdown report to file.
33086
+ *
33087
+ * Creates directory if it doesn't exist.
33088
+ * Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
33089
+ *
33090
+ * Delegates to StrategyMarkdownService.dump() which:
33091
+ * 1. Generates markdown report via getReport()
33092
+ * 2. Creates output directory (recursive mkdir)
33093
+ * 3. Writes file with UTF-8 encoding
33094
+ * 4. Logs success/failure to console
33095
+ *
33096
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
33097
+ * @param context - Strategy context with strategyName, exchangeName, frameName
33098
+ * @param backtest - Whether to dump backtest data (default: false)
33099
+ * @param path - Output directory path (default: "./dump/strategy")
33100
+ * @param columns - Optional columns configuration for the report
33101
+ * @returns Promise that resolves when file is written
33102
+ *
33103
+ * @example
33104
+ * ```typescript
33105
+ * // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
33106
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
33107
+ *
33108
+ * // Save to custom path
33109
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
33110
+ *
33111
+ * // After multiple symbols backtested, export all reports
33112
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
33113
+ * await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
33114
+ * }
33115
+ * ```
33116
+ */
33117
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
33118
+ bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
33119
+ bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
33120
+ bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
33121
+ context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
33122
+ {
33123
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
33124
+ riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
33125
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
33126
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
33127
+ }
33128
+ await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
33129
+ };
33130
+ }
33131
+ }
33132
+ /**
33133
+ * Global singleton instance of StrategyUtils.
33134
+ * Provides static-like access to strategy management reporting methods.
33135
+ *
33136
+ * @example
33137
+ * ```typescript
33138
+ * import { Strategy } from "backtest-kit";
33139
+ *
33140
+ * // Usage same as StrategyUtils methods
33141
+ * const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33142
+ * const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33143
+ * await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
33144
+ * ```
33145
+ */
33146
+ const Strategy = new StrategyUtils();
33147
+
31392
33148
  /**
31393
33149
  * Rounds a price to the appropriate precision based on the tick size.
31394
33150
  *
@@ -31521,4 +33277,4 @@ const set = (object, path, value) => {
31521
33277
  }
31522
33278
  };
31523
33279
 
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 };
33280
+ 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 };