backtest-kit 1.4.11 → 1.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -7,6 +7,7 @@ var fs = require('fs/promises');
7
7
  var path = require('path');
8
8
  var crypto = require('crypto');
9
9
  var os = require('os');
10
+ var fs$1 = require('fs');
10
11
 
11
12
  const GLOBAL_CONFIG = {
12
13
  /**
@@ -162,6 +163,7 @@ const markdownServices$1 = {
162
163
  walkerMarkdownService: Symbol('walkerMarkdownService'),
163
164
  heatMarkdownService: Symbol('heatMarkdownService'),
164
165
  partialMarkdownService: Symbol('partialMarkdownService'),
166
+ outlineMarkdownService: Symbol('outlineMarkdownService'),
165
167
  };
166
168
  const validationServices$1 = {
167
169
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -10462,6 +10464,11 @@ const columns = [
10462
10464
  label: "Symbol",
10463
10465
  format: (data) => data.symbol,
10464
10466
  },
10467
+ {
10468
+ key: "strategyName",
10469
+ label: "Strategy",
10470
+ format: (data) => data.strategyName,
10471
+ },
10465
10472
  {
10466
10473
  key: "signalId",
10467
10474
  label: "Signal ID",
@@ -10496,7 +10503,7 @@ const columns = [
10496
10503
  /** Maximum number of events to store in partial reports */
10497
10504
  const MAX_EVENTS = 250;
10498
10505
  /**
10499
- * Storage class for accumulating partial profit/loss events per symbol.
10506
+ * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
10500
10507
  * Maintains a chronological list of profit and loss level events.
10501
10508
  */
10502
10509
  class ReportStorage {
@@ -10507,17 +10514,17 @@ class ReportStorage {
10507
10514
  /**
10508
10515
  * Adds a profit event to the storage.
10509
10516
  *
10510
- * @param symbol - Trading pair symbol
10511
10517
  * @param data - Signal row data
10512
10518
  * @param currentPrice - Current market price
10513
10519
  * @param level - Profit level reached
10514
10520
  * @param backtest - True if backtest mode
10515
10521
  */
10516
- addProfitEvent(symbol, data, currentPrice, level, backtest, timestamp) {
10522
+ addProfitEvent(data, currentPrice, level, backtest, timestamp) {
10517
10523
  this._eventList.push({
10518
10524
  timestamp,
10519
10525
  action: "profit",
10520
- symbol,
10526
+ symbol: data.symbol,
10527
+ strategyName: data.strategyName,
10521
10528
  signalId: data.id,
10522
10529
  position: data.position,
10523
10530
  currentPrice,
@@ -10532,17 +10539,17 @@ class ReportStorage {
10532
10539
  /**
10533
10540
  * Adds a loss event to the storage.
10534
10541
  *
10535
- * @param symbol - Trading pair symbol
10536
10542
  * @param data - Signal row data
10537
10543
  * @param currentPrice - Current market price
10538
10544
  * @param level - Loss level reached
10539
10545
  * @param backtest - True if backtest mode
10540
10546
  */
10541
- addLossEvent(symbol, data, currentPrice, level, backtest, timestamp) {
10547
+ addLossEvent(data, currentPrice, level, backtest, timestamp) {
10542
10548
  this._eventList.push({
10543
10549
  timestamp,
10544
10550
  action: "loss",
10545
- symbol,
10551
+ symbol: data.symbol,
10552
+ strategyName: data.strategyName,
10546
10553
  signalId: data.id,
10547
10554
  position: data.position,
10548
10555
  currentPrice,
@@ -10578,35 +10585,37 @@ class ReportStorage {
10578
10585
  };
10579
10586
  }
10580
10587
  /**
10581
- * Generates markdown report with all partial events for a symbol (View).
10588
+ * Generates markdown report with all partial events for a symbol-strategy pair (View).
10582
10589
  *
10583
10590
  * @param symbol - Trading pair symbol
10591
+ * @param strategyName - Strategy name
10584
10592
  * @returns Markdown formatted report with all events
10585
10593
  */
10586
- async getReport(symbol) {
10594
+ async getReport(symbol, strategyName) {
10587
10595
  const stats = await this.getData();
10588
10596
  if (stats.totalEvents === 0) {
10589
- return functoolsKit.str.newline(`# Partial Profit/Loss Report: ${symbol}`, "", "No partial profit/loss events recorded yet.");
10597
+ return functoolsKit.str.newline(`# Partial Profit/Loss Report: ${symbol}:${strategyName}`, "", "No partial profit/loss events recorded yet.");
10590
10598
  }
10591
10599
  const header = columns.map((col) => col.label);
10592
10600
  const separator = columns.map(() => "---");
10593
10601
  const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
10594
10602
  const tableData = [header, separator, ...rows];
10595
10603
  const table = functoolsKit.str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
10596
- return functoolsKit.str.newline(`# Partial Profit/Loss Report: ${symbol}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Profit events:** ${stats.totalProfit}`, `**Loss events:** ${stats.totalLoss}`);
10604
+ return functoolsKit.str.newline(`# Partial Profit/Loss Report: ${symbol}:${strategyName}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Profit events:** ${stats.totalProfit}`, `**Loss events:** ${stats.totalLoss}`);
10597
10605
  }
10598
10606
  /**
10599
- * Saves symbol report to disk.
10607
+ * Saves symbol-strategy report to disk.
10600
10608
  *
10601
10609
  * @param symbol - Trading pair symbol
10610
+ * @param strategyName - Strategy name
10602
10611
  * @param path - Directory path to save report (default: "./dump/partial")
10603
10612
  */
10604
- async dump(symbol, path$1 = "./dump/partial") {
10605
- const markdown = await this.getReport(symbol);
10613
+ async dump(symbol, strategyName, path$1 = "./dump/partial") {
10614
+ const markdown = await this.getReport(symbol, strategyName);
10606
10615
  try {
10607
10616
  const dir = path.join(process.cwd(), path$1);
10608
10617
  await fs.mkdir(dir, { recursive: true });
10609
- const filename = `${symbol}.md`;
10618
+ const filename = `${symbol}_${strategyName}.md`;
10610
10619
  const filepath = path.join(dir, filename);
10611
10620
  await fs.writeFile(filepath, markdown, "utf-8");
10612
10621
  console.log(`Partial profit/loss report saved: ${filepath}`);
@@ -10621,10 +10630,10 @@ class ReportStorage {
10621
10630
  *
10622
10631
  * Features:
10623
10632
  * - Listens to partial profit and loss events via partialProfitSubject/partialLossSubject
10624
- * - Accumulates all events (profit, loss) per symbol
10633
+ * - Accumulates all events (profit, loss) per symbol-strategy pair
10625
10634
  * - Generates markdown tables with detailed event information
10626
10635
  * - Provides statistics (total profit/loss events)
10627
- * - Saves reports to disk in dump/partial/{symbol}.md
10636
+ * - Saves reports to disk in dump/partial/{symbol}_{strategyName}.md
10628
10637
  *
10629
10638
  * @example
10630
10639
  * ```typescript
@@ -10634,7 +10643,7 @@ class ReportStorage {
10634
10643
  * // No manual callback setup needed
10635
10644
  *
10636
10645
  * // Later: generate and save report
10637
- * await service.dump("BTCUSDT");
10646
+ * await service.dump("BTCUSDT", "my-strategy");
10638
10647
  * ```
10639
10648
  */
10640
10649
  class PartialMarkdownService {
@@ -10642,10 +10651,10 @@ class PartialMarkdownService {
10642
10651
  /** Logger service for debug output */
10643
10652
  this.loggerService = inject(TYPES.loggerService);
10644
10653
  /**
10645
- * Memoized function to get or create ReportStorage for a symbol.
10646
- * Each symbol gets its own isolated storage instance.
10654
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
10655
+ * Each symbol-strategy combination gets its own isolated storage instance.
10647
10656
  */
10648
- this.getStorage = functoolsKit.memoize(([symbol]) => `${symbol}`, () => new ReportStorage());
10657
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => JSON.stringify([symbol, strategyName]), () => new ReportStorage());
10649
10658
  /**
10650
10659
  * Processes profit events and accumulates them.
10651
10660
  * Should be called from partialProfitSubject subscription.
@@ -10662,8 +10671,8 @@ class PartialMarkdownService {
10662
10671
  this.loggerService.log("partialMarkdownService tickProfit", {
10663
10672
  data,
10664
10673
  });
10665
- const storage = this.getStorage(data.symbol);
10666
- storage.addProfitEvent(data.symbol, data.data, data.currentPrice, data.level, data.backtest, data.timestamp);
10674
+ const storage = this.getStorage(data.symbol, data.data.strategyName);
10675
+ storage.addProfitEvent(data.data, data.currentPrice, data.level, data.backtest, data.timestamp);
10667
10676
  };
10668
10677
  /**
10669
10678
  * Processes loss events and accumulates them.
@@ -10681,101 +10690,113 @@ class PartialMarkdownService {
10681
10690
  this.loggerService.log("partialMarkdownService tickLoss", {
10682
10691
  data,
10683
10692
  });
10684
- const storage = this.getStorage(data.symbol);
10685
- storage.addLossEvent(data.symbol, data.data, data.currentPrice, data.level, data.backtest, data.timestamp);
10693
+ const storage = this.getStorage(data.symbol, data.data.strategyName);
10694
+ storage.addLossEvent(data.data, data.currentPrice, data.level, data.backtest, data.timestamp);
10686
10695
  };
10687
10696
  /**
10688
- * Gets statistical data from all partial profit/loss events for a symbol.
10697
+ * Gets statistical data from all partial profit/loss events for a symbol-strategy pair.
10689
10698
  * Delegates to ReportStorage.getData().
10690
10699
  *
10691
10700
  * @param symbol - Trading pair symbol to get data for
10701
+ * @param strategyName - Strategy name to get data for
10692
10702
  * @returns Statistical data object with all metrics
10693
10703
  *
10694
10704
  * @example
10695
10705
  * ```typescript
10696
10706
  * const service = new PartialMarkdownService();
10697
- * const stats = await service.getData("BTCUSDT");
10707
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
10698
10708
  * console.log(stats.totalProfit, stats.totalLoss);
10699
10709
  * ```
10700
10710
  */
10701
- this.getData = async (symbol) => {
10711
+ this.getData = async (symbol, strategyName) => {
10702
10712
  this.loggerService.log("partialMarkdownService getData", {
10703
10713
  symbol,
10714
+ strategyName,
10704
10715
  });
10705
- const storage = this.getStorage(symbol);
10716
+ const storage = this.getStorage(symbol, strategyName);
10706
10717
  return storage.getData();
10707
10718
  };
10708
10719
  /**
10709
- * Generates markdown report with all partial events for a symbol.
10720
+ * Generates markdown report with all partial events for a symbol-strategy pair.
10710
10721
  * Delegates to ReportStorage.getReport().
10711
10722
  *
10712
10723
  * @param symbol - Trading pair symbol to generate report for
10724
+ * @param strategyName - Strategy name to generate report for
10713
10725
  * @returns Markdown formatted report string with table of all events
10714
10726
  *
10715
10727
  * @example
10716
10728
  * ```typescript
10717
10729
  * const service = new PartialMarkdownService();
10718
- * const markdown = await service.getReport("BTCUSDT");
10730
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
10719
10731
  * console.log(markdown);
10720
10732
  * ```
10721
10733
  */
10722
- this.getReport = async (symbol) => {
10734
+ this.getReport = async (symbol, strategyName) => {
10723
10735
  this.loggerService.log("partialMarkdownService getReport", {
10724
10736
  symbol,
10737
+ strategyName,
10725
10738
  });
10726
- const storage = this.getStorage(symbol);
10727
- return storage.getReport(symbol);
10739
+ const storage = this.getStorage(symbol, strategyName);
10740
+ return storage.getReport(symbol, strategyName);
10728
10741
  };
10729
10742
  /**
10730
- * Saves symbol report to disk.
10743
+ * Saves symbol-strategy report to disk.
10731
10744
  * Creates directory if it doesn't exist.
10732
10745
  * Delegates to ReportStorage.dump().
10733
10746
  *
10734
10747
  * @param symbol - Trading pair symbol to save report for
10748
+ * @param strategyName - Strategy name to save report for
10735
10749
  * @param path - Directory path to save report (default: "./dump/partial")
10736
10750
  *
10737
10751
  * @example
10738
10752
  * ```typescript
10739
10753
  * const service = new PartialMarkdownService();
10740
10754
  *
10741
- * // Save to default path: ./dump/partial/BTCUSDT.md
10742
- * await service.dump("BTCUSDT");
10755
+ * // Save to default path: ./dump/partial/BTCUSDT_my-strategy.md
10756
+ * await service.dump("BTCUSDT", "my-strategy");
10743
10757
  *
10744
- * // Save to custom path: ./custom/path/BTCUSDT.md
10745
- * await service.dump("BTCUSDT", "./custom/path");
10758
+ * // Save to custom path: ./custom/path/BTCUSDT_my-strategy.md
10759
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
10746
10760
  * ```
10747
10761
  */
10748
- this.dump = async (symbol, path = "./dump/partial") => {
10762
+ this.dump = async (symbol, strategyName, path = "./dump/partial") => {
10749
10763
  this.loggerService.log("partialMarkdownService dump", {
10750
10764
  symbol,
10765
+ strategyName,
10751
10766
  path,
10752
10767
  });
10753
- const storage = this.getStorage(symbol);
10754
- await storage.dump(symbol, path);
10768
+ const storage = this.getStorage(symbol, strategyName);
10769
+ await storage.dump(symbol, strategyName, path);
10755
10770
  };
10756
10771
  /**
10757
10772
  * Clears accumulated event data from storage.
10758
- * If symbol is provided, clears only that symbol's data.
10759
- * If symbol is omitted, clears all symbols' data.
10773
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
10774
+ * If nothing is provided, clears all data.
10760
10775
  *
10761
- * @param symbol - Optional symbol to clear specific symbol data
10776
+ * @param ctx - Optional context with symbol and strategyName
10762
10777
  *
10763
10778
  * @example
10764
10779
  * ```typescript
10765
10780
  * const service = new PartialMarkdownService();
10766
10781
  *
10767
- * // Clear specific symbol data
10768
- * await service.clear("BTCUSDT");
10782
+ * // Clear specific symbol-strategy pair
10783
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
10769
10784
  *
10770
- * // Clear all symbols' data
10785
+ * // Clear all data
10771
10786
  * await service.clear();
10772
10787
  * ```
10773
10788
  */
10774
- this.clear = async (symbol) => {
10789
+ this.clear = async (ctx) => {
10775
10790
  this.loggerService.log("partialMarkdownService clear", {
10776
- symbol,
10791
+ ctx,
10777
10792
  });
10778
- this.getStorage.clear(symbol);
10793
+ if (ctx) {
10794
+ const key = JSON.stringify([ctx.symbol, ctx.strategyName]);
10795
+ this.getStorage.clear(key);
10796
+ }
10797
+ else {
10798
+ this.getStorage.clear();
10799
+ }
10779
10800
  };
10780
10801
  /**
10781
10802
  * Initializes the service by subscribing to partial profit/loss events.
@@ -10906,6 +10927,145 @@ class PartialGlobalService {
10906
10927
  }
10907
10928
  }
10908
10929
 
10930
+ /**
10931
+ * Warning threshold for message size in kilobytes.
10932
+ * Messages exceeding this size trigger console warnings.
10933
+ */
10934
+ const WARN_KB = 100;
10935
+ /**
10936
+ * Internal function for dumping signal data to markdown files.
10937
+ * Creates a directory structure with system prompts, user messages, and LLM output.
10938
+ *
10939
+ * @param signalId - Unique identifier for the result
10940
+ * @param history - Array of message models from LLM conversation
10941
+ * @param signal - Signal DTO with trade parameters
10942
+ * @param outputDir - Output directory path (default: "./dump/strategy")
10943
+ * @returns Promise that resolves when all files are written
10944
+ */
10945
+ const DUMP_SIGNAL_FN = async (signalId, history, signal, outputDir = "./dump/strategy") => {
10946
+ // Extract system messages and system reminders from existing data
10947
+ const systemMessages = history.filter((m) => m.role === "system");
10948
+ const userMessages = history.filter((m) => m.role === "user");
10949
+ const subfolderPath = path.join(outputDir, String(signalId));
10950
+ try {
10951
+ await fs$1.promises.access(subfolderPath);
10952
+ return;
10953
+ }
10954
+ catch {
10955
+ await fs$1.promises.mkdir(subfolderPath, { recursive: true });
10956
+ }
10957
+ {
10958
+ let summary = "# Outline Result Summary\n";
10959
+ {
10960
+ summary += "\n";
10961
+ summary += `**ResultId**: ${String(signalId)}\n`;
10962
+ summary += "\n";
10963
+ }
10964
+ if (signal) {
10965
+ summary += "## Output Data\n\n";
10966
+ summary += "```json\n";
10967
+ summary += JSON.stringify(signal, null, 2);
10968
+ summary += "\n```\n\n";
10969
+ }
10970
+ // Add system messages to summary
10971
+ if (systemMessages.length > 0) {
10972
+ summary += "## System Messages\n\n";
10973
+ systemMessages.forEach((msg, idx) => {
10974
+ summary += `### System Message ${idx + 1}\n\n`;
10975
+ summary += msg.content;
10976
+ summary += "\n";
10977
+ });
10978
+ }
10979
+ const summaryFile = path.join(subfolderPath, "00_system_prompt.md");
10980
+ await fs$1.promises.writeFile(summaryFile, summary, "utf8");
10981
+ }
10982
+ {
10983
+ await Promise.all(Array.from(userMessages.entries()).map(async ([idx, message]) => {
10984
+ const messageNum = String(idx + 1).padStart(2, "0");
10985
+ const contentFileName = `${messageNum}_user_message.md`;
10986
+ const contentFilePath = path.join(subfolderPath, contentFileName);
10987
+ {
10988
+ const messageSizeBytes = Buffer.byteLength(message.content, "utf8");
10989
+ const messageSizeKb = Math.floor(messageSizeBytes / 1024);
10990
+ if (messageSizeKb > WARN_KB) {
10991
+ console.warn(`User message ${idx + 1} is ${messageSizeBytes} bytes (${messageSizeKb}kb), which exceeds warning limit`);
10992
+ }
10993
+ }
10994
+ let content = `# User Input ${idx + 1}\n\n`;
10995
+ content += `**ResultId**: ${String(signalId)}\n\n`;
10996
+ content += message.content;
10997
+ content += "\n";
10998
+ await fs$1.promises.writeFile(contentFilePath, content, "utf8");
10999
+ }));
11000
+ }
11001
+ {
11002
+ const messageNum = String(userMessages.length + 1).padStart(2, "0");
11003
+ const contentFileName = `${messageNum}_llm_output.md`;
11004
+ const contentFilePath = path.join(subfolderPath, contentFileName);
11005
+ let content = "# Full Outline Result\n\n";
11006
+ content += `**ResultId**: ${String(signalId)}\n\n`;
11007
+ if (signal) {
11008
+ content += "## Output Data\n\n";
11009
+ content += "```json\n";
11010
+ content += JSON.stringify(signal, null, 2);
11011
+ content += "\n```\n";
11012
+ }
11013
+ await fs$1.promises.writeFile(contentFilePath, content, "utf8");
11014
+ }
11015
+ };
11016
+ /**
11017
+ * Service for generating markdown documentation from LLM outline results.
11018
+ * Used by AI Strategy Optimizer to save debug logs and conversation history.
11019
+ *
11020
+ * Creates directory structure:
11021
+ * - ./dump/strategy/{signalId}/00_system_prompt.md - System messages and output data
11022
+ * - ./dump/strategy/{signalId}/01_user_message.md - First user input
11023
+ * - ./dump/strategy/{signalId}/02_user_message.md - Second user input
11024
+ * - ./dump/strategy/{signalId}/XX_llm_output.md - Final LLM output
11025
+ */
11026
+ class OutlineMarkdownService {
11027
+ constructor() {
11028
+ /** Logger service injected via DI */
11029
+ this.loggerService = inject(TYPES.loggerService);
11030
+ /**
11031
+ * Dumps signal data and conversation history to markdown files.
11032
+ * Skips if directory already exists to avoid overwriting previous results.
11033
+ *
11034
+ * Generated files:
11035
+ * - 00_system_prompt.md - System messages and output summary
11036
+ * - XX_user_message.md - Each user message in separate file (numbered)
11037
+ * - XX_llm_output.md - Final LLM output with signal data
11038
+ *
11039
+ * @param signalId - Unique identifier for the result (used as directory name)
11040
+ * @param history - Array of message models from LLM conversation
11041
+ * @param signal - Signal DTO with trade parameters (priceOpen, TP, SL, etc.)
11042
+ * @param outputDir - Output directory path (default: "./dump/strategy")
11043
+ * @returns Promise that resolves when all files are written
11044
+ *
11045
+ * @example
11046
+ * ```typescript
11047
+ * await outlineService.dumpSignal(
11048
+ * "strategy-1",
11049
+ * conversationHistory,
11050
+ * { position: "long", priceTakeProfit: 51000, priceStopLoss: 49000, minuteEstimatedTime: 60 }
11051
+ * );
11052
+ * // Creates: ./dump/strategy/strategy-1/00_system_prompt.md
11053
+ * // ./dump/strategy/strategy-1/01_user_message.md
11054
+ * // ./dump/strategy/strategy-1/02_llm_output.md
11055
+ * ```
11056
+ */
11057
+ this.dumpSignal = async (signalId, history, signal, outputDir = "./dump/strategy") => {
11058
+ this.loggerService.log("outlineMarkdownService dumpSignal", {
11059
+ signalId,
11060
+ history,
11061
+ signal,
11062
+ outputDir,
11063
+ });
11064
+ return await DUMP_SIGNAL_FN(signalId, history, signal, outputDir);
11065
+ };
11066
+ }
11067
+ }
11068
+
10909
11069
  {
10910
11070
  provide(TYPES.loggerService, () => new LoggerService());
10911
11071
  }
@@ -10963,6 +11123,7 @@ class PartialGlobalService {
10963
11123
  provide(TYPES.walkerMarkdownService, () => new WalkerMarkdownService());
10964
11124
  provide(TYPES.heatMarkdownService, () => new HeatMarkdownService());
10965
11125
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
11126
+ provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
10966
11127
  }
10967
11128
  {
10968
11129
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -11034,6 +11195,7 @@ const markdownServices = {
11034
11195
  walkerMarkdownService: inject(TYPES.walkerMarkdownService),
11035
11196
  heatMarkdownService: inject(TYPES.heatMarkdownService),
11036
11197
  partialMarkdownService: inject(TYPES.partialMarkdownService),
11198
+ outlineMarkdownService: inject(TYPES.outlineMarkdownService),
11037
11199
  };
11038
11200
  const validationServices = {
11039
11201
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -12700,6 +12862,83 @@ async function getMode() {
12700
12862
  return bt ? "backtest" : "live";
12701
12863
  }
12702
12864
 
12865
+ const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
12866
+ /**
12867
+ * Dumps signal data and LLM conversation history to markdown files.
12868
+ * Used by AI-powered strategies to save debug logs for analysis.
12869
+ *
12870
+ * Creates a directory structure with:
12871
+ * - 00_system_prompt.md - System messages and output summary
12872
+ * - XX_user_message.md - Each user message in separate file (numbered)
12873
+ * - XX_llm_output.md - Final LLM output with signal data
12874
+ *
12875
+ * Skips if directory already exists to avoid overwriting previous results.
12876
+ *
12877
+ * @param signalId - Unique identifier for the result (used as directory name, e.g., UUID)
12878
+ * @param history - Array of message models from LLM conversation
12879
+ * @param signal - Signal DTO returned by LLM (position, priceOpen, TP, SL, etc.)
12880
+ * @param outputDir - Output directory path (default: "./dump/strategy")
12881
+ * @returns Promise that resolves when all files are written
12882
+ *
12883
+ * @example
12884
+ * ```typescript
12885
+ * import { dumpSignal, getCandles } from "backtest-kit";
12886
+ * import { v4 as uuid } from "uuid";
12887
+ *
12888
+ * addStrategy({
12889
+ * strategyName: "llm-strategy",
12890
+ * interval: "5m",
12891
+ * getSignal: async (symbol) => {
12892
+ * const messages = [];
12893
+ *
12894
+ * // Build multi-timeframe analysis conversation
12895
+ * const candles1h = await getCandles(symbol, "1h", 24);
12896
+ * messages.push(
12897
+ * { role: "user", content: `Analyze 1h trend:\n${formatCandles(candles1h)}` },
12898
+ * { role: "assistant", content: "Trend analyzed" }
12899
+ * );
12900
+ *
12901
+ * const candles5m = await getCandles(symbol, "5m", 24);
12902
+ * messages.push(
12903
+ * { role: "user", content: `Analyze 5m structure:\n${formatCandles(candles5m)}` },
12904
+ * { role: "assistant", content: "Structure analyzed" }
12905
+ * );
12906
+ *
12907
+ * // Request signal
12908
+ * messages.push({
12909
+ * role: "user",
12910
+ * content: "Generate trading signal. Use position: 'wait' if uncertain."
12911
+ * });
12912
+ *
12913
+ * const resultId = uuid();
12914
+ * const signal = await llmRequest(messages);
12915
+ *
12916
+ * // Save conversation and result for debugging
12917
+ * await dumpSignal(resultId, messages, signal);
12918
+ *
12919
+ * return signal;
12920
+ * }
12921
+ * });
12922
+ *
12923
+ * // Creates: ./dump/strategy/{uuid}/00_system_prompt.md
12924
+ * // ./dump/strategy/{uuid}/01_user_message.md (1h analysis)
12925
+ * // ./dump/strategy/{uuid}/02_assistant_message.md
12926
+ * // ./dump/strategy/{uuid}/03_user_message.md (5m analysis)
12927
+ * // ./dump/strategy/{uuid}/04_assistant_message.md
12928
+ * // ./dump/strategy/{uuid}/05_user_message.md (signal request)
12929
+ * // ./dump/strategy/{uuid}/06_llm_output.md (final signal)
12930
+ * ```
12931
+ */
12932
+ async function dumpSignal(signalId, history, signal, outputDir = "./dump/strategy") {
12933
+ backtest$1.loggerService.info(DUMP_SIGNAL_METHOD_NAME, {
12934
+ signalId,
12935
+ history,
12936
+ signal,
12937
+ outputDir,
12938
+ });
12939
+ return await backtest$1.outlineMarkdownService.dumpSignal(signalId, history, signal, outputDir);
12940
+ }
12941
+
12703
12942
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
12704
12943
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
12705
12944
  const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
@@ -13920,26 +14159,26 @@ const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
13920
14159
  *
13921
14160
  * Data source:
13922
14161
  * - PartialMarkdownService listens to partialProfitSubject/partialLossSubject
13923
- * - Accumulates events in ReportStorage (max 250 events per symbol)
13924
- * - Events include: timestamp, action, symbol, signalId, position, level, price, mode
14162
+ * - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
14163
+ * - Events include: timestamp, action, symbol, strategyName, signalId, position, level, price, mode
13925
14164
  *
13926
14165
  * @example
13927
14166
  * ```typescript
13928
14167
  * import { Partial } from "./classes/Partial";
13929
14168
  *
13930
- * // Get statistical data for BTCUSDT
13931
- * const stats = await Partial.getData("BTCUSDT");
14169
+ * // Get statistical data for BTCUSDT:my-strategy
14170
+ * const stats = await Partial.getData("BTCUSDT", "my-strategy");
13932
14171
  * console.log(`Total events: ${stats.totalEvents}`);
13933
14172
  * console.log(`Profit events: ${stats.totalProfit}`);
13934
14173
  * console.log(`Loss events: ${stats.totalLoss}`);
13935
14174
  *
13936
14175
  * // Generate markdown report
13937
- * const markdown = await Partial.getReport("BTCUSDT");
14176
+ * const markdown = await Partial.getReport("BTCUSDT", "my-strategy");
13938
14177
  * console.log(markdown); // Formatted table with all events
13939
14178
  *
13940
14179
  * // Export report to file
13941
- * await Partial.dump("BTCUSDT"); // Saves to ./dump/partial/BTCUSDT.md
13942
- * await Partial.dump("BTCUSDT", "./custom/path"); // Custom directory
14180
+ * await Partial.dump("BTCUSDT", "my-strategy"); // Saves to ./dump/partial/BTCUSDT_my-strategy.md
14181
+ * await Partial.dump("BTCUSDT", "my-strategy", "./custom/path"); // Custom directory
13943
14182
  * ```
13944
14183
  */
13945
14184
  class PartialUtils {
@@ -13951,11 +14190,12 @@ class PartialUtils {
13951
14190
  * Returns aggregated metrics calculated from all profit and loss events.
13952
14191
  *
13953
14192
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14193
+ * @param strategyName - Strategy name (e.g., "my-strategy")
13954
14194
  * @returns Promise resolving to PartialStatistics object with counts and event list
13955
14195
  *
13956
14196
  * @example
13957
14197
  * ```typescript
13958
- * const stats = await Partial.getData("BTCUSDT");
14198
+ * const stats = await Partial.getData("BTCUSDT", "my-strategy");
13959
14199
  *
13960
14200
  * console.log(`Total events: ${stats.totalEvents}`);
13961
14201
  * console.log(`Profit events: ${stats.totalProfit} (${(stats.totalProfit / stats.totalEvents * 100).toFixed(1)}%)`);
@@ -13967,16 +14207,17 @@ class PartialUtils {
13967
14207
  * }
13968
14208
  * ```
13969
14209
  */
13970
- this.getData = async (symbol) => {
13971
- backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_DATA, { symbol });
13972
- return await backtest$1.partialMarkdownService.getData(symbol);
14210
+ this.getData = async (symbol, strategyName) => {
14211
+ backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_DATA, { symbol, strategyName });
14212
+ return await backtest$1.partialMarkdownService.getData(symbol, strategyName);
13973
14213
  };
13974
14214
  /**
13975
- * Generates markdown report with all partial profit/loss events for a symbol.
14215
+ * Generates markdown report with all partial profit/loss events for a symbol-strategy pair.
13976
14216
  *
13977
14217
  * Creates formatted table containing:
13978
14218
  * - Action (PROFIT/LOSS)
13979
14219
  * - Symbol
14220
+ * - Strategy
13980
14221
  * - Signal ID
13981
14222
  * - Position (LONG/SHORT)
13982
14223
  * - Level % (+10%, -20%, etc)
@@ -13987,35 +14228,36 @@ class PartialUtils {
13987
14228
  * Also includes summary statistics at the end.
13988
14229
  *
13989
14230
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14231
+ * @param strategyName - Strategy name (e.g., "my-strategy")
13990
14232
  * @returns Promise resolving to markdown formatted report string
13991
14233
  *
13992
14234
  * @example
13993
14235
  * ```typescript
13994
- * const markdown = await Partial.getReport("BTCUSDT");
14236
+ * const markdown = await Partial.getReport("BTCUSDT", "my-strategy");
13995
14237
  * console.log(markdown);
13996
14238
  *
13997
14239
  * // Output:
13998
- * // # Partial Profit/Loss Report: BTCUSDT
14240
+ * // # Partial Profit/Loss Report: BTCUSDT:my-strategy
13999
14241
  * //
14000
- * // | Action | Symbol | Signal ID | Position | Level % | Current Price | Timestamp | Mode |
14001
- * // | --- | --- | --- | --- | --- | --- | --- | --- |
14002
- * // | PROFIT | BTCUSDT | abc123 | LONG | +10% | 51500.00000000 USD | 2024-01-15T10:30:00.000Z | Backtest |
14003
- * // | LOSS | BTCUSDT | abc123 | LONG | -10% | 49000.00000000 USD | 2024-01-15T11:00:00.000Z | Backtest |
14242
+ * // | Action | Symbol | Strategy | Signal ID | Position | Level % | Current Price | Timestamp | Mode |
14243
+ * // | --- | --- | --- | --- | --- | --- | --- | --- | --- |
14244
+ * // | PROFIT | BTCUSDT | my-strategy | abc123 | LONG | +10% | 51500.00000000 USD | 2024-01-15T10:30:00.000Z | Backtest |
14245
+ * // | LOSS | BTCUSDT | my-strategy | abc123 | LONG | -10% | 49000.00000000 USD | 2024-01-15T11:00:00.000Z | Backtest |
14004
14246
  * //
14005
14247
  * // **Total events:** 2
14006
14248
  * // **Profit events:** 1
14007
14249
  * // **Loss events:** 1
14008
14250
  * ```
14009
14251
  */
14010
- this.getReport = async (symbol) => {
14011
- backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol });
14012
- return await backtest$1.partialMarkdownService.getReport(symbol);
14252
+ this.getReport = async (symbol, strategyName) => {
14253
+ backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
14254
+ return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
14013
14255
  };
14014
14256
  /**
14015
14257
  * Generates and saves markdown report to file.
14016
14258
  *
14017
14259
  * Creates directory if it doesn't exist.
14018
- * Filename format: {symbol}.md (e.g., "BTCUSDT.md")
14260
+ * Filename format: {symbol}_{strategyName}.md (e.g., "BTCUSDT_my-strategy.md")
14019
14261
  *
14020
14262
  * Delegates to PartialMarkdownService.dump() which:
14021
14263
  * 1. Generates markdown report via getReport()
@@ -14024,26 +14266,27 @@ class PartialUtils {
14024
14266
  * 4. Logs success/failure to console
14025
14267
  *
14026
14268
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14269
+ * @param strategyName - Strategy name (e.g., "my-strategy")
14027
14270
  * @param path - Output directory path (default: "./dump/partial")
14028
14271
  * @returns Promise that resolves when file is written
14029
14272
  *
14030
14273
  * @example
14031
14274
  * ```typescript
14032
- * // Save to default path: ./dump/partial/BTCUSDT.md
14033
- * await Partial.dump("BTCUSDT");
14275
+ * // Save to default path: ./dump/partial/BTCUSDT_my-strategy.md
14276
+ * await Partial.dump("BTCUSDT", "my-strategy");
14034
14277
  *
14035
- * // Save to custom path: ./reports/partial/BTCUSDT.md
14036
- * await Partial.dump("BTCUSDT", "./reports/partial");
14278
+ * // Save to custom path: ./reports/partial/BTCUSDT_my-strategy.md
14279
+ * await Partial.dump("BTCUSDT", "my-strategy", "./reports/partial");
14037
14280
  *
14038
14281
  * // After multiple symbols backtested, export all reports
14039
14282
  * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
14040
- * await Partial.dump(symbol, "./backtest-results");
14283
+ * await Partial.dump(symbol, "my-strategy", "./backtest-results");
14041
14284
  * }
14042
14285
  * ```
14043
14286
  */
14044
- this.dump = async (symbol, path) => {
14045
- backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, path });
14046
- await backtest$1.partialMarkdownService.dump(symbol, path);
14287
+ this.dump = async (symbol, strategyName, path) => {
14288
+ backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
14289
+ await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
14047
14290
  };
14048
14291
  }
14049
14292
  }
@@ -14056,9 +14299,9 @@ class PartialUtils {
14056
14299
  * import { Partial } from "backtest-kit";
14057
14300
  *
14058
14301
  * // Usage same as PartialUtils methods
14059
- * const stats = await Partial.getData("BTCUSDT");
14060
- * const report = await Partial.getReport("BTCUSDT");
14061
- * await Partial.dump("BTCUSDT");
14302
+ * const stats = await Partial.getData("BTCUSDT", "my-strategy");
14303
+ * const report = await Partial.getReport("BTCUSDT", "my-strategy");
14304
+ * await Partial.dump("BTCUSDT", "my-strategy");
14062
14305
  * ```
14063
14306
  */
14064
14307
  const Partial = new PartialUtils();
@@ -14168,6 +14411,7 @@ exports.addRisk = addRisk;
14168
14411
  exports.addSizing = addSizing;
14169
14412
  exports.addStrategy = addStrategy;
14170
14413
  exports.addWalker = addWalker;
14414
+ exports.dumpSignal = dumpSignal;
14171
14415
  exports.emitters = emitters;
14172
14416
  exports.formatPrice = formatPrice;
14173
14417
  exports.formatQuantity = formatQuantity;