@backtest-kit/cli 7.4.0 โ 7.6.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 +105 -0
- package/build/index.cjs +176 -27
- package/build/index.mjs +177 -28
- package/package.json +13 -13
- package/template/project/package.mustache +5 -5
- package/template/signal-info.mustache +20 -20
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
|
|
|
44
44
|
| **PineScript** | `--pine` | Run a local `.pine` indicator against exchange data |
|
|
45
45
|
| **Pine Editor** | `--editor` | Open the visual Pine Script editor in the browser |
|
|
46
46
|
| **Candle Dump** | `--dump` | Fetch and save raw OHLCV candles to a file |
|
|
47
|
+
| **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price and direction |
|
|
47
48
|
| **Flush** | `--flush` | Delete report/log/markdown/agent folders from strategy dump dir |
|
|
48
49
|
| **Init Project** | `--init` | Scaffold a new backtest-kit project |
|
|
49
50
|
|
|
@@ -1065,6 +1066,110 @@ Or add it to `package.json`:
|
|
|
1065
1066
|
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
1066
1067
|
```
|
|
1067
1068
|
|
|
1069
|
+
## ๐ PnL Debug (`--pnldebug`)
|
|
1070
|
+
|
|
1071
|
+
`@backtest-kit/cli` can simulate a hypothetical position minute by minute and print running PnL, peak profit, and maximum drawdown for each candle โ without placing any trades or loading a strategy file.
|
|
1072
|
+
|
|
1073
|
+
### CLI Flags
|
|
1074
|
+
|
|
1075
|
+
| Flag | Type | Description |
|
|
1076
|
+
|------|------|-------------|
|
|
1077
|
+
| `--pnldebug` | boolean | Enable PnL debug mode |
|
|
1078
|
+
| `--priceopen` | number | Entry price (required) |
|
|
1079
|
+
| `--direction` | string | `long` or `short` (default: `long`) |
|
|
1080
|
+
| `--when` | string | Start timestamp โ ISO 8601 or Unix ms (default: now) |
|
|
1081
|
+
| `--minutes` | string | Number of 1m candles to simulate (default: `60`) |
|
|
1082
|
+
| `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
|
|
1083
|
+
| `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
|
|
1084
|
+
| `--output` | string | Output file base name (default: `{SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP}`) |
|
|
1085
|
+
| `--json` | boolean | Save results as JSON array to `./dump/<output>.json` |
|
|
1086
|
+
| `--jsonl` | boolean | Save results as JSONL to `./dump/<output>.jsonl` |
|
|
1087
|
+
| `--markdown` | boolean | Save results as Markdown table to `./dump/<output>.md` |
|
|
1088
|
+
|
|
1089
|
+
### Output columns
|
|
1090
|
+
|
|
1091
|
+
| Column | Description |
|
|
1092
|
+
|--------|-------------|
|
|
1093
|
+
| `min` | Minute offset from start (1-based) |
|
|
1094
|
+
| `timestamp` | Candle timestamp (ISO 8601) |
|
|
1095
|
+
| `close` | Candle close price |
|
|
1096
|
+
| `pnl%` | Running PnL vs entry price (signed %) |
|
|
1097
|
+
| `peak%` | Highest PnL reached so far (always โฅ 0) |
|
|
1098
|
+
| `drawdown%` | Lowest PnL reached so far (always โค 0) |
|
|
1099
|
+
|
|
1100
|
+
### Exchange via `pnldebug.module`
|
|
1101
|
+
|
|
1102
|
+
By default the CLI registers CCXT Binance automatically. To use a different exchange, create a `modules/pnldebug.module.ts` file in the current working directory โ the CLI loads it automatically before fetching candles.
|
|
1103
|
+
|
|
1104
|
+
```typescript
|
|
1105
|
+
// modules/pnldebug.module.ts
|
|
1106
|
+
import { addExchangeSchema } from "backtest-kit";
|
|
1107
|
+
import ccxt from "ccxt";
|
|
1108
|
+
|
|
1109
|
+
addExchangeSchema({
|
|
1110
|
+
exchangeName: "my-exchange",
|
|
1111
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
1112
|
+
const exchange = new ccxt.bybit({ enableRateLimit: true });
|
|
1113
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
1114
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
|
|
1115
|
+
timestamp, open, high, low, close, volume,
|
|
1116
|
+
}));
|
|
1117
|
+
},
|
|
1118
|
+
formatPrice: (symbol, price) => price.toFixed(2),
|
|
1119
|
+
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
|
|
1120
|
+
});
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### Usage
|
|
1124
|
+
|
|
1125
|
+
Print to stdout (default table format):
|
|
1126
|
+
|
|
1127
|
+
```bash
|
|
1128
|
+
npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
Save as Markdown:
|
|
1132
|
+
|
|
1133
|
+
```bash
|
|
1134
|
+
npx @backtest-kit/cli --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
1135
|
+
# โ ./dump/BTCUSDT_long_67956.73_{timestamp}.md
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
Override the output file name with `--output`:
|
|
1139
|
+
|
|
1140
|
+
```bash
|
|
1141
|
+
npx @backtest-kit/cli --pnldebug --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120 \
|
|
1142
|
+
--jsonl --output feb25_short_debug
|
|
1143
|
+
# โ ./dump/feb25_short_debug.jsonl
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
Or add it to `package.json`:
|
|
1147
|
+
|
|
1148
|
+
```json
|
|
1149
|
+
{
|
|
1150
|
+
"scripts": {
|
|
1151
|
+
"pnldebug": "npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when \"2025-02-25\" --minutes 120"
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
```bash
|
|
1157
|
+
npm run pnldebug
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
### Example stdout output
|
|
1161
|
+
|
|
1162
|
+
```
|
|
1163
|
+
Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
|
|
1164
|
+
|
|
1165
|
+
min | timestamp | close | pnl% | peak% | drawdown%
|
|
1166
|
+
-----------------------------------------------------------------------------------
|
|
1167
|
+
1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
|
|
1168
|
+
2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
|
|
1169
|
+
...
|
|
1170
|
+
120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1068
1173
|
## ๐๏ธ Flushing Strategy Output (`--flush`)
|
|
1069
1174
|
|
|
1070
1175
|
`@backtest-kit/cli` can delete generated output folders from one or more strategy dump directories without touching cached candle data.
|
package/build/index.cjs
CHANGED
|
@@ -570,6 +570,22 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
570
570
|
type: "boolean",
|
|
571
571
|
default: false,
|
|
572
572
|
},
|
|
573
|
+
pnldebug: {
|
|
574
|
+
type: "boolean",
|
|
575
|
+
default: false,
|
|
576
|
+
},
|
|
577
|
+
priceopen: {
|
|
578
|
+
type: "string",
|
|
579
|
+
default: "",
|
|
580
|
+
},
|
|
581
|
+
direction: {
|
|
582
|
+
type: "string",
|
|
583
|
+
default: "",
|
|
584
|
+
},
|
|
585
|
+
minutes: {
|
|
586
|
+
type: "string",
|
|
587
|
+
default: "",
|
|
588
|
+
},
|
|
573
589
|
init: {
|
|
574
590
|
type: "boolean",
|
|
575
591
|
default: false,
|
|
@@ -716,11 +732,15 @@ class SetupUtils {
|
|
|
716
732
|
BacktestKit.Markdown.enable();
|
|
717
733
|
BacktestKit.Report.enable();
|
|
718
734
|
BacktestKit.Dump.enable();
|
|
735
|
+
BacktestKit.State.enable();
|
|
719
736
|
BacktestKit.Memory.enable();
|
|
720
737
|
}
|
|
721
738
|
{
|
|
722
739
|
BacktestKit.Dump.useMarkdown();
|
|
723
|
-
|
|
740
|
+
}
|
|
741
|
+
{
|
|
742
|
+
BacktestKit.SessionLive.usePersist();
|
|
743
|
+
BacktestKit.SessionBacktest.useLocal();
|
|
724
744
|
}
|
|
725
745
|
{
|
|
726
746
|
BacktestKit.StorageLive.usePersist();
|
|
@@ -734,6 +754,18 @@ class SetupUtils {
|
|
|
734
754
|
BacktestKit.NotificationLive.usePersist();
|
|
735
755
|
BacktestKit.NotificationBacktest.useMemory();
|
|
736
756
|
}
|
|
757
|
+
{
|
|
758
|
+
BacktestKit.RecentLive.usePersist();
|
|
759
|
+
BacktestKit.RecentBacktest.useMemory();
|
|
760
|
+
}
|
|
761
|
+
{
|
|
762
|
+
BacktestKit.MemoryLive.usePersist();
|
|
763
|
+
BacktestKit.MemoryBacktest.useLocal();
|
|
764
|
+
}
|
|
765
|
+
{
|
|
766
|
+
BacktestKit.StateLive.usePersist();
|
|
767
|
+
BacktestKit.StateBacktest.useLocal();
|
|
768
|
+
}
|
|
737
769
|
{
|
|
738
770
|
BacktestKit.Markdown.useDummy();
|
|
739
771
|
BacktestKit.Log.useJsonl();
|
|
@@ -776,6 +808,8 @@ class SetupUtils {
|
|
|
776
808
|
BacktestKit.PersistIntervalAdapter.clear();
|
|
777
809
|
BacktestKit.PersistMemoryAdapter.clear();
|
|
778
810
|
BacktestKit.PersistRecentAdapter.clear();
|
|
811
|
+
BacktestKit.PersistStateAdapter.clear();
|
|
812
|
+
BacktestKit.PersistSessionAdapter.clear();
|
|
779
813
|
}
|
|
780
814
|
{
|
|
781
815
|
BacktestKit.Dump.clear();
|
|
@@ -967,7 +1001,7 @@ class WalkerMainService {
|
|
|
967
1001
|
}
|
|
968
1002
|
strategyMap.set(strategyName, entryPoint);
|
|
969
1003
|
}
|
|
970
|
-
sessionMap.set(entryPoint, BacktestKit.
|
|
1004
|
+
sessionMap.set(entryPoint, BacktestKit.System.createSnapshot());
|
|
971
1005
|
BacktestKit.Cache.resetCounter();
|
|
972
1006
|
BacktestKit.Interval.resetCounter();
|
|
973
1007
|
}
|
|
@@ -2965,14 +2999,14 @@ const cli = {
|
|
|
2965
2999
|
};
|
|
2966
3000
|
init();
|
|
2967
3001
|
|
|
2968
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
|
|
3002
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "flush", "init", "help", "version"];
|
|
2969
3003
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2970
3004
|
const HELP_TEXT$1 = `
|
|
2971
3005
|
Example:
|
|
2972
3006
|
|
|
2973
3007
|
node ${ENTRY_PATH$1} --help
|
|
2974
3008
|
`.trimStart();
|
|
2975
|
-
const main$
|
|
3009
|
+
const main$e = async () => {
|
|
2976
3010
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2977
3011
|
return;
|
|
2978
3012
|
}
|
|
@@ -2980,14 +3014,14 @@ const main$d = async () => {
|
|
|
2980
3014
|
if (MODES.some((mode) => values[mode])) {
|
|
2981
3015
|
return;
|
|
2982
3016
|
}
|
|
2983
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
3017
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n`);
|
|
2984
3018
|
process.stdout.write("\n");
|
|
2985
3019
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2986
3020
|
process.stdout.write("\n");
|
|
2987
3021
|
process.stdout.write(HELP_TEXT$1);
|
|
2988
3022
|
process.exit(0);
|
|
2989
3023
|
};
|
|
2990
|
-
main$
|
|
3024
|
+
main$e();
|
|
2991
3025
|
|
|
2992
3026
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
2993
3027
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
@@ -3003,7 +3037,7 @@ const flush = async (entryPoint) => {
|
|
|
3003
3037
|
console.log(`Removed: ${target}`);
|
|
3004
3038
|
}
|
|
3005
3039
|
};
|
|
3006
|
-
const main$
|
|
3040
|
+
const main$d = async () => {
|
|
3007
3041
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3008
3042
|
return;
|
|
3009
3043
|
}
|
|
@@ -3020,7 +3054,7 @@ const main$c = async () => {
|
|
|
3020
3054
|
}
|
|
3021
3055
|
process.exit(0);
|
|
3022
3056
|
};
|
|
3023
|
-
main$
|
|
3057
|
+
main$d();
|
|
3024
3058
|
|
|
3025
3059
|
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
3026
3060
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3042,7 +3076,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
3042
3076
|
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
3043
3077
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3044
3078
|
});
|
|
3045
|
-
const main$
|
|
3079
|
+
const main$c = async () => {
|
|
3046
3080
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3047
3081
|
return;
|
|
3048
3082
|
}
|
|
@@ -3057,7 +3091,7 @@ const main$b = async () => {
|
|
|
3057
3091
|
await cli.backtestMainService.connect();
|
|
3058
3092
|
listenGracefulShutdown$5();
|
|
3059
3093
|
};
|
|
3060
|
-
main$
|
|
3094
|
+
main$c();
|
|
3061
3095
|
|
|
3062
3096
|
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
3063
3097
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3075,7 +3109,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
3075
3109
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
3076
3110
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3077
3111
|
});
|
|
3078
|
-
const main$
|
|
3112
|
+
const main$b = async () => {
|
|
3079
3113
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3080
3114
|
return;
|
|
3081
3115
|
}
|
|
@@ -3091,7 +3125,7 @@ const main$a = async () => {
|
|
|
3091
3125
|
listenGracefulShutdown$4();
|
|
3092
3126
|
await cli.walkerMainService.connect();
|
|
3093
3127
|
};
|
|
3094
|
-
main$
|
|
3128
|
+
main$b();
|
|
3095
3129
|
|
|
3096
3130
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
3097
3131
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3112,7 +3146,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
3112
3146
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
3113
3147
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3114
3148
|
});
|
|
3115
|
-
const main$
|
|
3149
|
+
const main$a = async () => {
|
|
3116
3150
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3117
3151
|
return;
|
|
3118
3152
|
}
|
|
@@ -3123,7 +3157,7 @@ const main$9 = async () => {
|
|
|
3123
3157
|
cli.paperMainService.connect();
|
|
3124
3158
|
listenGracefulShutdown$3();
|
|
3125
3159
|
};
|
|
3126
|
-
main$
|
|
3160
|
+
main$a();
|
|
3127
3161
|
|
|
3128
3162
|
const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
3129
3163
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -3144,7 +3178,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
3144
3178
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
3145
3179
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3146
3180
|
});
|
|
3147
|
-
const main$
|
|
3181
|
+
const main$9 = async () => {
|
|
3148
3182
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3149
3183
|
return;
|
|
3150
3184
|
}
|
|
@@ -3155,7 +3189,7 @@ const main$8 = async () => {
|
|
|
3155
3189
|
await cli.liveMainService.connect();
|
|
3156
3190
|
listenGracefulShutdown$2();
|
|
3157
3191
|
};
|
|
3158
|
-
main$
|
|
3192
|
+
main$9();
|
|
3159
3193
|
|
|
3160
3194
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
3161
3195
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -3165,7 +3199,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
3165
3199
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
3166
3200
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
3167
3201
|
});
|
|
3168
|
-
const main$
|
|
3202
|
+
const main$8 = async () => {
|
|
3169
3203
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3170
3204
|
return;
|
|
3171
3205
|
}
|
|
@@ -3175,7 +3209,7 @@ const main$7 = async () => {
|
|
|
3175
3209
|
}
|
|
3176
3210
|
listenGracefulShutdown$1();
|
|
3177
3211
|
};
|
|
3178
|
-
main$
|
|
3212
|
+
main$8();
|
|
3179
3213
|
|
|
3180
3214
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
3181
3215
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -3185,7 +3219,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
3185
3219
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
3186
3220
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
3187
3221
|
});
|
|
3188
|
-
const main$
|
|
3222
|
+
const main$7 = async () => {
|
|
3189
3223
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3190
3224
|
return;
|
|
3191
3225
|
}
|
|
@@ -3195,7 +3229,7 @@ const main$6 = async () => {
|
|
|
3195
3229
|
}
|
|
3196
3230
|
listenGracefulShutdown();
|
|
3197
3231
|
};
|
|
3198
|
-
main$
|
|
3232
|
+
main$7();
|
|
3199
3233
|
|
|
3200
3234
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
3201
3235
|
const keys = Object.keys(schema);
|
|
@@ -3217,7 +3251,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
3217
3251
|
}
|
|
3218
3252
|
return rows;
|
|
3219
3253
|
};
|
|
3220
|
-
const main$
|
|
3254
|
+
const main$6 = async () => {
|
|
3221
3255
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3222
3256
|
return;
|
|
3223
3257
|
}
|
|
@@ -3297,9 +3331,9 @@ const main$5 = async () => {
|
|
|
3297
3331
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
3298
3332
|
process.exit(0);
|
|
3299
3333
|
};
|
|
3300
|
-
main$
|
|
3334
|
+
main$6();
|
|
3301
3335
|
|
|
3302
|
-
const main$
|
|
3336
|
+
const main$5 = async () => {
|
|
3303
3337
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3304
3338
|
return;
|
|
3305
3339
|
}
|
|
@@ -3334,9 +3368,9 @@ const main$4 = async () => {
|
|
|
3334
3368
|
};
|
|
3335
3369
|
process.on("SIGINT", beforeExit);
|
|
3336
3370
|
};
|
|
3337
|
-
main$
|
|
3371
|
+
main$5();
|
|
3338
3372
|
|
|
3339
|
-
const main$
|
|
3373
|
+
const main$4 = async () => {
|
|
3340
3374
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3341
3375
|
return;
|
|
3342
3376
|
}
|
|
@@ -3397,6 +3431,102 @@ const main$3 = async () => {
|
|
|
3397
3431
|
console.log(JSON.stringify(candles, null, 2));
|
|
3398
3432
|
process.exit(0);
|
|
3399
3433
|
};
|
|
3434
|
+
main$4();
|
|
3435
|
+
|
|
3436
|
+
const main$3 = async () => {
|
|
3437
|
+
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const { values } = getArgs();
|
|
3441
|
+
if (!values.pnldebug) {
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
await cli.moduleConnectionService.loadModule("./pnldebug.module");
|
|
3445
|
+
{
|
|
3446
|
+
await cli.exchangeSchemaService.addSchema();
|
|
3447
|
+
await cli.symbolSchemaService.addSchema();
|
|
3448
|
+
}
|
|
3449
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
3450
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
3451
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
3452
|
+
const priceOpenStr = values.priceopen;
|
|
3453
|
+
if (!priceOpenStr) {
|
|
3454
|
+
console.error("Error: --priceopen is required");
|
|
3455
|
+
process.exit(1);
|
|
3456
|
+
}
|
|
3457
|
+
const priceOpen = parseFloat(priceOpenStr);
|
|
3458
|
+
if (isNaN(priceOpen)) {
|
|
3459
|
+
console.error(`Error: --priceopen must be a number, got: ${priceOpenStr}`);
|
|
3460
|
+
process.exit(1);
|
|
3461
|
+
}
|
|
3462
|
+
const direction = (values.direction || "long").toLowerCase();
|
|
3463
|
+
if (direction !== "long" && direction !== "short") {
|
|
3464
|
+
console.error(`Error: --direction must be 'long' or 'short', got: ${direction}`);
|
|
3465
|
+
process.exit(1);
|
|
3466
|
+
}
|
|
3467
|
+
const whenStr = values.when || Date.now().toString();
|
|
3468
|
+
const whenStamp = Date.parse(whenStr);
|
|
3469
|
+
const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
|
|
3470
|
+
const timestamp = BacktestKit.alignToInterval(when, "1m").getTime();
|
|
3471
|
+
const minutesStr = values.minutes || "60";
|
|
3472
|
+
const minutesNum = parseInt(minutesStr);
|
|
3473
|
+
const minutes = isNaN(minutesNum) ? 60 : minutesNum;
|
|
3474
|
+
const candles = await BacktestKit.Exchange.getRawCandles(symbol, "1m", { exchangeName }, minutes, undefined, timestamp);
|
|
3475
|
+
if (candles.length === 0) {
|
|
3476
|
+
console.error("Error: no candles returned for the given parameters");
|
|
3477
|
+
process.exit(1);
|
|
3478
|
+
}
|
|
3479
|
+
let peak = 0;
|
|
3480
|
+
let drawdown = 0;
|
|
3481
|
+
const rows = candles.map((c, i) => {
|
|
3482
|
+
const pnl = direction === "short"
|
|
3483
|
+
? (priceOpen - c.close) / priceOpen * 100
|
|
3484
|
+
: (c.close - priceOpen) / priceOpen * 100;
|
|
3485
|
+
if (pnl > peak)
|
|
3486
|
+
peak = pnl;
|
|
3487
|
+
if (pnl < drawdown)
|
|
3488
|
+
drawdown = pnl;
|
|
3489
|
+
return { min: i + 1, timestamp: c.timestamp, close: c.close, pnl, peak, drawdown };
|
|
3490
|
+
});
|
|
3491
|
+
const dumpName = values.output || `${symbol}_${direction}_${priceOpen}_${timestamp}`;
|
|
3492
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
3493
|
+
if (values.json) {
|
|
3494
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
3495
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
3496
|
+
await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
3497
|
+
console.log(`Saved: ${filePath}`);
|
|
3498
|
+
process.exit(0);
|
|
3499
|
+
}
|
|
3500
|
+
if (values.jsonl) {
|
|
3501
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
|
|
3502
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
3503
|
+
await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
3504
|
+
console.log(`Saved: ${filePath}`);
|
|
3505
|
+
process.exit(0);
|
|
3506
|
+
}
|
|
3507
|
+
if (values.markdown) {
|
|
3508
|
+
const header = `| min | timestamp | close | pnl% | peak% | drawdown% |\n| --- | --- | --- | --- | --- | --- |`;
|
|
3509
|
+
const mdRows = rows.map((r) => `| ${r.min} | ${new Date(r.timestamp).toISOString()} | ${r.close.toFixed(2)} | ${(r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2)}% | +${r.peak.toFixed(2)}% | ${r.drawdown.toFixed(2)}% |`);
|
|
3510
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
3511
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
3512
|
+
await fs$1.writeFile(filePath, [header, ...mdRows].join("\n"), "utf-8");
|
|
3513
|
+
console.log(`Saved: ${filePath}`);
|
|
3514
|
+
process.exit(0);
|
|
3515
|
+
}
|
|
3516
|
+
console.log(`Symbol: ${symbol} | Direction: ${direction} | PriceOpen: ${priceOpen} | From: ${new Date(timestamp).toISOString()} | Minutes: ${minutes}\n`);
|
|
3517
|
+
console.log(`${"min".padStart(5)} | ${"timestamp".padEnd(24)} | ${"close".padStart(12)} | ${"pnl%".padStart(8)} | ${"peak%".padStart(8)} | ${"drawdown%".padStart(10)}`);
|
|
3518
|
+
console.log("-".repeat(83));
|
|
3519
|
+
for (const r of rows) {
|
|
3520
|
+
const min = String(r.min).padStart(5);
|
|
3521
|
+
const ts = new Date(r.timestamp).toISOString().padEnd(24);
|
|
3522
|
+
const close = r.close.toFixed(2).padStart(12);
|
|
3523
|
+
const pnlStr = (r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2) + "%";
|
|
3524
|
+
const peakStr = "+" + r.peak.toFixed(2) + "%";
|
|
3525
|
+
const drawdownStr = r.drawdown.toFixed(2) + "%";
|
|
3526
|
+
console.log(`${min} | ${ts} | ${close} | ${pnlStr.padStart(8)} | ${peakStr.padStart(8)} | ${drawdownStr.padStart(10)}`);
|
|
3527
|
+
}
|
|
3528
|
+
process.exit(0);
|
|
3529
|
+
};
|
|
3400
3530
|
main$3();
|
|
3401
3531
|
|
|
3402
3532
|
const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
@@ -3515,6 +3645,7 @@ Modes:
|
|
|
3515
3645
|
--pine <entry> Execute a local .pine indicator file
|
|
3516
3646
|
--editor Open the Pine Script visual editor in the browser
|
|
3517
3647
|
--dump Fetch and save raw OHLCV candles
|
|
3648
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
3518
3649
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
3519
3650
|
--init Scaffold a new project in the current directory
|
|
3520
3651
|
--help Print this help message
|
|
@@ -3587,6 +3718,21 @@ Candle dump flags (--dump):
|
|
|
3587
3718
|
|
|
3588
3719
|
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
3589
3720
|
|
|
3721
|
+
PnL debug flags (--pnldebug):
|
|
3722
|
+
|
|
3723
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
3724
|
+
--priceopen <number> Entry price (required)
|
|
3725
|
+
--direction <string> Position direction: long or short (default: long)
|
|
3726
|
+
--when <string> Start timestamp โ ISO 8601 or Unix ms (default: now)
|
|
3727
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
3728
|
+
--exchange <string> Exchange name (default: first registered)
|
|
3729
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
3730
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
3731
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
3732
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
3733
|
+
|
|
3734
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
3735
|
+
|
|
3590
3736
|
Flush flags (--flush):
|
|
3591
3737
|
|
|
3592
3738
|
One or more positional entry points. For each entry point the following
|
|
@@ -3609,6 +3755,7 @@ Module hooks (loaded automatically by each mode):
|
|
|
3609
3755
|
modules/pine.module --pine Exchange schema for PineScript runs
|
|
3610
3756
|
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
3611
3757
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
3758
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
3612
3759
|
|
|
3613
3760
|
--flush has no associated module. It only removes dump subdirectories.
|
|
3614
3761
|
|
|
@@ -3632,6 +3779,8 @@ Examples:
|
|
|
3632
3779
|
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
3633
3780
|
node ${ENTRY_PATH} --editor
|
|
3634
3781
|
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
3782
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
3783
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
3635
3784
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
3636
3785
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
3637
3786
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -3644,7 +3793,7 @@ const main$1 = async () => {
|
|
|
3644
3793
|
if (!values.help) {
|
|
3645
3794
|
return;
|
|
3646
3795
|
}
|
|
3647
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
3796
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n\n`);
|
|
3648
3797
|
process.stdout.write(HELP_TEXT);
|
|
3649
3798
|
process.exit(0);
|
|
3650
3799
|
};
|
|
@@ -3658,7 +3807,7 @@ const main = async () => {
|
|
|
3658
3807
|
if (!values.version) {
|
|
3659
3808
|
return;
|
|
3660
3809
|
}
|
|
3661
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
3810
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n`);
|
|
3662
3811
|
process.exit(0);
|
|
3663
3812
|
};
|
|
3664
3813
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, Notification, Recent, Storage, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, RecentLive, RecentBacktest, NotificationLive, NotificationBacktest, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, listStrategySchema, overrideExchangeSchema, Backtest,
|
|
3
|
+
import { setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, Notification, Recent, Storage, Markdown, Report, Dump, State, Memory, SessionLive, SessionBacktest, StorageLive, StorageBacktest, RecentLive, RecentBacktest, NotificationLive, NotificationBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, listenSignalNotify, Exchange } from 'backtest-kit';
|
|
4
4
|
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, createAwaiter, execpool, queued, sleep, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
|
|
5
5
|
import fs, { constants } from 'fs';
|
|
6
6
|
import * as stackTrace from 'stack-trace';
|
|
@@ -545,6 +545,22 @@ const getArgs = singleshot(() => {
|
|
|
545
545
|
type: "boolean",
|
|
546
546
|
default: false,
|
|
547
547
|
},
|
|
548
|
+
pnldebug: {
|
|
549
|
+
type: "boolean",
|
|
550
|
+
default: false,
|
|
551
|
+
},
|
|
552
|
+
priceopen: {
|
|
553
|
+
type: "string",
|
|
554
|
+
default: "",
|
|
555
|
+
},
|
|
556
|
+
direction: {
|
|
557
|
+
type: "string",
|
|
558
|
+
default: "",
|
|
559
|
+
},
|
|
560
|
+
minutes: {
|
|
561
|
+
type: "string",
|
|
562
|
+
default: "",
|
|
563
|
+
},
|
|
548
564
|
init: {
|
|
549
565
|
type: "boolean",
|
|
550
566
|
default: false,
|
|
@@ -691,11 +707,15 @@ class SetupUtils {
|
|
|
691
707
|
Markdown.enable();
|
|
692
708
|
Report.enable();
|
|
693
709
|
Dump.enable();
|
|
710
|
+
State.enable();
|
|
694
711
|
Memory.enable();
|
|
695
712
|
}
|
|
696
713
|
{
|
|
697
714
|
Dump.useMarkdown();
|
|
698
|
-
|
|
715
|
+
}
|
|
716
|
+
{
|
|
717
|
+
SessionLive.usePersist();
|
|
718
|
+
SessionBacktest.useLocal();
|
|
699
719
|
}
|
|
700
720
|
{
|
|
701
721
|
StorageLive.usePersist();
|
|
@@ -709,6 +729,18 @@ class SetupUtils {
|
|
|
709
729
|
NotificationLive.usePersist();
|
|
710
730
|
NotificationBacktest.useMemory();
|
|
711
731
|
}
|
|
732
|
+
{
|
|
733
|
+
RecentLive.usePersist();
|
|
734
|
+
RecentBacktest.useMemory();
|
|
735
|
+
}
|
|
736
|
+
{
|
|
737
|
+
MemoryLive.usePersist();
|
|
738
|
+
MemoryBacktest.useLocal();
|
|
739
|
+
}
|
|
740
|
+
{
|
|
741
|
+
StateLive.usePersist();
|
|
742
|
+
StateBacktest.useLocal();
|
|
743
|
+
}
|
|
712
744
|
{
|
|
713
745
|
Markdown.useDummy();
|
|
714
746
|
Log.useJsonl();
|
|
@@ -751,6 +783,8 @@ class SetupUtils {
|
|
|
751
783
|
PersistIntervalAdapter.clear();
|
|
752
784
|
PersistMemoryAdapter.clear();
|
|
753
785
|
PersistRecentAdapter.clear();
|
|
786
|
+
PersistStateAdapter.clear();
|
|
787
|
+
PersistSessionAdapter.clear();
|
|
754
788
|
}
|
|
755
789
|
{
|
|
756
790
|
Dump.clear();
|
|
@@ -942,7 +976,7 @@ class WalkerMainService {
|
|
|
942
976
|
}
|
|
943
977
|
strategyMap.set(strategyName, entryPoint);
|
|
944
978
|
}
|
|
945
|
-
sessionMap.set(entryPoint,
|
|
979
|
+
sessionMap.set(entryPoint, System.createSnapshot());
|
|
946
980
|
Cache.resetCounter();
|
|
947
981
|
Interval.resetCounter();
|
|
948
982
|
}
|
|
@@ -2936,14 +2970,14 @@ const cli = {
|
|
|
2936
2970
|
};
|
|
2937
2971
|
init();
|
|
2938
2972
|
|
|
2939
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
|
|
2973
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "flush", "init", "help", "version"];
|
|
2940
2974
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2941
2975
|
const HELP_TEXT$1 = `
|
|
2942
2976
|
Example:
|
|
2943
2977
|
|
|
2944
2978
|
node ${ENTRY_PATH$1} --help
|
|
2945
2979
|
`.trimStart();
|
|
2946
|
-
const main$
|
|
2980
|
+
const main$e = async () => {
|
|
2947
2981
|
if (!getEntry(import.meta.url)) {
|
|
2948
2982
|
return;
|
|
2949
2983
|
}
|
|
@@ -2951,14 +2985,14 @@ const main$d = async () => {
|
|
|
2951
2985
|
if (MODES.some((mode) => values[mode])) {
|
|
2952
2986
|
return;
|
|
2953
2987
|
}
|
|
2954
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
2988
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n`);
|
|
2955
2989
|
process.stdout.write("\n");
|
|
2956
2990
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2957
2991
|
process.stdout.write("\n");
|
|
2958
2992
|
process.stdout.write(HELP_TEXT$1);
|
|
2959
2993
|
process.exit(0);
|
|
2960
2994
|
};
|
|
2961
|
-
main$
|
|
2995
|
+
main$e();
|
|
2962
2996
|
|
|
2963
2997
|
const notifyShutdown = singleshot(async () => {
|
|
2964
2998
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
@@ -2974,7 +3008,7 @@ const flush = async (entryPoint) => {
|
|
|
2974
3008
|
console.log(`Removed: ${target}`);
|
|
2975
3009
|
}
|
|
2976
3010
|
};
|
|
2977
|
-
const main$
|
|
3011
|
+
const main$d = async () => {
|
|
2978
3012
|
if (!getEntry(import.meta.url)) {
|
|
2979
3013
|
return;
|
|
2980
3014
|
}
|
|
@@ -2991,7 +3025,7 @@ const main$c = async () => {
|
|
|
2991
3025
|
}
|
|
2992
3026
|
process.exit(0);
|
|
2993
3027
|
};
|
|
2994
|
-
main$
|
|
3028
|
+
main$d();
|
|
2995
3029
|
|
|
2996
3030
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
2997
3031
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3013,7 +3047,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3013
3047
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3014
3048
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3015
3049
|
});
|
|
3016
|
-
const main$
|
|
3050
|
+
const main$c = async () => {
|
|
3017
3051
|
if (!getEntry(import.meta.url)) {
|
|
3018
3052
|
return;
|
|
3019
3053
|
}
|
|
@@ -3028,7 +3062,7 @@ const main$b = async () => {
|
|
|
3028
3062
|
await cli.backtestMainService.connect();
|
|
3029
3063
|
listenGracefulShutdown$5();
|
|
3030
3064
|
};
|
|
3031
|
-
main$
|
|
3065
|
+
main$c();
|
|
3032
3066
|
|
|
3033
3067
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3034
3068
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3046,7 +3080,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3046
3080
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3047
3081
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3048
3082
|
});
|
|
3049
|
-
const main$
|
|
3083
|
+
const main$b = async () => {
|
|
3050
3084
|
if (!getEntry(import.meta.url)) {
|
|
3051
3085
|
return;
|
|
3052
3086
|
}
|
|
@@ -3062,7 +3096,7 @@ const main$a = async () => {
|
|
|
3062
3096
|
listenGracefulShutdown$4();
|
|
3063
3097
|
await cli.walkerMainService.connect();
|
|
3064
3098
|
};
|
|
3065
|
-
main$
|
|
3099
|
+
main$b();
|
|
3066
3100
|
|
|
3067
3101
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3068
3102
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3083,7 +3117,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3083
3117
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3084
3118
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3085
3119
|
});
|
|
3086
|
-
const main$
|
|
3120
|
+
const main$a = async () => {
|
|
3087
3121
|
if (!getEntry(import.meta.url)) {
|
|
3088
3122
|
return;
|
|
3089
3123
|
}
|
|
@@ -3094,7 +3128,7 @@ const main$9 = async () => {
|
|
|
3094
3128
|
cli.paperMainService.connect();
|
|
3095
3129
|
listenGracefulShutdown$3();
|
|
3096
3130
|
};
|
|
3097
|
-
main$
|
|
3131
|
+
main$a();
|
|
3098
3132
|
|
|
3099
3133
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
3100
3134
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -3115,7 +3149,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3115
3149
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3116
3150
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3117
3151
|
});
|
|
3118
|
-
const main$
|
|
3152
|
+
const main$9 = async () => {
|
|
3119
3153
|
if (!getEntry(import.meta.url)) {
|
|
3120
3154
|
return;
|
|
3121
3155
|
}
|
|
@@ -3126,7 +3160,7 @@ const main$8 = async () => {
|
|
|
3126
3160
|
await cli.liveMainService.connect();
|
|
3127
3161
|
listenGracefulShutdown$2();
|
|
3128
3162
|
};
|
|
3129
|
-
main$
|
|
3163
|
+
main$9();
|
|
3130
3164
|
|
|
3131
3165
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
3132
3166
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -3136,7 +3170,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
3136
3170
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
3137
3171
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
3138
3172
|
});
|
|
3139
|
-
const main$
|
|
3173
|
+
const main$8 = async () => {
|
|
3140
3174
|
if (!getEntry(import.meta.url)) {
|
|
3141
3175
|
return;
|
|
3142
3176
|
}
|
|
@@ -3146,7 +3180,7 @@ const main$7 = async () => {
|
|
|
3146
3180
|
}
|
|
3147
3181
|
listenGracefulShutdown$1();
|
|
3148
3182
|
};
|
|
3149
|
-
main$
|
|
3183
|
+
main$8();
|
|
3150
3184
|
|
|
3151
3185
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
3152
3186
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -3156,7 +3190,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
3156
3190
|
const listenGracefulShutdown = singleshot(() => {
|
|
3157
3191
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
3158
3192
|
});
|
|
3159
|
-
const main$
|
|
3193
|
+
const main$7 = async () => {
|
|
3160
3194
|
if (!getEntry(import.meta.url)) {
|
|
3161
3195
|
return;
|
|
3162
3196
|
}
|
|
@@ -3166,7 +3200,7 @@ const main$6 = async () => {
|
|
|
3166
3200
|
}
|
|
3167
3201
|
listenGracefulShutdown();
|
|
3168
3202
|
};
|
|
3169
|
-
main$
|
|
3203
|
+
main$7();
|
|
3170
3204
|
|
|
3171
3205
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
3172
3206
|
const keys = Object.keys(schema);
|
|
@@ -3188,7 +3222,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
3188
3222
|
}
|
|
3189
3223
|
return rows;
|
|
3190
3224
|
};
|
|
3191
|
-
const main$
|
|
3225
|
+
const main$6 = async () => {
|
|
3192
3226
|
if (!getEntry(import.meta.url)) {
|
|
3193
3227
|
return;
|
|
3194
3228
|
}
|
|
@@ -3268,9 +3302,9 @@ const main$5 = async () => {
|
|
|
3268
3302
|
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
3269
3303
|
process.exit(0);
|
|
3270
3304
|
};
|
|
3271
|
-
main$
|
|
3305
|
+
main$6();
|
|
3272
3306
|
|
|
3273
|
-
const main$
|
|
3307
|
+
const main$5 = async () => {
|
|
3274
3308
|
if (!getEntry(import.meta.url)) {
|
|
3275
3309
|
return;
|
|
3276
3310
|
}
|
|
@@ -3305,9 +3339,9 @@ const main$4 = async () => {
|
|
|
3305
3339
|
};
|
|
3306
3340
|
process.on("SIGINT", beforeExit);
|
|
3307
3341
|
};
|
|
3308
|
-
main$
|
|
3342
|
+
main$5();
|
|
3309
3343
|
|
|
3310
|
-
const main$
|
|
3344
|
+
const main$4 = async () => {
|
|
3311
3345
|
if (!getEntry(import.meta.url)) {
|
|
3312
3346
|
return;
|
|
3313
3347
|
}
|
|
@@ -3368,6 +3402,102 @@ const main$3 = async () => {
|
|
|
3368
3402
|
console.log(JSON.stringify(candles, null, 2));
|
|
3369
3403
|
process.exit(0);
|
|
3370
3404
|
};
|
|
3405
|
+
main$4();
|
|
3406
|
+
|
|
3407
|
+
const main$3 = async () => {
|
|
3408
|
+
if (!getEntry(import.meta.url)) {
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
const { values } = getArgs();
|
|
3412
|
+
if (!values.pnldebug) {
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
await cli.moduleConnectionService.loadModule("./pnldebug.module");
|
|
3416
|
+
{
|
|
3417
|
+
await cli.exchangeSchemaService.addSchema();
|
|
3418
|
+
await cli.symbolSchemaService.addSchema();
|
|
3419
|
+
}
|
|
3420
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
3421
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
3422
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
3423
|
+
const priceOpenStr = values.priceopen;
|
|
3424
|
+
if (!priceOpenStr) {
|
|
3425
|
+
console.error("Error: --priceopen is required");
|
|
3426
|
+
process.exit(1);
|
|
3427
|
+
}
|
|
3428
|
+
const priceOpen = parseFloat(priceOpenStr);
|
|
3429
|
+
if (isNaN(priceOpen)) {
|
|
3430
|
+
console.error(`Error: --priceopen must be a number, got: ${priceOpenStr}`);
|
|
3431
|
+
process.exit(1);
|
|
3432
|
+
}
|
|
3433
|
+
const direction = (values.direction || "long").toLowerCase();
|
|
3434
|
+
if (direction !== "long" && direction !== "short") {
|
|
3435
|
+
console.error(`Error: --direction must be 'long' or 'short', got: ${direction}`);
|
|
3436
|
+
process.exit(1);
|
|
3437
|
+
}
|
|
3438
|
+
const whenStr = values.when || Date.now().toString();
|
|
3439
|
+
const whenStamp = Date.parse(whenStr);
|
|
3440
|
+
const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
|
|
3441
|
+
const timestamp = alignToInterval(when, "1m").getTime();
|
|
3442
|
+
const minutesStr = values.minutes || "60";
|
|
3443
|
+
const minutesNum = parseInt(minutesStr);
|
|
3444
|
+
const minutes = isNaN(minutesNum) ? 60 : minutesNum;
|
|
3445
|
+
const candles = await Exchange.getRawCandles(symbol, "1m", { exchangeName }, minutes, undefined, timestamp);
|
|
3446
|
+
if (candles.length === 0) {
|
|
3447
|
+
console.error("Error: no candles returned for the given parameters");
|
|
3448
|
+
process.exit(1);
|
|
3449
|
+
}
|
|
3450
|
+
let peak = 0;
|
|
3451
|
+
let drawdown = 0;
|
|
3452
|
+
const rows = candles.map((c, i) => {
|
|
3453
|
+
const pnl = direction === "short"
|
|
3454
|
+
? (priceOpen - c.close) / priceOpen * 100
|
|
3455
|
+
: (c.close - priceOpen) / priceOpen * 100;
|
|
3456
|
+
if (pnl > peak)
|
|
3457
|
+
peak = pnl;
|
|
3458
|
+
if (pnl < drawdown)
|
|
3459
|
+
drawdown = pnl;
|
|
3460
|
+
return { min: i + 1, timestamp: c.timestamp, close: c.close, pnl, peak, drawdown };
|
|
3461
|
+
});
|
|
3462
|
+
const dumpName = values.output || `${symbol}_${direction}_${priceOpen}_${timestamp}`;
|
|
3463
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
3464
|
+
if (values.json) {
|
|
3465
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
3466
|
+
await mkdir(dumpDir, { recursive: true });
|
|
3467
|
+
await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
3468
|
+
console.log(`Saved: ${filePath}`);
|
|
3469
|
+
process.exit(0);
|
|
3470
|
+
}
|
|
3471
|
+
if (values.jsonl) {
|
|
3472
|
+
const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
|
|
3473
|
+
await mkdir(dumpDir, { recursive: true });
|
|
3474
|
+
await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
3475
|
+
console.log(`Saved: ${filePath}`);
|
|
3476
|
+
process.exit(0);
|
|
3477
|
+
}
|
|
3478
|
+
if (values.markdown) {
|
|
3479
|
+
const header = `| min | timestamp | close | pnl% | peak% | drawdown% |\n| --- | --- | --- | --- | --- | --- |`;
|
|
3480
|
+
const mdRows = rows.map((r) => `| ${r.min} | ${new Date(r.timestamp).toISOString()} | ${r.close.toFixed(2)} | ${(r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2)}% | +${r.peak.toFixed(2)}% | ${r.drawdown.toFixed(2)}% |`);
|
|
3481
|
+
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
3482
|
+
await mkdir(dumpDir, { recursive: true });
|
|
3483
|
+
await writeFile(filePath, [header, ...mdRows].join("\n"), "utf-8");
|
|
3484
|
+
console.log(`Saved: ${filePath}`);
|
|
3485
|
+
process.exit(0);
|
|
3486
|
+
}
|
|
3487
|
+
console.log(`Symbol: ${symbol} | Direction: ${direction} | PriceOpen: ${priceOpen} | From: ${new Date(timestamp).toISOString()} | Minutes: ${minutes}\n`);
|
|
3488
|
+
console.log(`${"min".padStart(5)} | ${"timestamp".padEnd(24)} | ${"close".padStart(12)} | ${"pnl%".padStart(8)} | ${"peak%".padStart(8)} | ${"drawdown%".padStart(10)}`);
|
|
3489
|
+
console.log("-".repeat(83));
|
|
3490
|
+
for (const r of rows) {
|
|
3491
|
+
const min = String(r.min).padStart(5);
|
|
3492
|
+
const ts = new Date(r.timestamp).toISOString().padEnd(24);
|
|
3493
|
+
const close = r.close.toFixed(2).padStart(12);
|
|
3494
|
+
const pnlStr = (r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2) + "%";
|
|
3495
|
+
const peakStr = "+" + r.peak.toFixed(2) + "%";
|
|
3496
|
+
const drawdownStr = r.drawdown.toFixed(2) + "%";
|
|
3497
|
+
console.log(`${min} | ${ts} | ${close} | ${pnlStr.padStart(8)} | ${peakStr.padStart(8)} | ${drawdownStr.padStart(10)}`);
|
|
3498
|
+
}
|
|
3499
|
+
process.exit(0);
|
|
3500
|
+
};
|
|
3371
3501
|
main$3();
|
|
3372
3502
|
|
|
3373
3503
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -3486,6 +3616,7 @@ Modes:
|
|
|
3486
3616
|
--pine <entry> Execute a local .pine indicator file
|
|
3487
3617
|
--editor Open the Pine Script visual editor in the browser
|
|
3488
3618
|
--dump Fetch and save raw OHLCV candles
|
|
3619
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
3489
3620
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
3490
3621
|
--init Scaffold a new project in the current directory
|
|
3491
3622
|
--help Print this help message
|
|
@@ -3558,6 +3689,21 @@ Candle dump flags (--dump):
|
|
|
3558
3689
|
|
|
3559
3690
|
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
3560
3691
|
|
|
3692
|
+
PnL debug flags (--pnldebug):
|
|
3693
|
+
|
|
3694
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
3695
|
+
--priceopen <number> Entry price (required)
|
|
3696
|
+
--direction <string> Position direction: long or short (default: long)
|
|
3697
|
+
--when <string> Start timestamp โ ISO 8601 or Unix ms (default: now)
|
|
3698
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
3699
|
+
--exchange <string> Exchange name (default: first registered)
|
|
3700
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
3701
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
3702
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
3703
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
3704
|
+
|
|
3705
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
3706
|
+
|
|
3561
3707
|
Flush flags (--flush):
|
|
3562
3708
|
|
|
3563
3709
|
One or more positional entry points. For each entry point the following
|
|
@@ -3580,6 +3726,7 @@ Module hooks (loaded automatically by each mode):
|
|
|
3580
3726
|
modules/pine.module --pine Exchange schema for PineScript runs
|
|
3581
3727
|
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
3582
3728
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
3729
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
3583
3730
|
|
|
3584
3731
|
--flush has no associated module. It only removes dump subdirectories.
|
|
3585
3732
|
|
|
@@ -3603,6 +3750,8 @@ Examples:
|
|
|
3603
3750
|
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
3604
3751
|
node ${ENTRY_PATH} --editor
|
|
3605
3752
|
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
3753
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
3754
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
3606
3755
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
3607
3756
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
3608
3757
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -3615,7 +3764,7 @@ const main$1 = async () => {
|
|
|
3615
3764
|
if (!values.help) {
|
|
3616
3765
|
return;
|
|
3617
3766
|
}
|
|
3618
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
3767
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n\n`);
|
|
3619
3768
|
process.stdout.write(HELP_TEXT);
|
|
3620
3769
|
process.exit(0);
|
|
3621
3770
|
};
|
|
@@ -3629,7 +3778,7 @@ const main = async () => {
|
|
|
3629
3778
|
if (!values.version) {
|
|
3630
3779
|
return;
|
|
3631
3780
|
}
|
|
3632
|
-
process.stdout.write(`@backtest-kit/cli ${"7.
|
|
3781
|
+
process.stdout.write(`@backtest-kit/cli ${"7.6.0"}\n`);
|
|
3633
3782
|
process.exit(0);
|
|
3634
3783
|
};
|
|
3635
3784
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications โ no setup code required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@babel/plugin-transform-modules-umd": "7.27.1",
|
|
64
64
|
"@babel/standalone": "7.29.1",
|
|
65
|
-
"@backtest-kit/graph": "7.
|
|
66
|
-
"@backtest-kit/ollama": "7.
|
|
67
|
-
"@backtest-kit/pinets": "7.
|
|
68
|
-
"@backtest-kit/signals": "7.
|
|
69
|
-
"@backtest-kit/ui": "7.
|
|
65
|
+
"@backtest-kit/graph": "7.6.0",
|
|
66
|
+
"@backtest-kit/ollama": "7.6.0",
|
|
67
|
+
"@backtest-kit/pinets": "7.6.0",
|
|
68
|
+
"@backtest-kit/signals": "7.6.0",
|
|
69
|
+
"@backtest-kit/ui": "7.6.0",
|
|
70
70
|
"@rollup/plugin-replace": "6.0.3",
|
|
71
71
|
"@rollup/plugin-typescript": "11.1.6",
|
|
72
72
|
"@types/image-size": "0.7.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@types/mustache": "4.2.6",
|
|
75
75
|
"@types/node": "22.9.0",
|
|
76
76
|
"@types/stack-trace": "0.0.33",
|
|
77
|
-
"backtest-kit": "7.
|
|
77
|
+
"backtest-kit": "7.6.0",
|
|
78
78
|
"glob": "11.0.1",
|
|
79
79
|
"markdown-it": "14.1.1",
|
|
80
80
|
"rimraf": "6.0.1",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"peerDependencies": {
|
|
90
90
|
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
|
91
91
|
"@babel/standalone": "^7.29.1",
|
|
92
|
-
"@backtest-kit/graph": "^7.
|
|
93
|
-
"@backtest-kit/ollama": "^7.
|
|
94
|
-
"@backtest-kit/pinets": "^7.
|
|
95
|
-
"@backtest-kit/signals": "^7.
|
|
96
|
-
"@backtest-kit/ui": "^7.
|
|
97
|
-
"backtest-kit": "^7.
|
|
92
|
+
"@backtest-kit/graph": "^7.6.0",
|
|
93
|
+
"@backtest-kit/ollama": "^7.6.0",
|
|
94
|
+
"@backtest-kit/pinets": "^7.6.0",
|
|
95
|
+
"@backtest-kit/signals": "^7.6.0",
|
|
96
|
+
"@backtest-kit/ui": "^7.6.0",
|
|
97
|
+
"backtest-kit": "^7.6.0",
|
|
98
98
|
"markdown-it": "^14.1.1",
|
|
99
99
|
"typescript": "^5.0.0"
|
|
100
100
|
},
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"type": "commonjs",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@backtest-kit/cli": "^7.
|
|
17
|
-
"@backtest-kit/graph": "^7.
|
|
18
|
-
"@backtest-kit/pinets": "^7.
|
|
19
|
-
"@backtest-kit/ui": "^7.
|
|
16
|
+
"@backtest-kit/cli": "^7.6.0",
|
|
17
|
+
"@backtest-kit/graph": "^7.6.0",
|
|
18
|
+
"@backtest-kit/pinets": "^7.6.0",
|
|
19
|
+
"@backtest-kit/ui": "^7.6.0",
|
|
20
20
|
"agent-swarm-kit": "^2.6.0",
|
|
21
|
-
"backtest-kit": "^7.
|
|
21
|
+
"backtest-kit": "^7.6.0",
|
|
22
22
|
"functools-kit": "^2.3.0",
|
|
23
23
|
"garch": "^1.2.3",
|
|
24
24
|
"get-moment-stamp": "^1.1.2",
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
**โน๏ธ Signal Info**
|
|
2
|
-
|
|
3
|
-
**Symbol:** `{{symbol}}` ({{data.position}})
|
|
4
|
-
**Current:** `{{currentPrice}}`
|
|
5
|
-
**Entry:** `{{data.priceOpen}}`
|
|
6
|
-
**Orig Entry:** `{{data.originalPriceOpen}}`
|
|
7
|
-
**DCA Entries:** `{{data.totalEntries}}`
|
|
8
|
-
**Partials Done:** `{{data.totalPartials}}`
|
|
9
|
-
**Take Profit:** `{{data.priceTakeProfit}}`
|
|
10
|
-
**Stop Loss:** `{{data.priceStopLoss}}`
|
|
11
|
-
**Orig TP:** `{{data.originalPriceTakeProfit}}`
|
|
12
|
-
**Orig SL:** `{{data.originalPriceStopLoss}}`
|
|
13
|
-
**PnL:** {{data.pnl.pnlPercentage}}% ({{data.pnl.pnlCost}} / {{data.pnl.pnlEntries}})
|
|
14
|
-
**Peak Profit:** {{data.peakProfit.pnlPercentage}}% ({{data.peakProfit.pnlCost}} / {{data.peakProfit.pnlEntries}})
|
|
15
|
-
**Max Drawdown:** {{data.maxDrawdown.pnlPercentage}}% ({{data.maxDrawdown.pnlCost}} / {{data.maxDrawdown.pnlEntries}})
|
|
16
|
-
**Signal ID:** `{{data.id}}`
|
|
17
|
-
**Time:** `{{timestamp}}`
|
|
18
|
-
|
|
19
|
-
{{#backtest}}
|
|
20
|
-
_๐งช Backtest_
|
|
1
|
+
**โน๏ธ Signal Info**
|
|
2
|
+
|
|
3
|
+
**Symbol:** `{{symbol}}` ({{data.position}})
|
|
4
|
+
**Current:** `{{currentPrice}}`
|
|
5
|
+
**Entry:** `{{data.priceOpen}}`
|
|
6
|
+
**Orig Entry:** `{{data.originalPriceOpen}}`
|
|
7
|
+
**DCA Entries:** `{{data.totalEntries}}`
|
|
8
|
+
**Partials Done:** `{{data.totalPartials}}`
|
|
9
|
+
**Take Profit:** `{{data.priceTakeProfit}}`
|
|
10
|
+
**Stop Loss:** `{{data.priceStopLoss}}`
|
|
11
|
+
**Orig TP:** `{{data.originalPriceTakeProfit}}`
|
|
12
|
+
**Orig SL:** `{{data.originalPriceStopLoss}}`
|
|
13
|
+
**PnL:** {{data.pnl.pnlPercentage}}% ({{data.pnl.pnlCost}} / {{data.pnl.pnlEntries}})
|
|
14
|
+
**Peak Profit:** {{data.peakProfit.pnlPercentage}}% ({{data.peakProfit.pnlCost}} / {{data.peakProfit.pnlEntries}})
|
|
15
|
+
**Max Drawdown:** {{data.maxDrawdown.pnlPercentage}}% ({{data.maxDrawdown.pnlCost}} / {{data.maxDrawdown.pnlEntries}})
|
|
16
|
+
**Signal ID:** `{{data.id}}`
|
|
17
|
+
**Time:** `{{timestamp}}`
|
|
18
|
+
|
|
19
|
+
{{#backtest}}
|
|
20
|
+
_๐งช Backtest_
|
|
21
21
|
{{/backtest}}
|