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 +328 -84
- package/build/index.mjs +328 -85
- package/package.json +1 -1
- package/types.d.ts +182 -53
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(
|
|
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(
|
|
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]) =>
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
|
10759
|
-
* If
|
|
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
|
|
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
|
|
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
|
|
10785
|
+
* // Clear all data
|
|
10771
10786
|
* await service.clear();
|
|
10772
10787
|
* ```
|
|
10773
10788
|
*/
|
|
10774
|
-
this.clear = async (
|
|
10789
|
+
this.clear = async (ctx) => {
|
|
10775
10790
|
this.loggerService.log("partialMarkdownService clear", {
|
|
10776
|
-
|
|
10791
|
+
ctx,
|
|
10777
10792
|
});
|
|
10778
|
-
|
|
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/
|
|
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., "
|
|
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/
|
|
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/
|
|
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;
|