backtest-kit 1.4.11 → 1.4.12

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'),
@@ -10906,6 +10908,145 @@ class PartialGlobalService {
10906
10908
  }
10907
10909
  }
10908
10910
 
10911
+ /**
10912
+ * Warning threshold for message size in kilobytes.
10913
+ * Messages exceeding this size trigger console warnings.
10914
+ */
10915
+ const WARN_KB = 100;
10916
+ /**
10917
+ * Internal function for dumping signal data to markdown files.
10918
+ * Creates a directory structure with system prompts, user messages, and LLM output.
10919
+ *
10920
+ * @param signalId - Unique identifier for the result
10921
+ * @param history - Array of message models from LLM conversation
10922
+ * @param signal - Signal DTO with trade parameters
10923
+ * @param outputDir - Output directory path (default: "./dump/strategy")
10924
+ * @returns Promise that resolves when all files are written
10925
+ */
10926
+ const DUMP_SIGNAL_FN = async (signalId, history, signal, outputDir = "./dump/strategy") => {
10927
+ // Extract system messages and system reminders from existing data
10928
+ const systemMessages = history.filter((m) => m.role === "system");
10929
+ const userMessages = history.filter((m) => m.role === "user");
10930
+ const subfolderPath = path.join(outputDir, String(signalId));
10931
+ try {
10932
+ await fs$1.promises.access(subfolderPath);
10933
+ return;
10934
+ }
10935
+ catch {
10936
+ await fs$1.promises.mkdir(subfolderPath, { recursive: true });
10937
+ }
10938
+ {
10939
+ let summary = "# Outline Result Summary\n";
10940
+ {
10941
+ summary += "\n";
10942
+ summary += `**ResultId**: ${String(signalId)}\n`;
10943
+ summary += "\n";
10944
+ }
10945
+ if (signal) {
10946
+ summary += "## Output Data\n\n";
10947
+ summary += "```json\n";
10948
+ summary += JSON.stringify(signal, null, 2);
10949
+ summary += "\n```\n\n";
10950
+ }
10951
+ // Add system messages to summary
10952
+ if (systemMessages.length > 0) {
10953
+ summary += "## System Messages\n\n";
10954
+ systemMessages.forEach((msg, idx) => {
10955
+ summary += `### System Message ${idx + 1}\n\n`;
10956
+ summary += msg.content;
10957
+ summary += "\n";
10958
+ });
10959
+ }
10960
+ const summaryFile = path.join(subfolderPath, "00_system_prompt.md");
10961
+ await fs$1.promises.writeFile(summaryFile, summary, "utf8");
10962
+ }
10963
+ {
10964
+ await Promise.all(Array.from(userMessages.entries()).map(async ([idx, message]) => {
10965
+ const messageNum = String(idx + 1).padStart(2, "0");
10966
+ const contentFileName = `${messageNum}_user_message.md`;
10967
+ const contentFilePath = path.join(subfolderPath, contentFileName);
10968
+ {
10969
+ const messageSizeBytes = Buffer.byteLength(message.content, "utf8");
10970
+ const messageSizeKb = Math.floor(messageSizeBytes / 1024);
10971
+ if (messageSizeKb > WARN_KB) {
10972
+ console.warn(`User message ${idx + 1} is ${messageSizeBytes} bytes (${messageSizeKb}kb), which exceeds warning limit`);
10973
+ }
10974
+ }
10975
+ let content = `# User Input ${idx + 1}\n\n`;
10976
+ content += `**ResultId**: ${String(signalId)}\n\n`;
10977
+ content += message.content;
10978
+ content += "\n";
10979
+ await fs$1.promises.writeFile(contentFilePath, content, "utf8");
10980
+ }));
10981
+ }
10982
+ {
10983
+ const messageNum = String(userMessages.length + 1).padStart(2, "0");
10984
+ const contentFileName = `${messageNum}_llm_output.md`;
10985
+ const contentFilePath = path.join(subfolderPath, contentFileName);
10986
+ let content = "# Full Outline Result\n\n";
10987
+ content += `**ResultId**: ${String(signalId)}\n\n`;
10988
+ if (signal) {
10989
+ content += "## Output Data\n\n";
10990
+ content += "```json\n";
10991
+ content += JSON.stringify(signal, null, 2);
10992
+ content += "\n```\n";
10993
+ }
10994
+ await fs$1.promises.writeFile(contentFilePath, content, "utf8");
10995
+ }
10996
+ };
10997
+ /**
10998
+ * Service for generating markdown documentation from LLM outline results.
10999
+ * Used by AI Strategy Optimizer to save debug logs and conversation history.
11000
+ *
11001
+ * Creates directory structure:
11002
+ * - ./dump/strategy/{signalId}/00_system_prompt.md - System messages and output data
11003
+ * - ./dump/strategy/{signalId}/01_user_message.md - First user input
11004
+ * - ./dump/strategy/{signalId}/02_user_message.md - Second user input
11005
+ * - ./dump/strategy/{signalId}/XX_llm_output.md - Final LLM output
11006
+ */
11007
+ class OutlineMarkdownService {
11008
+ constructor() {
11009
+ /** Logger service injected via DI */
11010
+ this.loggerService = inject(TYPES.loggerService);
11011
+ /**
11012
+ * Dumps signal data and conversation history to markdown files.
11013
+ * Skips if directory already exists to avoid overwriting previous results.
11014
+ *
11015
+ * Generated files:
11016
+ * - 00_system_prompt.md - System messages and output summary
11017
+ * - XX_user_message.md - Each user message in separate file (numbered)
11018
+ * - XX_llm_output.md - Final LLM output with signal data
11019
+ *
11020
+ * @param signalId - Unique identifier for the result (used as directory name)
11021
+ * @param history - Array of message models from LLM conversation
11022
+ * @param signal - Signal DTO with trade parameters (priceOpen, TP, SL, etc.)
11023
+ * @param outputDir - Output directory path (default: "./dump/strategy")
11024
+ * @returns Promise that resolves when all files are written
11025
+ *
11026
+ * @example
11027
+ * ```typescript
11028
+ * await outlineService.dumpSignal(
11029
+ * "strategy-1",
11030
+ * conversationHistory,
11031
+ * { position: "long", priceTakeProfit: 51000, priceStopLoss: 49000, minuteEstimatedTime: 60 }
11032
+ * );
11033
+ * // Creates: ./dump/strategy/strategy-1/00_system_prompt.md
11034
+ * // ./dump/strategy/strategy-1/01_user_message.md
11035
+ * // ./dump/strategy/strategy-1/02_llm_output.md
11036
+ * ```
11037
+ */
11038
+ this.dumpSignal = async (signalId, history, signal, outputDir = "./dump/strategy") => {
11039
+ this.loggerService.log("outlineMarkdownService dumpSignal", {
11040
+ signalId,
11041
+ history,
11042
+ signal,
11043
+ outputDir,
11044
+ });
11045
+ return await DUMP_SIGNAL_FN(signalId, history, signal, outputDir);
11046
+ };
11047
+ }
11048
+ }
11049
+
10909
11050
  {
10910
11051
  provide(TYPES.loggerService, () => new LoggerService());
10911
11052
  }
