backtest-kit 3.1.0 → 3.2.0
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/README.md +48 -22
- package/build/index.cjs +144 -51
- package/build/index.mjs +144 -52
- package/package.json +1 -1
- package/types.d.ts +50 -1
package/README.md
CHANGED
|
@@ -441,6 +441,30 @@ Unlike cloud-based platforms, backtest-kit runs entirely in your environment. Yo
|
|
|
441
441
|
|
|
442
442
|
The `backtest-kit` ecosystem extends beyond the core library, offering complementary packages and tools to enhance your trading system development experience:
|
|
443
443
|
|
|
444
|
+
|
|
445
|
+
### @backtest-kit/cli
|
|
446
|
+
|
|
447
|
+
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/cli)** 📟
|
|
448
|
+
|
|
449
|
+
The **@backtest-kit/cli** package is a zero-boilerplate CLI runner for backtest-kit strategies. Point it at your strategy file and run backtests, paper trading, or live bots — no infrastructure code required.
|
|
450
|
+
|
|
451
|
+
#### Key Features
|
|
452
|
+
- 🚀 **Zero Config**: Run a backtest with one command — no setup code needed
|
|
453
|
+
- 🔄 **Three Modes**: `--backtest`, `--paper`, `--live` with graceful SIGINT shutdown
|
|
454
|
+
- 💾 **Auto Cache**: Warms OHLCV candle cache for all intervals before the backtest starts
|
|
455
|
+
- 🌐 **Web Dashboard**: Launch `@backtest-kit/ui` with a single `--ui` flag
|
|
456
|
+
- 📬 **Telegram Alerts**: Formatted trade notifications with price charts via `--telegram`
|
|
457
|
+
- 🗂️ **Monorepo Ready**: Each strategy's `dump/`, `modules/`, and `template/` are automatically isolated by entry point directory
|
|
458
|
+
|
|
459
|
+
#### Use Case
|
|
460
|
+
The fastest way to run any backtest-kit strategy from the command line. Instead of writing boilerplate for storage, notifications, candle caching, and signal logging, add one dependency and wire up your `package.json` scripts. Works equally well for a single-strategy project or a monorepo with dozens of strategies in separate subdirectories.
|
|
461
|
+
|
|
462
|
+
#### Get Started
|
|
463
|
+
```bash
|
|
464
|
+
npx -y @backtest-kit/cli --init
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
|
|
444
468
|
### @backtest-kit/pinets
|
|
445
469
|
|
|
446
470
|
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/pinets)** 📜
|
|
@@ -531,9 +555,32 @@ npm install @backtest-kit/signals backtest-kit
|
|
|
531
555
|
```
|
|
532
556
|
|
|
533
557
|
|
|
558
|
+
|
|
559
|
+
### @backtest-kit/graph
|
|
560
|
+
|
|
561
|
+
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/graph)** 🔗
|
|
562
|
+
|
|
563
|
+
The **@backtest-kit/graph** package lets you compose backtest-kit computations as a typed directed acyclic graph (DAG). Define source nodes that fetch market data and output nodes that compute derived values — then resolve the whole graph in topological order with automatic parallelism.
|
|
564
|
+
|
|
565
|
+
#### Key Features
|
|
566
|
+
- 🔌 **DAG Execution**: Nodes are resolved bottom-up in topological order with `Promise.all` parallelism
|
|
567
|
+
- 🔒 **Type-Safe Values**: TypeScript infers the return type of every node through the graph via generics
|
|
568
|
+
- 🧱 **Two APIs**: Low-level `INode` for runtime/storage, high-level `sourceNode` + `outputNode` builders for authoring
|
|
569
|
+
- 💾 **DB-Ready Serialization**: `serialize` / `deserialize` convert the graph to a flat `IFlatNode[]` list with `id` / `nodeIds`
|
|
570
|
+
- 🌐 **Context-Aware Fetch**: `sourceNode` receives `(symbol, when, exchangeName)` from the execution context automatically
|
|
571
|
+
|
|
572
|
+
#### Use Case
|
|
573
|
+
Perfect for multi-timeframe strategies where multiple Pine Script or indicator computations must be combined. Instead of manually chaining async calls, define each computation as a node and let the graph resolve dependencies in parallel. Adding a new filter or timeframe requires no changes to the existing wiring.
|
|
574
|
+
|
|
575
|
+
#### Get Started
|
|
576
|
+
```bash
|
|
577
|
+
npm install @backtest-kit/graph backtest-kit
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
|
|
534
581
|
### @backtest-kit/sidekick
|
|
535
582
|
|
|
536
|
-
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/sidekick)**
|
|
583
|
+
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/sidekick)** 🚀
|
|
537
584
|
|
|
538
585
|
The **@backtest-kit/sidekick** package is the easiest way to create a new Backtest Kit trading bot project. Like create-react-app, but for algorithmic trading.
|
|
539
586
|
|
|
@@ -556,27 +603,6 @@ npm start
|
|
|
556
603
|
```
|
|
557
604
|
|
|
558
605
|
|
|
559
|
-
### @backtest-kit/graph
|
|
560
|
-
|
|
561
|
-
> **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/graph)** 🔗
|
|
562
|
-
|
|
563
|
-
The **@backtest-kit/graph** package lets you compose backtest-kit computations as a typed directed acyclic graph (DAG). Define source nodes that fetch market data and output nodes that compute derived values — then resolve the whole graph in topological order with automatic parallelism.
|
|
564
|
-
|
|
565
|
-
#### Key Features
|
|
566
|
-
- 🔌 **DAG Execution**: Nodes are resolved bottom-up in topological order with `Promise.all` parallelism
|
|
567
|
-
- 🔒 **Type-Safe Values**: TypeScript infers the return type of every node through the graph via generics
|
|
568
|
-
- 🧱 **Two APIs**: Low-level `INode` for runtime/storage, high-level `sourceNode` + `outputNode` builders for authoring
|
|
569
|
-
- 💾 **DB-Ready Serialization**: `serialize` / `deserialize` convert the graph to a flat `IFlatNode[]` list with `id` / `nodeIds`
|
|
570
|
-
- 🌐 **Context-Aware Fetch**: `sourceNode` receives `(symbol, when, exchangeName)` from the execution context automatically
|
|
571
|
-
|
|
572
|
-
#### Use Case
|
|
573
|
-
Perfect for multi-timeframe strategies where multiple Pine Script or indicator computations must be combined. Instead of manually chaining async calls, define each computation as a node and let the graph resolve dependencies in parallel. Adding a new filter or timeframe requires no changes to the existing wiring.
|
|
574
|
-
|
|
575
|
-
#### Get Started
|
|
576
|
-
```bash
|
|
577
|
-
npm install @backtest-kit/graph backtest-kit
|
|
578
|
-
```
|
|
579
|
-
|
|
580
606
|
## 🤖 Are you a robot?
|
|
581
607
|
|
|
582
608
|
**For language models**: Read extended description in [./LLMs.md](./LLMs.md)
|
package/build/index.cjs
CHANGED
|
@@ -2905,8 +2905,9 @@ class ExchangeConnectionService {
|
|
|
2905
2905
|
*
|
|
2906
2906
|
* For signals with partial closes:
|
|
2907
2907
|
* - Calculates weighted PNL: Σ(percent_i × pnl_i) for each partial + (remaining% × final_pnl)
|
|
2908
|
-
* - Each partial close has its own
|
|
2909
|
-
* -
|
|
2908
|
+
* - Each partial close has its own slippage
|
|
2909
|
+
* - Open fee is charged once; close fees are proportional to each partial's size
|
|
2910
|
+
* - Total fees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partial% / 100) × (closeWithSlip / openWithSlip)
|
|
2910
2911
|
*
|
|
2911
2912
|
* Formula breakdown:
|
|
2912
2913
|
* 1. Apply slippage to open/close prices (worse execution)
|
|
@@ -2915,7 +2916,7 @@ class ExchangeConnectionService {
|
|
|
2915
2916
|
* 2. Calculate raw PNL percentage
|
|
2916
2917
|
* - LONG: ((closePrice - openPrice) / openPrice) * 100
|
|
2917
2918
|
* - SHORT: ((openPrice - closePrice) / openPrice) * 100
|
|
2918
|
-
* 3. Subtract total fees
|
|
2919
|
+
* 3. Subtract total fees: open fee + close fee adjusted for slippage-affected execution price
|
|
2919
2920
|
*
|
|
2920
2921
|
* @param signal - Closed signal with position details and optional partial history
|
|
2921
2922
|
* @param priceClose - Actual close price at final exit
|
|
@@ -2953,67 +2954,49 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
2953
2954
|
// Calculate weighted PNL with partial closes
|
|
2954
2955
|
if (signal._partial && signal._partial.length > 0) {
|
|
2955
2956
|
let totalWeightedPnl = 0;
|
|
2956
|
-
|
|
2957
|
+
// Open fee is paid once for the whole position
|
|
2958
|
+
let totalFees = GLOBAL_CONFIG.CC_PERCENT_FEE;
|
|
2959
|
+
// priceOpenWithSlippage is the same for all partials — compute once
|
|
2960
|
+
const priceOpenWithSlippage = signal.position === "long"
|
|
2961
|
+
? priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2962
|
+
: priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2957
2963
|
// Calculate PNL for each partial close
|
|
2958
2964
|
for (const partial of signal._partial) {
|
|
2959
2965
|
const partialPercent = partial.percent;
|
|
2960
|
-
const
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
let priceCloseWithSlippage;
|
|
2964
|
-
if (signal.position === "long") {
|
|
2965
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2966
|
-
priceCloseWithSlippage = partialPrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2967
|
-
}
|
|
2968
|
-
else {
|
|
2969
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2970
|
-
priceCloseWithSlippage = partialPrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2971
|
-
}
|
|
2966
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
2967
|
+
? partial.price * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2968
|
+
: partial.price * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2972
2969
|
// Calculate PNL for this partial
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
}
|
|
2977
|
-
else {
|
|
2978
|
-
partialPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2979
|
-
}
|
|
2970
|
+
const partialPnl = signal.position === "long"
|
|
2971
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
2972
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2980
2973
|
// Weight by percentage of position closed
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
2974
|
+
totalWeightedPnl += (partialPercent / 100) * partialPnl;
|
|
2975
|
+
// Close fee is proportional to the size of this partial and adjusted for slippage
|
|
2976
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (partialPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
2985
2977
|
}
|
|
2986
2978
|
// Calculate PNL for remaining position (if any)
|
|
2987
2979
|
// Compute totalClosed from _partial array
|
|
2988
2980
|
const totalClosed = signal._partial.reduce((sum, p) => sum + p.percent, 0);
|
|
2981
|
+
if (totalClosed > 100) {
|
|
2982
|
+
throw new Error(`Partial closes exceed 100%: ${totalClosed}% (signal id: ${signal.id})`);
|
|
2983
|
+
}
|
|
2989
2984
|
const remainingPercent = 100 - totalClosed;
|
|
2990
2985
|
if (remainingPercent > 0) {
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
if (signal.position === "long") {
|
|
2995
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2996
|
-
priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2997
|
-
}
|
|
2998
|
-
else {
|
|
2999
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3000
|
-
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3001
|
-
}
|
|
2986
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
2987
|
+
? priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2988
|
+
: priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3002
2989
|
// Calculate PNL for remaining
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
}
|
|
3007
|
-
else {
|
|
3008
|
-
remainingPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
3009
|
-
}
|
|
2990
|
+
const remainingPnl = signal.position === "long"
|
|
2991
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
2992
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
3010
2993
|
// Weight by remaining percentage
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
2994
|
+
totalWeightedPnl += (remainingPercent / 100) * remainingPnl;
|
|
2995
|
+
// Close fee is proportional to the remaining size and adjusted for slippage
|
|
2996
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (remainingPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
3015
2997
|
}
|
|
3016
2998
|
// Subtract total fees from weighted PNL
|
|
2999
|
+
// totalFees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partialPercent/100) × (closeWithSlip/openWithSlip)
|
|
3017
3000
|
const pnlPercentage = totalWeightedPnl - totalFees;
|
|
3018
3001
|
return {
|
|
3019
3002
|
pnlPercentage,
|
|
@@ -3034,8 +3017,8 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
3034
3017
|
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3035
3018
|
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3036
3019
|
}
|
|
3037
|
-
//
|
|
3038
|
-
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE *
|
|
3020
|
+
// Открытие: комиссия от цены входа; закрытие: комиссия от фактической цены выхода (с учётом slippage)
|
|
3021
|
+
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * (1 + priceCloseWithSlippage / priceOpenWithSlippage);
|
|
3039
3022
|
let pnlPercentage;
|
|
3040
3023
|
if (signal.position === "long") {
|
|
3041
3024
|
// LONG: прибыль при росте цены
|
|
@@ -30233,6 +30216,115 @@ function listenStrategyCommitOnce(filterFn, fn) {
|
|
|
30233
30216
|
return strategyCommitSubject.filter(filterFn).once(fn);
|
|
30234
30217
|
}
|
|
30235
30218
|
|
|
30219
|
+
const WARN_KB = 30;
|
|
30220
|
+
const DUMP_MESSAGES_METHOD_NAME = "dump.dumpMessages";
|
|
30221
|
+
/**
|
|
30222
|
+
* Dumps chat history and result data to markdown files in a structured directory.
|
|
30223
|
+
*
|
|
30224
|
+
* Creates a subfolder named after `resultId` inside `outputDir`.
|
|
30225
|
+
* If the subfolder already exists, the function returns early without overwriting.
|
|
30226
|
+
* Writes:
|
|
30227
|
+
* - `00_system_prompt.md` — system messages and output data summary
|
|
30228
|
+
* - `NN_user_message.md` — each user message as a separate file
|
|
30229
|
+
* - `NN_llm_output.md` — final LLM output data
|
|
30230
|
+
*
|
|
30231
|
+
* Warns via logger if any user message exceeds 30 KB.
|
|
30232
|
+
*
|
|
30233
|
+
* @param resultId - Unique identifier for the result (used as subfolder name)
|
|
30234
|
+
* @param history - Full chat history containing system, user, and assistant messages
|
|
30235
|
+
* @param result - Structured output data to include in the dump
|
|
30236
|
+
* @param outputDir - Base directory for output files (default: `./dump/strategy`)
|
|
30237
|
+
* @returns Promise that resolves when all files are written
|
|
30238
|
+
*
|
|
30239
|
+
* @example
|
|
30240
|
+
* ```typescript
|
|
30241
|
+
* import { dumpMessages } from "backtest-kit";
|
|
30242
|
+
*
|
|
30243
|
+
* await dumpMessages("result-123", history, { profit: 42 });
|
|
30244
|
+
* ```
|
|
30245
|
+
*/
|
|
30246
|
+
async function dumpMessages(resultId, history, result, outputDir = "./dump/strategy") {
|
|
30247
|
+
bt.loggerService.info(DUMP_MESSAGES_METHOD_NAME, {
|
|
30248
|
+
resultId,
|
|
30249
|
+
outputDir,
|
|
30250
|
+
});
|
|
30251
|
+
// Extract system messages and system reminders from existing data
|
|
30252
|
+
const systemMessages = history.filter((m) => m.role === "system");
|
|
30253
|
+
const userMessages = history.filter((m) => m.role === "user");
|
|
30254
|
+
const subfolderPath = path.join(outputDir, String(resultId));
|
|
30255
|
+
try {
|
|
30256
|
+
await fs.access(subfolderPath);
|
|
30257
|
+
return;
|
|
30258
|
+
}
|
|
30259
|
+
catch {
|
|
30260
|
+
await fs.mkdir(subfolderPath, { recursive: true });
|
|
30261
|
+
}
|
|
30262
|
+
{
|
|
30263
|
+
let summary = "# Outline Result Summary\n";
|
|
30264
|
+
{
|
|
30265
|
+
summary += "\n";
|
|
30266
|
+
summary += `**ResultId**: ${resultId}\n`;
|
|
30267
|
+
summary += "\n";
|
|
30268
|
+
}
|
|
30269
|
+
if (result) {
|
|
30270
|
+
summary += "## Output Data\n\n";
|
|
30271
|
+
summary += "```json\n";
|
|
30272
|
+
summary += JSON.stringify(result, null, 2);
|
|
30273
|
+
summary += "\n```\n\n";
|
|
30274
|
+
}
|
|
30275
|
+
// Add system messages to summary
|
|
30276
|
+
if (systemMessages.length > 0) {
|
|
30277
|
+
summary += "## System Messages\n\n";
|
|
30278
|
+
systemMessages.forEach((msg, idx) => {
|
|
30279
|
+
summary += `### System Message ${idx + 1}\n\n`;
|
|
30280
|
+
summary += msg.content;
|
|
30281
|
+
summary += "\n";
|
|
30282
|
+
});
|
|
30283
|
+
}
|
|
30284
|
+
const summaryFile = path.join(subfolderPath, "00_system_prompt.md");
|
|
30285
|
+
await fs.writeFile(summaryFile, summary, "utf8");
|
|
30286
|
+
}
|
|
30287
|
+
{
|
|
30288
|
+
await Promise.all(Array.from(userMessages.entries()).map(async ([idx, message]) => {
|
|
30289
|
+
const messageNum = String(idx + 1).padStart(2, "0");
|
|
30290
|
+
const contentFileName = `${messageNum}_user_message.md`;
|
|
30291
|
+
const contentFilePath = path.join(subfolderPath, contentFileName);
|
|
30292
|
+
{
|
|
30293
|
+
const messageSizeBytes = Buffer.byteLength(message.content, "utf8");
|
|
30294
|
+
const messageSizeKb = Math.floor(messageSizeBytes / 1024);
|
|
30295
|
+
if (messageSizeKb > WARN_KB) {
|
|
30296
|
+
console.warn(`User message ${idx + 1} is ${messageSizeBytes} bytes (${messageSizeKb}kb), which exceeds warning limit`);
|
|
30297
|
+
bt.loggerService.warn(DUMP_MESSAGES_METHOD_NAME, {
|
|
30298
|
+
resultId,
|
|
30299
|
+
messageIndex: idx + 1,
|
|
30300
|
+
messageSizeBytes,
|
|
30301
|
+
messageSizeKb,
|
|
30302
|
+
});
|
|
30303
|
+
}
|
|
30304
|
+
}
|
|
30305
|
+
let content = `# User Input ${idx + 1}\n\n`;
|
|
30306
|
+
content += `**ResultId**: ${resultId}\n\n`;
|
|
30307
|
+
content += message.content;
|
|
30308
|
+
content += "\n";
|
|
30309
|
+
await fs.writeFile(contentFilePath, content, "utf8");
|
|
30310
|
+
}));
|
|
30311
|
+
}
|
|
30312
|
+
{
|
|
30313
|
+
const messageNum = String(userMessages.length + 1).padStart(2, "0");
|
|
30314
|
+
const contentFileName = `${messageNum}_llm_output.md`;
|
|
30315
|
+
const contentFilePath = path.join(subfolderPath, contentFileName);
|
|
30316
|
+
let content = "# Full Outline Result\n\n";
|
|
30317
|
+
content += `**ResultId**: ${resultId}\n\n`;
|
|
30318
|
+
if (result) {
|
|
30319
|
+
content += "## Output Data\n\n";
|
|
30320
|
+
content += "```json\n";
|
|
30321
|
+
content += JSON.stringify(result, null, 2);
|
|
30322
|
+
content += "\n```\n";
|
|
30323
|
+
}
|
|
30324
|
+
await fs.writeFile(contentFilePath, content, "utf8");
|
|
30325
|
+
}
|
|
30326
|
+
}
|
|
30327
|
+
|
|
30236
30328
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
30237
30329
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
30238
30330
|
const BACKTEST_METHOD_NAME_STOP = "BacktestUtils.stop";
|
|
@@ -37546,6 +37638,7 @@ exports.commitPartialLoss = commitPartialLoss;
|
|
|
37546
37638
|
exports.commitPartialProfit = commitPartialProfit;
|
|
37547
37639
|
exports.commitTrailingStop = commitTrailingStop;
|
|
37548
37640
|
exports.commitTrailingTake = commitTrailingTake;
|
|
37641
|
+
exports.dumpMessages = dumpMessages;
|
|
37549
37642
|
exports.emitters = emitters;
|
|
37550
37643
|
exports.formatPrice = formatPrice;
|
|
37551
37644
|
exports.formatQuantity = formatQuantity;
|
package/build/index.mjs
CHANGED
|
@@ -2885,8 +2885,9 @@ class ExchangeConnectionService {
|
|
|
2885
2885
|
*
|
|
2886
2886
|
* For signals with partial closes:
|
|
2887
2887
|
* - Calculates weighted PNL: Σ(percent_i × pnl_i) for each partial + (remaining% × final_pnl)
|
|
2888
|
-
* - Each partial close has its own
|
|
2889
|
-
* -
|
|
2888
|
+
* - Each partial close has its own slippage
|
|
2889
|
+
* - Open fee is charged once; close fees are proportional to each partial's size
|
|
2890
|
+
* - Total fees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partial% / 100) × (closeWithSlip / openWithSlip)
|
|
2890
2891
|
*
|
|
2891
2892
|
* Formula breakdown:
|
|
2892
2893
|
* 1. Apply slippage to open/close prices (worse execution)
|
|
@@ -2895,7 +2896,7 @@ class ExchangeConnectionService {
|
|
|
2895
2896
|
* 2. Calculate raw PNL percentage
|
|
2896
2897
|
* - LONG: ((closePrice - openPrice) / openPrice) * 100
|
|
2897
2898
|
* - SHORT: ((openPrice - closePrice) / openPrice) * 100
|
|
2898
|
-
* 3. Subtract total fees
|
|
2899
|
+
* 3. Subtract total fees: open fee + close fee adjusted for slippage-affected execution price
|
|
2899
2900
|
*
|
|
2900
2901
|
* @param signal - Closed signal with position details and optional partial history
|
|
2901
2902
|
* @param priceClose - Actual close price at final exit
|
|
@@ -2933,67 +2934,49 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
2933
2934
|
// Calculate weighted PNL with partial closes
|
|
2934
2935
|
if (signal._partial && signal._partial.length > 0) {
|
|
2935
2936
|
let totalWeightedPnl = 0;
|
|
2936
|
-
|
|
2937
|
+
// Open fee is paid once for the whole position
|
|
2938
|
+
let totalFees = GLOBAL_CONFIG.CC_PERCENT_FEE;
|
|
2939
|
+
// priceOpenWithSlippage is the same for all partials — compute once
|
|
2940
|
+
const priceOpenWithSlippage = signal.position === "long"
|
|
2941
|
+
? priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2942
|
+
: priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2937
2943
|
// Calculate PNL for each partial close
|
|
2938
2944
|
for (const partial of signal._partial) {
|
|
2939
2945
|
const partialPercent = partial.percent;
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
let priceCloseWithSlippage;
|
|
2944
|
-
if (signal.position === "long") {
|
|
2945
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2946
|
-
priceCloseWithSlippage = partialPrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2947
|
-
}
|
|
2948
|
-
else {
|
|
2949
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2950
|
-
priceCloseWithSlippage = partialPrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2951
|
-
}
|
|
2946
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
2947
|
+
? partial.price * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2948
|
+
: partial.price * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2952
2949
|
// Calculate PNL for this partial
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
}
|
|
2957
|
-
else {
|
|
2958
|
-
partialPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2959
|
-
}
|
|
2950
|
+
const partialPnl = signal.position === "long"
|
|
2951
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
2952
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2960
2953
|
// Weight by percentage of position closed
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
2954
|
+
totalWeightedPnl += (partialPercent / 100) * partialPnl;
|
|
2955
|
+
// Close fee is proportional to the size of this partial and adjusted for slippage
|
|
2956
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (partialPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
2965
2957
|
}
|
|
2966
2958
|
// Calculate PNL for remaining position (if any)
|
|
2967
2959
|
// Compute totalClosed from _partial array
|
|
2968
2960
|
const totalClosed = signal._partial.reduce((sum, p) => sum + p.percent, 0);
|
|
2961
|
+
if (totalClosed > 100) {
|
|
2962
|
+
throw new Error(`Partial closes exceed 100%: ${totalClosed}% (signal id: ${signal.id})`);
|
|
2963
|
+
}
|
|
2969
2964
|
const remainingPercent = 100 - totalClosed;
|
|
2970
2965
|
if (remainingPercent > 0) {
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
if (signal.position === "long") {
|
|
2975
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2976
|
-
priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2977
|
-
}
|
|
2978
|
-
else {
|
|
2979
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2980
|
-
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2981
|
-
}
|
|
2966
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
2967
|
+
? priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2968
|
+
: priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2982
2969
|
// Calculate PNL for remaining
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
}
|
|
2987
|
-
else {
|
|
2988
|
-
remainingPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2989
|
-
}
|
|
2970
|
+
const remainingPnl = signal.position === "long"
|
|
2971
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
2972
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2990
2973
|
// Weight by remaining percentage
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
2974
|
+
totalWeightedPnl += (remainingPercent / 100) * remainingPnl;
|
|
2975
|
+
// Close fee is proportional to the remaining size and adjusted for slippage
|
|
2976
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (remainingPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
2995
2977
|
}
|
|
2996
2978
|
// Subtract total fees from weighted PNL
|
|
2979
|
+
// totalFees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partialPercent/100) × (closeWithSlip/openWithSlip)
|
|
2997
2980
|
const pnlPercentage = totalWeightedPnl - totalFees;
|
|
2998
2981
|
return {
|
|
2999
2982
|
pnlPercentage,
|
|
@@ -3014,8 +2997,8 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
3014
2997
|
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3015
2998
|
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3016
2999
|
}
|
|
3017
|
-
//
|
|
3018
|
-
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE *
|
|
3000
|
+
// Открытие: комиссия от цены входа; закрытие: комиссия от фактической цены выхода (с учётом slippage)
|
|
3001
|
+
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * (1 + priceCloseWithSlippage / priceOpenWithSlippage);
|
|
3019
3002
|
let pnlPercentage;
|
|
3020
3003
|
if (signal.position === "long") {
|
|
3021
3004
|
// LONG: прибыль при росте цены
|
|
@@ -30213,6 +30196,115 @@ function listenStrategyCommitOnce(filterFn, fn) {
|
|
|
30213
30196
|
return strategyCommitSubject.filter(filterFn).once(fn);
|
|
30214
30197
|
}
|
|
30215
30198
|
|
|
30199
|
+
const WARN_KB = 30;
|
|
30200
|
+
const DUMP_MESSAGES_METHOD_NAME = "dump.dumpMessages";
|
|
30201
|
+
/**
|
|
30202
|
+
* Dumps chat history and result data to markdown files in a structured directory.
|
|
30203
|
+
*
|
|
30204
|
+
* Creates a subfolder named after `resultId` inside `outputDir`.
|
|
30205
|
+
* If the subfolder already exists, the function returns early without overwriting.
|
|
30206
|
+
* Writes:
|
|
30207
|
+
* - `00_system_prompt.md` — system messages and output data summary
|
|
30208
|
+
* - `NN_user_message.md` — each user message as a separate file
|
|
30209
|
+
* - `NN_llm_output.md` — final LLM output data
|
|
30210
|
+
*
|
|
30211
|
+
* Warns via logger if any user message exceeds 30 KB.
|
|
30212
|
+
*
|
|
30213
|
+
* @param resultId - Unique identifier for the result (used as subfolder name)
|
|
30214
|
+
* @param history - Full chat history containing system, user, and assistant messages
|
|
30215
|
+
* @param result - Structured output data to include in the dump
|
|
30216
|
+
* @param outputDir - Base directory for output files (default: `./dump/strategy`)
|
|
30217
|
+
* @returns Promise that resolves when all files are written
|
|
30218
|
+
*
|
|
30219
|
+
* @example
|
|
30220
|
+
* ```typescript
|
|
30221
|
+
* import { dumpMessages } from "backtest-kit";
|
|
30222
|
+
*
|
|
30223
|
+
* await dumpMessages("result-123", history, { profit: 42 });
|
|
30224
|
+
* ```
|
|
30225
|
+
*/
|
|
30226
|
+
async function dumpMessages(resultId, history, result, outputDir = "./dump/strategy") {
|
|
30227
|
+
bt.loggerService.info(DUMP_MESSAGES_METHOD_NAME, {
|
|
30228
|
+
resultId,
|
|
30229
|
+
outputDir,
|
|
30230
|
+
});
|
|
30231
|
+
// Extract system messages and system reminders from existing data
|
|
30232
|
+
const systemMessages = history.filter((m) => m.role === "system");
|
|
30233
|
+
const userMessages = history.filter((m) => m.role === "user");
|
|
30234
|
+
const subfolderPath = path.join(outputDir, String(resultId));
|
|
30235
|
+
try {
|
|
30236
|
+
await fs__default.access(subfolderPath);
|
|
30237
|
+
return;
|
|
30238
|
+
}
|
|
30239
|
+
catch {
|
|
30240
|
+
await fs__default.mkdir(subfolderPath, { recursive: true });
|
|
30241
|
+
}
|
|
30242
|
+
{
|
|
30243
|
+
let summary = "# Outline Result Summary\n";
|
|
30244
|
+
{
|
|
30245
|
+
summary += "\n";
|
|
30246
|
+
summary += `**ResultId**: ${resultId}\n`;
|
|
30247
|
+
summary += "\n";
|
|
30248
|
+
}
|
|
30249
|
+
if (result) {
|
|
30250
|
+
summary += "## Output Data\n\n";
|
|
30251
|
+
summary += "```json\n";
|
|
30252
|
+
summary += JSON.stringify(result, null, 2);
|
|
30253
|
+
summary += "\n```\n\n";
|
|
30254
|
+
}
|
|
30255
|
+
// Add system messages to summary
|
|
30256
|
+
if (systemMessages.length > 0) {
|
|
30257
|
+
summary += "## System Messages\n\n";
|
|
30258
|
+
systemMessages.forEach((msg, idx) => {
|
|
30259
|
+
summary += `### System Message ${idx + 1}\n\n`;
|
|
30260
|
+
summary += msg.content;
|
|
30261
|
+
summary += "\n";
|
|
30262
|
+
});
|
|
30263
|
+
}
|
|
30264
|
+
const summaryFile = path.join(subfolderPath, "00_system_prompt.md");
|
|
30265
|
+
await fs__default.writeFile(summaryFile, summary, "utf8");
|
|
30266
|
+
}
|
|
30267
|
+
{
|
|
30268
|
+
await Promise.all(Array.from(userMessages.entries()).map(async ([idx, message]) => {
|
|
30269
|
+
const messageNum = String(idx + 1).padStart(2, "0");
|
|
30270
|
+
const contentFileName = `${messageNum}_user_message.md`;
|
|
30271
|
+
const contentFilePath = path.join(subfolderPath, contentFileName);
|
|
30272
|
+
{
|
|
30273
|
+
const messageSizeBytes = Buffer.byteLength(message.content, "utf8");
|
|
30274
|
+
const messageSizeKb = Math.floor(messageSizeBytes / 1024);
|
|
30275
|
+
if (messageSizeKb > WARN_KB) {
|
|
30276
|
+
console.warn(`User message ${idx + 1} is ${messageSizeBytes} bytes (${messageSizeKb}kb), which exceeds warning limit`);
|
|
30277
|
+
bt.loggerService.warn(DUMP_MESSAGES_METHOD_NAME, {
|
|
30278
|
+
resultId,
|
|
30279
|
+
messageIndex: idx + 1,
|
|
30280
|
+
messageSizeBytes,
|
|
30281
|
+
messageSizeKb,
|
|
30282
|
+
});
|
|
30283
|
+
}
|
|
30284
|
+
}
|
|
30285
|
+
let content = `# User Input ${idx + 1}\n\n`;
|
|
30286
|
+
content += `**ResultId**: ${resultId}\n\n`;
|
|
30287
|
+
content += message.content;
|
|
30288
|
+
content += "\n";
|
|
30289
|
+
await fs__default.writeFile(contentFilePath, content, "utf8");
|
|
30290
|
+
}));
|
|
30291
|
+
}
|
|
30292
|
+
{
|
|
30293
|
+
const messageNum = String(userMessages.length + 1).padStart(2, "0");
|
|
30294
|
+
const contentFileName = `${messageNum}_llm_output.md`;
|
|
30295
|
+
const contentFilePath = path.join(subfolderPath, contentFileName);
|
|
30296
|
+
let content = "# Full Outline Result\n\n";
|
|
30297
|
+
content += `**ResultId**: ${resultId}\n\n`;
|
|
30298
|
+
if (result) {
|
|
30299
|
+
content += "## Output Data\n\n";
|
|
30300
|
+
content += "```json\n";
|
|
30301
|
+
content += JSON.stringify(result, null, 2);
|
|
30302
|
+
content += "\n```\n";
|
|
30303
|
+
}
|
|
30304
|
+
await fs__default.writeFile(contentFilePath, content, "utf8");
|
|
30305
|
+
}
|
|
30306
|
+
}
|
|
30307
|
+
|
|
30216
30308
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
30217
30309
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
30218
30310
|
const BACKTEST_METHOD_NAME_STOP = "BacktestUtils.stop";
|
|
@@ -37472,4 +37564,4 @@ const set = (object, path, value) => {
|
|
|
37472
37564
|
}
|
|
37473
37565
|
};
|
|
37474
37566
|
|
|
37475
|
-
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };
|
|
37567
|
+
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -6739,6 +6739,55 @@ declare function getRawCandles(symbol: string, interval: CandleInterval, limit?:
|
|
|
6739
6739
|
*/
|
|
6740
6740
|
declare function getNextCandles(symbol: string, interval: CandleInterval, limit: number): Promise<ICandleData[]>;
|
|
6741
6741
|
|
|
6742
|
+
/** Unique identifier for a dump result. Can be a string or numeric ID. */
|
|
6743
|
+
type ResultId = string | number;
|
|
6744
|
+
/** Role of the message sender in LLM chat history. */
|
|
6745
|
+
type BaseRole = "assistant" | "system" | "user";
|
|
6746
|
+
/**
|
|
6747
|
+
* A single message in the chat history.
|
|
6748
|
+
* Used to represent system instructions, user input, or LLM responses.
|
|
6749
|
+
*/
|
|
6750
|
+
interface Message<Role extends BaseRole = BaseRole> {
|
|
6751
|
+
/**
|
|
6752
|
+
* The sender of the message.
|
|
6753
|
+
* - "system": System instructions and context
|
|
6754
|
+
* - "user": User input and questions
|
|
6755
|
+
* - "assistant": LLM responses
|
|
6756
|
+
*/
|
|
6757
|
+
role: Role;
|
|
6758
|
+
/**
|
|
6759
|
+
* The text content of the message.
|
|
6760
|
+
* Contains the actual message text sent or received.
|
|
6761
|
+
*/
|
|
6762
|
+
content: string;
|
|
6763
|
+
}
|
|
6764
|
+
/**
|
|
6765
|
+
* Dumps chat history and result data to markdown files in a structured directory.
|
|
6766
|
+
*
|
|
6767
|
+
* Creates a subfolder named after `resultId` inside `outputDir`.
|
|
6768
|
+
* If the subfolder already exists, the function returns early without overwriting.
|
|
6769
|
+
* Writes:
|
|
6770
|
+
* - `00_system_prompt.md` — system messages and output data summary
|
|
6771
|
+
* - `NN_user_message.md` — each user message as a separate file
|
|
6772
|
+
* - `NN_llm_output.md` — final LLM output data
|
|
6773
|
+
*
|
|
6774
|
+
* Warns via logger if any user message exceeds 30 KB.
|
|
6775
|
+
*
|
|
6776
|
+
* @param resultId - Unique identifier for the result (used as subfolder name)
|
|
6777
|
+
* @param history - Full chat history containing system, user, and assistant messages
|
|
6778
|
+
* @param result - Structured output data to include in the dump
|
|
6779
|
+
* @param outputDir - Base directory for output files (default: `./dump/strategy`)
|
|
6780
|
+
* @returns Promise that resolves when all files are written
|
|
6781
|
+
*
|
|
6782
|
+
* @example
|
|
6783
|
+
* ```typescript
|
|
6784
|
+
* import { dumpMessages } from "backtest-kit";
|
|
6785
|
+
*
|
|
6786
|
+
* await dumpMessages("result-123", history, { profit: 42 });
|
|
6787
|
+
* ```
|
|
6788
|
+
*/
|
|
6789
|
+
declare function dumpMessages<Data extends object = any>(resultId: ResultId, history: Message[], result: Data, outputDir?: string): Promise<void>;
|
|
6790
|
+
|
|
6742
6791
|
/**
|
|
6743
6792
|
* Portfolio heatmap statistics for a single symbol.
|
|
6744
6793
|
* Aggregated metrics across all strategies for one trading pair.
|
|
@@ -20832,4 +20881,4 @@ declare const backtest: {
|
|
|
20832
20881
|
loggerService: LoggerService;
|
|
20833
20882
|
};
|
|
20834
20883
|
|
|
20835
|
-
export { ActionBase, type ActivateScheduledCommit, type ActivateScheduledCommitNotification, type ActivePingContract, Backtest, type BacktestStatisticsModel, Breakeven, type BreakevenAvailableNotification, type BreakevenCommit, type BreakevenCommitNotification, type BreakevenContract, type BreakevenData, Cache, type CancelScheduledCommit, type CandleData, type CandleInterval, type ClosePendingCommit, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IActionSchema, type IActivateScheduledCommitRow, type IBidData, type IBreakevenCommitRow, type ICandleData, type ICommitRow, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type INotificationUtils, type IOrderBookData, type IPartialLossCommitRow, type IPartialProfitCommitRow, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicAction, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskSignalRow, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingParams, type ISizingParamsATR, type ISizingParamsFixedPercentage, type ISizingParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStorageSignalRow, type IStorageUtils, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IStrategyTickResultWaiting, type ITrailingStopCommitRow, type ITrailingTakeCommitRow, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, MethodContextService, type MetricStats, Notification, NotificationBacktest, type NotificationData, NotificationLive, type NotificationModel, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossAvailableNotification, type PartialLossCommit, type PartialLossCommitNotification, type PartialLossContract, type PartialProfitAvailableNotification, type PartialProfitCommit, type PartialProfitCommitNotification, type PartialProfitContract, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, type ProgressBacktestContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type SchedulePingContract, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, Storage, StorageBacktest, type StorageData, StorageLive, Strategy, type StrategyActionType, type StrategyCancelReason, type StrategyCloseReason, type StrategyCommitContract, type StrategyEvent, type StrategyStatisticsModel, type TMarkdownBase, type TNotificationUtilsCtor, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TStorageUtilsCtor, type TickEvent, type TrailingStopCommit, type TrailingStopCommitNotification, type TrailingTakeCommit, type TrailingTakeCommitNotification, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };
|
|
20884
|
+
export { ActionBase, type ActivateScheduledCommit, type ActivateScheduledCommitNotification, type ActivePingContract, Backtest, type BacktestStatisticsModel, Breakeven, type BreakevenAvailableNotification, type BreakevenCommit, type BreakevenCommitNotification, type BreakevenContract, type BreakevenData, Cache, type CancelScheduledCommit, type CandleData, type CandleInterval, type ClosePendingCommit, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IActionSchema, type IActivateScheduledCommitRow, type IBidData, type IBreakevenCommitRow, type ICandleData, type ICommitRow, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type INotificationUtils, type IOrderBookData, type IPartialLossCommitRow, type IPartialProfitCommitRow, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicAction, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskSignalRow, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingParams, type ISizingParamsATR, type ISizingParamsFixedPercentage, type ISizingParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStorageSignalRow, type IStorageUtils, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IStrategyTickResultWaiting, type ITrailingStopCommitRow, type ITrailingTakeCommitRow, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, MethodContextService, type MetricStats, Notification, NotificationBacktest, type NotificationData, NotificationLive, type NotificationModel, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossAvailableNotification, type PartialLossCommit, type PartialLossCommitNotification, type PartialLossContract, type PartialProfitAvailableNotification, type PartialProfitCommit, type PartialProfitCommitNotification, type PartialProfitContract, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, type ProgressBacktestContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type SchedulePingContract, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, Storage, StorageBacktest, type StorageData, StorageLive, Strategy, type StrategyActionType, type StrategyCancelReason, type StrategyCloseReason, type StrategyCommitContract, type StrategyEvent, type StrategyStatisticsModel, type TMarkdownBase, type TNotificationUtilsCtor, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TStorageUtilsCtor, type TickEvent, type TrailingStopCommit, type TrailingStopCommitNotification, type TrailingTakeCommit, type TrailingTakeCommitNotification, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };
|