@@ -10963,6 +11104,7 @@ class PartialGlobalService {
10963
11104
  provide(TYPES.walkerMarkdownService, () => new WalkerMarkdownService());
10964
11105
  provide(TYPES.heatMarkdownService, () => new HeatMarkdownService());
10965
11106
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
11107
+ provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
10966
11108
  }
10967
11109
  {
10968
11110
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -11034,6 +11176,7 @@ const markdownServices = {
11034
11176
  walkerMarkdownService: inject(TYPES.walkerMarkdownService),
11035
11177
  heatMarkdownService: inject(TYPES.heatMarkdownService),
11036
11178
  partialMarkdownService: inject(TYPES.partialMarkdownService),
11179
+ outlineMarkdownService: inject(TYPES.outlineMarkdownService),
11037
11180
  };
11038
11181
  const validationServices = {
11039
11182
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -12700,6 +12843,83 @@ async function getMode() {
12700
12843
  return bt ? "backtest" : "live";
12701
12844
  }
12702
12845
 
12846
+ const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
12847
+ /**
12848
+ * Dumps signal data and LLM conversation history to markdown files.
12849
+ * Used by AI-powered strategies to save debug logs for analysis.
12850
+ *
12851
+ * Creates a directory structure with:
12852
+ * - 00_system_prompt.md - System messages and output summary
12853
+ * - XX_user_message.md - Each user message in separate file (numbered)
12854
+ * - XX_llm_output.md - Final LLM output with signal data
12855
+ *
12856
+ * Skips if directory already exists to avoid overwriting previous results.
12857
+ *
12858
+ * @param signalId - Unique identifier for the result (used as directory name, e.g., UUID)
12859
+ * @param history - Array of message models from LLM conversation
12860
+ * @param signal - Signal DTO returned by LLM (position, priceOpen, TP, SL, etc.)
12861
+ * @param outputDir - Output directory path (default: "./dump/strategy")
12862
+ * @returns Promise that resolves when all files are written
12863
+ *
12864
+ * @example
12865
+ * ```typescript
12866
+ * import { dumpSignal, getCandles } from "backtest-kit";
12867
+ * import { v4 as uuid } from "uuid";
12868
+ *
12869
+ * addStrategy({
12870
+ * strategyName: "llm-strategy",
12871
+ * interval: "5m",
12872
+ * getSignal: async (symbol) => {
12873
+ * const messages = [];
12874
+ *
12875
+ * // Build multi-timeframe analysis conversation
12876
+ * const candles1h = await getCandles(symbol, "1h", 24);
12877
+ * messages.push(
12878
+ * { role: "user", content: `Analyze 1h trend:\n${formatCandles(candles1h)}` },
12879
+ * { role: "assistant", content: "Trend analyzed" }
12880
+ * );
12881
+ *
12882
+ * const candles5m = await getCandles(symbol, "5m", 24);
12883
+ * messages.push(
12884
+ * { role: "user", content: `Analyze 5m structure:\n${formatCandles(candles5m)}` },
12885
+ * { role: "assistant", content: "Structure analyzed" }
12886
+ * );
12887
+ *
12888
+ * // Request signal
12889
+ * messages.push({
12890
+ * role: "user",
12891
+ * content: "Generate trading signal. Use position: 'wait' if uncertain."
12892
+ * });
12893
+ *
12894
+ * const resultId = uuid();
12895
+ * const signal = await llmRequest(messages);
12896
+ *
12897
+ * // Save conversation and result for debugging
12898
+ * await dumpSignal(resultId, messages, signal);
12899
+ *
12900
+ * return signal;
12901
+ * }
12902
+ * });
12903
+ *
12904
+ * // Creates: ./dump/strategy/{uuid}/00_system_prompt.md
12905
+ * // ./dump/strategy/{uuid}/01_user_message.md (1h analysis)
12906
+ * // ./dump/strategy/{uuid}/02_assistant_message.md
12907
+ * // ./dump/strategy/{uuid}/03_user_message.md (5m analysis)
12908
+ * // ./dump/strategy/{uuid}/04_assistant_message.md
12909
+ * // ./dump/strategy/{uuid}/05_user_message.md (signal request)
12910
+ * // ./dump/strategy/{uuid}/06_llm_output.md (final signal)
12911
+ * ```
12912
+ */
12913
+ async function dumpSignal(signalId, history, signal, outputDir = "./dump/strategy") {
12914
+ backtest$1.loggerService.info(DUMP_SIGNAL_METHOD_NAME, {
12915
+ signalId,
12916
+ history,
12917
+ signal,
12918
+ outputDir,
12919
+ });
12920
+ return await backtest$1.outlineMarkdownService.dumpSignal(signalId, history, signal, outputDir);
12921
+ }
12922
+
12703
12923
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
12704
12924
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
12705
12925
  const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
@@ -14168,6 +14388,7 @@ exports.addRisk = addRisk;
14168
14388
  exports.addSizing = addSizing;
14169
14389
  exports.addStrategy = addStrategy;
14170
14390
  exports.addWalker = addWalker;
14391
+ exports.dumpSignal = dumpSignal;
14171
14392
  exports.emitters = emitters;
14172
14393
  exports.formatPrice = formatPrice;
14173
14394
  exports.formatQuantity = formatQuantity;
package/build/index.mjs CHANGED
@@ -5,6 +5,7 @@ import fs, { mkdir, writeFile } from 'fs/promises';
5
5
  import path, { join } from 'path';
6
6
  import crypto from 'crypto';
7
7
  import os from 'os';
8
+ import { promises } from 'fs';
8
9
 
9
10
  const GLOBAL_CONFIG = {
10
11
  /**
@@ -160,6 +161,7 @@ const markdownServices$1 = {
160
161
  walkerMarkdownService: Symbol('walkerMarkdownService'),
161
162
  heatMarkdownService: Symbol('heatMarkdownService'),
162
163
  partialMarkdownService: Symbol('partialMarkdownService'),
164
+ outlineMarkdownService: Symbol('outlineMarkdownService'),
163
165
  };
164
166
  const validationServices$1 = {
165
167
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -10904,6 +10906,145 @@ class PartialGlobalService {
10904
10906
  }
10905
10907
  }
10906
10908
 
10909
+ /**
10910
+ * Warning threshold for message size in kilobytes.
10911
+ * Messages exceeding this size trigger console warnings.
10912
+ */
10913
+ const WARN_KB = 100;
10914
+ /**
10915
+ * Internal function for dumping signal data to markdown files.
10916
+ * Creates a directory structure with system prompts, user messages, and LLM output.
10917
+ *
10918
+ * @param signalId - Unique identifier for the result
10919
+ * @param history - Array of message models from LLM conversation
10920
+ * @param signal - Signal DTO with trade parameters
10921
+ * @param outputDir - Output directory path (default: "./dump/strategy")
10922
+ * @returns Promise that resolves when all files are written
10923
+ */
10924
+ const DUMP_SIGNAL_FN = async (signalId, history, signal, outputDir = "./dump/strategy") => {
10925
+ // Extract system messages and system reminders from existing data
10926
+ const systemMessages = history.filter((m) => m.role === "system");
10927
+ const userMessages = history.filter((m) => m.role === "user");
10928
+ const subfolderPath = path.join(outputDir, String(signalId));
10929
+ try {
10930
+ await promises.access(subfolderPath);
10931
+ return;
10932
+ }
10933
+ catch {
10934
+ await promises.mkdir(subfolderPath, { recursive: true });
10935
+ }
10936
+ {
10937
+ let summary = "# Outline Result Summary\n";
10938
+ {
10939
+ summary += "\n";
10940
+ summary += `**ResultId**: ${String(signalId)}\n`;
10941
+ summary += "\n";
10942
+ }
10943
+ if (signal) {
10944
+ summary += "## Output Data\n\n";
10945
+ summary += "```json\n";
10946
+ summary += JSON.stringify(signal, null, 2);
10947
+ summary += "\n```\n\n";
10948
+ }
10949
+ // Add system messages to summary
10950
+ if (systemMessages.length > 0) {
10951
+ summary += "## System Messages\n\n";
10952
+ systemMessages.forEach((msg, idx) => {
10953
+ summary += `### System Message ${idx + 1}\n\n`;
10954
+ summary += msg.content;
10955
+ summary += "\n";
10956
+ });
10957
+ }
10958
+ const summaryFile = path.join(subfolderPath, "00_system_prompt.md");
10959
+ await promises.writeFile(summaryFile, summary, "utf8");
10960
+ }
10961
+ {
10962
+ await Promise.all(Array.from(userMessages.entries()).map(async ([idx, message]) => {
10963
+ const messageNum = String(idx + 1).padStart(2, "0");
10964
+ const contentFileName = `${messageNum}_user_message.md`;
10965
+ const contentFilePath = path.join(subfolderPath, contentFileName);
10966
+ {
10967
+ const messageSizeBytes = Buffer.byteLength(message.content, "utf8");
10968
+ const messageSizeKb = Math.floor(messageSizeBytes / 1024);
10969
+ if (messageSizeKb > WARN_KB) {
10970
+ console.warn(`User message ${idx + 1} is ${messageSizeBytes} bytes (${messageSizeKb}kb), which exceeds warning limit`);
10971
+ }
10972
+ }
10973
+ let content = `# User Input ${idx + 1}\n\n`;
10974
+ content += `**ResultId**: ${String(signalId)}\n\n`;
10975
+ content += message.content;
10976
+ content += "\n";
10977
+ await promises.writeFile(contentFilePath, content, "utf8");
10978
+ }));
10979
+ }
10980
+ {
10981
+ const messageNum = String(userMessages.length + 1).padStart(2, "0");
10982
+ const contentFileName = `${messageNum}_llm_output.md`;
10983
+ const contentFilePath = path.join(subfolderPath, contentFileName);
10984
+ let content = "# Full Outline Result\n\n";
10985
+ content += `**ResultId**: ${String(signalId)}\n\n`;
10986
+ if (signal) {
10987
+ content += "## Output Data\n\n";
10988
+ content += "```json\n";
10989
+ content += JSON.stringify(signal, null, 2);
10990
+ content += "\n```\n";
10991
+ }
10992
+ await promises.writeFile(contentFilePath, content, "utf8");
10993
+ }
10994
+ };
10995
+ /**
10996
+ * Service for generating markdown documentation from LLM outline results.
10997
+ * Used by AI Strategy Optimizer to save debug logs and conversation history.
10998
+ *
10999
+ * Creates directory structure:
11000
+ * - ./dump/strategy/{signalId}/00_system_prompt.md - System messages and output data
11001
+ * - ./dump/strategy/{signalId}/01_user_message.md - First user input
11002
+ * - ./dump/strategy/{signalId}/02_user_message.md - Second user input
11003
+ * - ./dump/strategy/{signalId}/XX_llm_output.md - Final LLM output
11004
+ */
11005
+ class OutlineMarkdownService {
11006
+ constructor() {
11007
+ /** Logger service injected via DI */
11008
+ this.loggerService = inject(TYPES.loggerService);
11009
+ /**
11010
+ * Dumps signal data and conversation history to markdown files.
11011
+ * Skips if directory already exists to avoid overwriting previous results.
11012
+ *
11013
+ * Generated files:
11014
+ * - 00_system_prompt.md - System messages and output summary
11015
+ * - XX_user_message.md - Each user message in separate file (numbered)
11016
+ * - XX_llm_output.md - Final LLM output with signal data
11017
+ *
11018
+ * @param signalId - Unique identifier for the result (used as directory name)
11019
+ * @param history - Array of message models from LLM conversation
11020
+ * @param signal - Signal DTO with trade parameters (priceOpen, TP, SL, etc.)
11021
+ * @param outputDir - Output directory path (default: "./dump/strategy")
11022
+ * @returns Promise that resolves when all files are written
11023
+ *
11024
+ * @example
11025
+ * ```typescript
11026
+ * await outlineService.dumpSignal(
11027
+ * "strategy-1",
11028
+ * conversationHistory,
11029
+ * { position: "long", priceTakeProfit: 51000, priceStopLoss: 49000, minuteEstimatedTime: 60 }
11030
+ * );
11031
+ * // Creates: ./dump/strategy/strategy-1/00_system_prompt.md
11032
+ * // ./dump/strategy/strategy-1/01_user_message.md
11033
+ * // ./dump/strategy/strategy-1/02_llm_output.md
11034
+ * ```
11035
+ */
11036
+ this.dumpSignal = async (signalId, history, signal, outputDir = "./dump/strategy") => {
11037
+ this.loggerService.log("outlineMarkdownService dumpSignal", {
11038
+ signalId,
11039
+ history,
11040
+ signal,
11041
+ outputDir,
11042
+ });
11043
+ return await DUMP_SIGNAL_FN(signalId, history, signal, outputDir);
11044
+ };
11045
+ }
11046
+ }
11047
+
10907
11048
  {
10908
11049
  provide(TYPES.loggerService, () => new LoggerService());
10909
11050
  }
@@ -10961,6 +11102,7 @@ class PartialGlobalService {
10961
11102
  provide(TYPES.walkerMarkdownService, () => new WalkerMarkdownService());
10962
11103
  provide(TYPES.heatMarkdownService, () => new HeatMarkdownService());
10963
11104
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
11105
+ provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
10964
11106
  }
10965
11107
  {
10966
11108
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -11032,6 +11174,7 @@ const markdownServices = {
11032
11174
  walkerMarkdownService: inject(TYPES.walkerMarkdownService),
11033
11175
  heatMarkdownService: inject(TYPES.heatMarkdownService),
11034
11176
  partialMarkdownService: inject(TYPES.partialMarkdownService),
11177
+ outlineMarkdownService: inject(TYPES.outlineMarkdownService),
11035
11178
  };
11036
11179
  const validationServices = {
11037
11180
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -12698,6 +12841,83 @@ async function getMode() {
12698
12841
  return bt ? "backtest" : "live";
12699
12842
  }
12700
12843
 
12844
+ const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
12845
+ /**
12846
+ * Dumps signal data and LLM conversation history to markdown files.
12847
+ * Used by AI-powered strategies to save debug logs for analysis.
12848
+ *
12849
+ * Creates a directory structure with:
12850
+ * - 00_system_prompt.md - System messages and output summary
12851
+ * - XX_user_message.md - Each user message in separate file (numbered)
12852
+ * - XX_llm_output.md - Final LLM output with signal data
12853
+ *
12854
+ * Skips if directory already exists to avoid overwriting previous results.
12855
+ *
12856
+ * @param signalId - Unique identifier for the result (used as directory name, e.g., UUID)
12857
+ * @param history - Array of message models from LLM conversation
12858
+ * @param signal - Signal DTO returned by LLM (position, priceOpen, TP, SL, etc.)
12859
+ * @param outputDir - Output directory path (default: "./dump/strategy")
12860
+ * @returns Promise that resolves when all files are written
12861
+ *
12862
+ * @example
12863
+ * ```typescript
12864
+ * import { dumpSignal, getCandles } from "backtest-kit";
12865
+ * import { v4 as uuid } from "uuid";
12866
+ *
12867
+ * addStrategy({
12868
+ * strategyName: "llm-strategy",
12869
+ * interval: "5m",
12870
+ * getSignal: async (symbol) => {
12871
+ * const messages = [];
12872
+ *
12873
+ * // Build multi-timeframe analysis conversation
12874
+ * const candles1h = await getCandles(symbol, "1h", 24);
12875
+ * messages.push(
12876
+ * { role: "user", content: `Analyze 1h trend:\n${formatCandles(candles1h)}` },
12877
+ * { role: "assistant", content: "Trend analyzed" }
12878
+ * );
12879
+ *
12880
+ * const candles5m = await getCandles(symbol, "5m", 24);
12881
+ * messages.push(
12882
+ * { role: "user", content: `Analyze 5m structure:\n${formatCandles(candles5m)}` },
12883
+ * { role: "assistant", content: "Structure analyzed" }
12884
+ * );
12885
+ *
12886
+ * // Request signal
12887
+ * messages.push({
12888
+ * role: "user",
12889
+ * content: "Generate trading signal. Use position: 'wait' if uncertain."
12890
+ * });
12891
+ *
12892
+ * const resultId = uuid();
12893
+ * const signal = await llmRequest(messages);
12894
+ *
12895
+ * // Save conversation and result for debugging
12896
+ * await dumpSignal(resultId, messages, signal);
12897
+ *
12898
+ * return signal;
12899
+ * }
12900
+ * });
12901
+ *
12902
+ * // Creates: ./dump/strategy/{uuid}/00_system_prompt.md
12903
+ * // ./dump/strategy/{uuid}/01_user_message.md (1h analysis)
12904
+ * // ./dump/strategy/{uuid}/02_assistant_message.md
12905
+ * // ./dump/strategy/{uuid}/03_user_message.md (5m analysis)
12906
+ * // ./dump/strategy/{uuid}/04_assistant_message.md
12907
+ * // ./dump/strategy/{uuid}/05_user_message.md (signal request)
12908
+ * // ./dump/strategy/{uuid}/06_llm_output.md (final signal)
12909
+ * ```
12910
+ */
12911
+ async function dumpSignal(signalId, history, signal, outputDir = "./dump/strategy") {
12912
+ backtest$1.loggerService.info(DUMP_SIGNAL_METHOD_NAME, {
12913
+ signalId,
12914
+ history,
12915
+ signal,
12916
+ outputDir,
12917
+ });
12918
+ return await backtest$1.outlineMarkdownService.dumpSignal(signalId, history, signal, outputDir);
12919
+ }
12920
+
12701
12921
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
12702
12922
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
12703
12923
  const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
@@ -14142,4 +14362,4 @@ class ConstantUtils {
14142
14362
  */
14143
14363
  const Constant = new ConstantUtils();
14144
14364
 
14145
- export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
14365
+ export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.4.11",
3
+ "version": "1.4.12",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -3616,6 +3616,74 @@ declare function getDate(): Promise<Date>;
3616
3616
  */
3617
3617
  declare function getMode(): Promise<"backtest" | "live">;
3618
3618
 
3619
+ /**
3620
+ * Dumps signal data and LLM conversation history to markdown files.
3621
+ * Used by AI-powered strategies to save debug logs for analysis.
3622
+ *
3623
+ * Creates a directory structure with:
3624
+ * - 00_system_prompt.md - System messages and output summary
3625
+ * - XX_user_message.md - Each user message in separate file (numbered)
3626
+ * - XX_llm_output.md - Final LLM output with signal data
3627
+ *
3628
+ * Skips if directory already exists to avoid overwriting previous results.
3629
+ *
3630
+ * @param signalId - Unique identifier for the result (used as directory name, e.g., UUID)
3631
+ * @param history - Array of message models from LLM conversation
3632
+ * @param signal - Signal DTO returned by LLM (position, priceOpen, TP, SL, etc.)
3633
+ * @param outputDir - Output directory path (default: "./dump/strategy")
3634
+ * @returns Promise that resolves when all files are written
3635
+ *
3636
+ * @example
3637
+ * ```typescript
3638
+ * import { dumpSignal, getCandles } from "backtest-kit";
3639
+ * import { v4 as uuid } from "uuid";
3640
+ *
3641
+ * addStrategy({
3642
+ * strategyName: "llm-strategy",
3643
+ * interval: "5m",
3644
+ * getSignal: async (symbol) => {
3645
+ * const messages = [];
3646
+ *
3647
+ * // Build multi-timeframe analysis conversation
3648
+ * const candles1h = await getCandles(symbol, "1h", 24);
3649
+ * messages.push(
3650
+ * { role: "user", content: `Analyze 1h trend:\n${formatCandles(candles1h)}` },
3651
+ * { role: "assistant", content: "Trend analyzed" }
3652
+ * );
3653
+ *
3654
+ * const candles5m = await getCandles(symbol, "5m", 24);
3655
+ * messages.push(
3656
+ * { role: "user", content: `Analyze 5m structure:\n${formatCandles(candles5m)}` },
3657
+ * { role: "assistant", content: "Structure analyzed" }
3658
+ * );
3659
+ *
3660
+ * // Request signal
3661
+ * messages.push({
3662
+ * role: "user",
3663
+ * content: "Generate trading signal. Use position: 'wait' if uncertain."
3664
+ * });
3665
+ *
3666
+ * const resultId = uuid();
3667
+ * const signal = await llmRequest(messages);
3668
+ *
3669
+ * // Save conversation and result for debugging
3670
+ * await dumpSignal(resultId, messages, signal);
3671
+ *
3672
+ * return signal;
3673
+ * }
3674
+ * });
3675
+ *
3676
+ * // Creates: ./dump/strategy/{uuid}/00_system_prompt.md
3677
+ * // ./dump/strategy/{uuid}/01_user_message.md (1h analysis)
3678
+ * // ./dump/strategy/{uuid}/02_assistant_message.md
3679
+ * // ./dump/strategy/{uuid}/03_user_message.md (5m analysis)
3680
+ * // ./dump/strategy/{uuid}/04_assistant_message.md
3681
+ * // ./dump/strategy/{uuid}/05_user_message.md (signal request)
3682
+ * // ./dump/strategy/{uuid}/06_llm_output.md (final signal)
3683
+ * ```
3684
+ */
3685
+ declare function dumpSignal(signalId: string | number, history: MessageModel[], signal: ISignalDto, outputDir?: string): Promise<void>;
3686
+
3619
3687
  /**
3620
3688
  * Portfolio heatmap statistics for a single symbol.
3621
3689
  * Aggregated metrics across all strategies for one trading pair.
@@ -8661,6 +8729,54 @@ declare class PartialGlobalService {
8661
8729
  clear: (symbol: string, data: ISignalRow, priceClose: number) => Promise<void>;
8662
8730
  }
8663
8731
 
8732
+ /**
8733
+ * Unique identifier for outline result.
8734
+ * Can be string or number for flexible ID formats.
8735
+ */
8736
+ type ResultId = string | number;
8737
+ /**
8738
+ * Service for generating markdown documentation from LLM outline results.
8739
+ * Used by AI Strategy Optimizer to save debug logs and conversation history.
8740
+ *
8741
+ * Creates directory structure:
8742
+ * - ./dump/strategy/{signalId}/00_system_prompt.md - System messages and output data
8743
+ * - ./dump/strategy/{signalId}/01_user_message.md - First user input
8744
+ * - ./dump/strategy/{signalId}/02_user_message.md - Second user input
8745
+ * - ./dump/strategy/{signalId}/XX_llm_output.md - Final LLM output
8746
+ */
8747
+ declare class OutlineMarkdownService {
8748
+ /** Logger service injected via DI */
8749
+ private readonly loggerService;
8750
+ /**
8751
+ * Dumps signal data and conversation history to markdown files.
8752
+ * Skips if directory already exists to avoid overwriting previous results.
8753
+ *
8754
+ * Generated files:
8755
+ * - 00_system_prompt.md - System messages and output summary
8756
+ * - XX_user_message.md - Each user message in separate file (numbered)
8757
+ * - XX_llm_output.md - Final LLM output with signal data
8758
+ *
8759
+ * @param signalId - Unique identifier for the result (used as directory name)
8760
+ * @param history - Array of message models from LLM conversation
8761
+ * @param signal - Signal DTO with trade parameters (priceOpen, TP, SL, etc.)
8762
+ * @param outputDir - Output directory path (default: "./dump/strategy")
8763
+ * @returns Promise that resolves when all files are written
8764
+ *
8765
+ * @example
8766
+ * ```typescript
8767
+ * await outlineService.dumpSignal(
8768
+ * "strategy-1",
8769
+ * conversationHistory,
8770
+ * { position: "long", priceTakeProfit: 51000, priceStopLoss: 49000, minuteEstimatedTime: 60 }
8771
+ * );
8772
+ * // Creates: ./dump/strategy/strategy-1/00_system_prompt.md
8773
+ * // ./dump/strategy/strategy-1/01_user_message.md
8774
+ * // ./dump/strategy/strategy-1/02_llm_output.md
8775
+ * ```
8776
+ */
8777
+ dumpSignal: (signalId: ResultId, history: MessageModel[], signal: ISignalDto, outputDir?: string) => Promise<void>;
8778
+ }
8779
+
8664
8780
  declare const backtest: {
8665
8781
  optimizerTemplateService: OptimizerTemplateService;
8666
8782
  exchangeValidationService: ExchangeValidationService;
@@ -8677,6 +8793,7 @@ declare const backtest: {
8677
8793
  walkerMarkdownService: WalkerMarkdownService;
8678
8794
  heatMarkdownService: HeatMarkdownService;
8679
8795
  partialMarkdownService: PartialMarkdownService;
8796
+ outlineMarkdownService: OutlineMarkdownService;
8680
8797
  backtestLogicPublicService: BacktestLogicPublicService;
8681
8798
  liveLogicPublicService: LiveLogicPublicService;
8682
8799
  walkerLogicPublicService: WalkerLogicPublicService;
@@ -8716,4 +8833,4 @@ declare const backtest: {
8716
8833
  loggerService: LoggerService;
8717
8834
  };
8718
8835
 
8719
- export { Backtest, type BacktestStatistics, type CandleInterval, Constant, type DoneContract, type EntityId, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type ICandleData, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IHeatmapStatistics, type IOptimizerCallbacks, type IOptimizerData, type IOptimizerFetchArgs, type IOptimizerFilterArgs, type IOptimizerRange, type IOptimizerSchema, type IOptimizerSource, type IOptimizerStrategy, type IOptimizerTemplate, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStrategyPnL, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, Live, type LiveStatistics, type MessageModel, type MessageRole, MethodContextService, Optimizer, Partial$1 as Partial, type PartialData, type PartialLossContract, type PartialProfitContract, type PartialStatistics, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatistics, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, type ProgressBacktestContract, type ProgressOptimizerContract, type ProgressWalkerContract, type RiskData, Schedule, type ScheduleData, type ScheduleStatistics, type SignalData, type SignalInterval, type TPersistBase, type TPersistBaseCtor, Walker, type WalkerContract, type WalkerMetric, type WalkerStatistics, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
8836
+ export { Backtest, type BacktestStatistics, type CandleInterval, Constant, type DoneContract, type EntityId, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type ICandleData, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IHeatmapStatistics, type IOptimizerCallbacks, type IOptimizerData, type IOptimizerFetchArgs, type IOptimizerFilterArgs, type IOptimizerRange, type IOptimizerSchema, type IOptimizerSource, type IOptimizerStrategy, type IOptimizerTemplate, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStrategyPnL, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, Live, type LiveStatistics, type MessageModel, type MessageRole, MethodContextService, Optimizer, Partial$1 as Partial, type PartialData, type PartialLossContract, type PartialProfitContract, type PartialStatistics, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatistics, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, type ProgressBacktestContract, type ProgressOptimizerContract, type ProgressWalkerContract, type RiskData, Schedule, type ScheduleData, type ScheduleStatistics, type SignalData, type SignalInterval, type TPersistBase, type TPersistBaseCtor, Walker, type WalkerContract, type WalkerMetric, type WalkerStatistics, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };