@backtest-kit/cli 16.4.0 → 16.5.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 +2 -2
- package/build/index.cjs +186 -33
- package/build/index.mjs +188 -35
- package/config/notification.config.mjs +4 -0
- package/docker/package.json +6 -6
- package/package.json +13 -13
- package/template/order-continue.mustache +17 -0
- package/template/{signal-close.mustache → order-fill-close.mustache} +1 -1
- package/template/{signal-open.mustache → order-fill-open.mustache} +2 -1
- package/template/order-reject-close.mustache +17 -0
- package/template/order-reject-open.mustache +16 -0
- package/template/order-stop.mustache +18 -0
- package/template/project/package.mustache +5 -5
- package/types.d.ts +33 -7
package/README.md
CHANGED
|
@@ -452,10 +452,10 @@ The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--sym
|
|
|
452
452
|
|
|
453
453
|
### 🎛️ Simulator (`--simulator`)
|
|
454
454
|
|
|
455
|
-
Sweep exit/entry parameters over a feed of crowd trading ideas using the `Simulator` entity — one candle pass per idea, a full parameter grid evaluated arithmetically, an author whitelist trained with default-ban semantics. Prints a Markdown report with
|
|
455
|
+
Sweep exit/entry parameters over a feed of crowd trading ideas using the `Simulator` entity — one candle pass per idea, a full parameter grid evaluated arithmetically, an author whitelist trained with default-ban semantics. Prints a Markdown report with four ranking winners (time-based Sharpe, Sortino, PnL, recovery factor) and the production whitelist.
|
|
456
456
|
|
|
457
457
|
```bash
|
|
458
|
-
npx @backtest-kit/cli --simulator --symbol BTCUSDT ./assets/
|
|
458
|
+
npx @backtest-kit/cli --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
459
459
|
# → summary to stdout; add --json / --markdown to save into ./dump/
|
|
460
460
|
```
|
|
461
461
|
|
package/build/index.cjs
CHANGED
|
@@ -2341,6 +2341,13 @@ const STOP_BOT_FN = functoolsKit.singleshot(async () => {
|
|
|
2341
2341
|
const { stopBot } = await getTelegram();
|
|
2342
2342
|
stopBot();
|
|
2343
2343
|
});
|
|
2344
|
+
/**
|
|
2345
|
+
* Throttle window for the per-tick order_continue decision channel: at most one
|
|
2346
|
+
* Telegram notification per signalId per 15 minutes via functools-kit ttl. The
|
|
2347
|
+
* terminal order_stop is NOT throttled (fires once per signal) and clears the
|
|
2348
|
+
* throttle slot for its signalId.
|
|
2349
|
+
*/
|
|
2350
|
+
const ORDER_CHECK_NOTIFY_TTL = 15 * 60 * 1000;
|
|
2344
2351
|
class TelegramLogicService {
|
|
2345
2352
|
constructor() {
|
|
2346
2353
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -2489,11 +2496,24 @@ class TelegramLogicService {
|
|
|
2489
2496
|
markdown,
|
|
2490
2497
|
});
|
|
2491
2498
|
};
|
|
2492
|
-
this.
|
|
2493
|
-
this.loggerService.log("telegramLogicService
|
|
2499
|
+
this.notifyOrderFillOpen = functoolsKit.trycatch(async (event) => {
|
|
2500
|
+
this.loggerService.log("telegramLogicService notifyOrderFillOpen", {
|
|
2501
|
+
event,
|
|
2502
|
+
});
|
|
2503
|
+
const markdown = await this.telegramTemplateService.getOrderFillOpenMarkdown(event);
|
|
2504
|
+
if (!markdown) {
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
await this.telegramWebService.publishNotify({
|
|
2508
|
+
symbol: event.symbol,
|
|
2509
|
+
markdown,
|
|
2510
|
+
});
|
|
2511
|
+
});
|
|
2512
|
+
this.notifyOrderFillClose = functoolsKit.trycatch(async (event) => {
|
|
2513
|
+
this.loggerService.log("telegramLogicService notifyOrderFillClose", {
|
|
2494
2514
|
event,
|
|
2495
2515
|
});
|
|
2496
|
-
const markdown = await this.telegramTemplateService.
|
|
2516
|
+
const markdown = await this.telegramTemplateService.getOrderFillCloseMarkdown(event);
|
|
2497
2517
|
if (!markdown) {
|
|
2498
2518
|
return;
|
|
2499
2519
|
}
|
|
@@ -2502,11 +2522,64 @@ class TelegramLogicService {
|
|
|
2502
2522
|
markdown,
|
|
2503
2523
|
});
|
|
2504
2524
|
});
|
|
2505
|
-
this.
|
|
2506
|
-
this.loggerService.log("telegramLogicService
|
|
2525
|
+
this.notifyOrderRejectOpen = functoolsKit.trycatch(async (event) => {
|
|
2526
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
|
|
2507
2527
|
event,
|
|
2508
2528
|
});
|
|
2509
|
-
const markdown = await this.telegramTemplateService.
|
|
2529
|
+
const markdown = await this.telegramTemplateService.getOrderRejectOpenMarkdown(event);
|
|
2530
|
+
if (!markdown) {
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
await this.telegramWebService.publishNotify({
|
|
2534
|
+
symbol: event.symbol,
|
|
2535
|
+
markdown,
|
|
2536
|
+
});
|
|
2537
|
+
});
|
|
2538
|
+
this.notifyOrderRejectClose = functoolsKit.trycatch(async (event) => {
|
|
2539
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectClose", {
|
|
2540
|
+
event,
|
|
2541
|
+
});
|
|
2542
|
+
const markdown = await this.telegramTemplateService.getOrderRejectCloseMarkdown(event);
|
|
2543
|
+
if (!markdown) {
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
await this.telegramWebService.publishNotify({
|
|
2547
|
+
symbol: event.symbol,
|
|
2548
|
+
markdown,
|
|
2549
|
+
});
|
|
2550
|
+
});
|
|
2551
|
+
/**
|
|
2552
|
+
* Post-verdict order-check CONTINUE (order_continue). Fires every live tick the
|
|
2553
|
+
* order survives the check, so it is throttled via functools-kit ttl:
|
|
2554
|
+
* one message per signalId per ORDER_CHECK_NOTIFY_TTL (15 minutes).
|
|
2555
|
+
*/
|
|
2556
|
+
this.notifyOrderContinue = functoolsKit.ttl(functoolsKit.trycatch(async (event) => {
|
|
2557
|
+
this.loggerService.log("telegramLogicService notifyOrderContinue", {
|
|
2558
|
+
event,
|
|
2559
|
+
});
|
|
2560
|
+
const markdown = await this.telegramTemplateService.getOrderContinueMarkdown(event);
|
|
2561
|
+
if (!markdown) {
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
await this.telegramWebService.publishNotify({
|
|
2565
|
+
symbol: event.symbol,
|
|
2566
|
+
markdown,
|
|
2567
|
+
});
|
|
2568
|
+
}), {
|
|
2569
|
+
key: ([event]) => event.signalId,
|
|
2570
|
+
timeout: ORDER_CHECK_NOTIFY_TTL,
|
|
2571
|
+
});
|
|
2572
|
+
/**
|
|
2573
|
+
* Post-verdict order-check STOP (order_stop). Terminal — fires once per signal,
|
|
2574
|
+
* so no throttle; drops the continue throttle slot for this signalId
|
|
2575
|
+
* (the signal leaves monitoring, the ttl entry would only leak otherwise).
|
|
2576
|
+
*/
|
|
2577
|
+
this.notifyOrderStop = functoolsKit.trycatch(async (event) => {
|
|
2578
|
+
this.loggerService.log("telegramLogicService notifyOrderStop", {
|
|
2579
|
+
event,
|
|
2580
|
+
});
|
|
2581
|
+
this.notifyOrderContinue.clear(event.signalId);
|
|
2582
|
+
const markdown = await this.telegramTemplateService.getOrderStopMarkdown(event);
|
|
2510
2583
|
if (!markdown) {
|
|
2511
2584
|
return;
|
|
2512
2585
|
}
|
|
@@ -2611,26 +2684,51 @@ class TelegramLogicService {
|
|
|
2611
2684
|
return;
|
|
2612
2685
|
}
|
|
2613
2686
|
});
|
|
2614
|
-
|
|
2687
|
+
// Broker-CONFIRMED fills only (post-verdict orderFillSubject): a rejected or
|
|
2688
|
+
// transient gate attempt never reaches this channel, so "Order Filled" is
|
|
2689
|
+
// guaranteed truthful. The pre-verdict syncSubject is NOT consumed here.
|
|
2690
|
+
const unOrderFill = BacktestKit.listenOrderFill(async (event) => {
|
|
2615
2691
|
if (event.action === "signal-open") {
|
|
2616
|
-
// type "
|
|
2617
|
-
//
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
await this.notifySignalOpen(event);
|
|
2692
|
+
// Both open outcomes notify: type "active" — the order opened at once
|
|
2693
|
+
// (immediate fill, no schedule phase), type "schedule" — the resting
|
|
2694
|
+
// order was placed on the exchange. Either way the broker confirmed a
|
|
2695
|
+
// real order; the template's Order field carries the type.
|
|
2696
|
+
await this.notifyOrderFillOpen(event);
|
|
2622
2697
|
return;
|
|
2623
2698
|
}
|
|
2624
2699
|
if (event.action === "signal-close") {
|
|
2625
|
-
await this.
|
|
2700
|
+
await this.notifyOrderFillClose(event);
|
|
2626
2701
|
return;
|
|
2627
2702
|
}
|
|
2628
2703
|
});
|
|
2704
|
+
// Terminal broker rejection (post-verdict orderRejectSubject): exactly one
|
|
2705
|
+
// event per dropped order attempt — the engine consumes the rejected signal
|
|
2706
|
+
// id, so this cannot repeat per-tick for the same signal. Dispatched by
|
|
2707
|
+
// action to the dedicated open/close notifications (single responsibility).
|
|
2708
|
+
const unOrderReject = BacktestKit.listenOrderReject(async (event) => {
|
|
2709
|
+
if (event.action === "signal-open") {
|
|
2710
|
+
await this.notifyOrderRejectOpen(event);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
if (event.action === "signal-close") {
|
|
2714
|
+
await this.notifyOrderRejectClose(event);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
// Post-verdict check decisions: CONTINUE fires every surviving tick and is
|
|
2719
|
+
// throttled to one message per signalId per 15 minutes via ttl; terminal
|
|
2720
|
+
// STOP fires once per signal, unthrottled.
|
|
2721
|
+
const unOrderContinue = BacktestKit.listenOrderContinue(async (event) => {
|
|
2722
|
+
await this.notifyOrderContinue(event);
|
|
2723
|
+
});
|
|
2724
|
+
const unOrderStop = BacktestKit.listenOrderStop(async (event) => {
|
|
2725
|
+
await this.notifyOrderStop(event);
|
|
2726
|
+
});
|
|
2629
2727
|
const unSignalNotify = BacktestKit.listenSignalNotify(async (event) => {
|
|
2630
2728
|
await this.notifySignalInfo(event);
|
|
2631
2729
|
});
|
|
2632
2730
|
const unConnect = () => this.connect.clear();
|
|
2633
|
-
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () =>
|
|
2731
|
+
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2634
2732
|
return () => {
|
|
2635
2733
|
STOP_BOT_FN();
|
|
2636
2734
|
unListen();
|
|
@@ -2783,25 +2881,65 @@ class TelegramTemplateService {
|
|
|
2783
2881
|
}
|
|
2784
2882
|
return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
|
|
2785
2883
|
};
|
|
2786
|
-
this.
|
|
2787
|
-
this.loggerService.log("telegramTemplateService
|
|
2884
|
+
this.getOrderFillOpenMarkdown = async (event) => {
|
|
2885
|
+
this.loggerService.log("telegramTemplateService getOrderFillOpenMarkdown", {
|
|
2886
|
+
event,
|
|
2887
|
+
});
|
|
2888
|
+
const adapter = await this.getTelegramAdapter();
|
|
2889
|
+
if (adapter?.getOrderFillOpenMarkdown) {
|
|
2890
|
+
return await adapter.getOrderFillOpenMarkdown(event);
|
|
2891
|
+
}
|
|
2892
|
+
return await RENDER_TEMPLATE_FN("order-fill-open.mustache", event, this);
|
|
2893
|
+
};
|
|
2894
|
+
this.getOrderFillCloseMarkdown = async (event) => {
|
|
2895
|
+
this.loggerService.log("telegramTemplateService getOrderFillCloseMarkdown", {
|
|
2788
2896
|
event,
|
|
2789
2897
|
});
|
|
2790
2898
|
const adapter = await this.getTelegramAdapter();
|
|
2791
|
-
if (adapter?.
|
|
2792
|
-
return await adapter.
|
|
2899
|
+
if (adapter?.getOrderFillCloseMarkdown) {
|
|
2900
|
+
return await adapter.getOrderFillCloseMarkdown(event);
|
|
2793
2901
|
}
|
|
2794
|
-
return await RENDER_TEMPLATE_FN("
|
|
2902
|
+
return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
|
|
2795
2903
|
};
|
|
2796
|
-
this.
|
|
2797
|
-
this.loggerService.log("telegramTemplateService
|
|
2904
|
+
this.getOrderRejectOpenMarkdown = async (event) => {
|
|
2905
|
+
this.loggerService.log("telegramTemplateService getOrderRejectOpenMarkdown", {
|
|
2798
2906
|
event,
|
|
2799
2907
|
});
|
|
2800
2908
|
const adapter = await this.getTelegramAdapter();
|
|
2801
|
-
if (adapter?.
|
|
2802
|
-
return await adapter.
|
|
2909
|
+
if (adapter?.getOrderRejectOpenMarkdown) {
|
|
2910
|
+
return await adapter.getOrderRejectOpenMarkdown(event);
|
|
2803
2911
|
}
|
|
2804
|
-
return await RENDER_TEMPLATE_FN("
|
|
2912
|
+
return await RENDER_TEMPLATE_FN("order-reject-open.mustache", event, this);
|
|
2913
|
+
};
|
|
2914
|
+
this.getOrderRejectCloseMarkdown = async (event) => {
|
|
2915
|
+
this.loggerService.log("telegramTemplateService getOrderRejectCloseMarkdown", {
|
|
2916
|
+
event,
|
|
2917
|
+
});
|
|
2918
|
+
const adapter = await this.getTelegramAdapter();
|
|
2919
|
+
if (adapter?.getOrderRejectCloseMarkdown) {
|
|
2920
|
+
return await adapter.getOrderRejectCloseMarkdown(event);
|
|
2921
|
+
}
|
|
2922
|
+
return await RENDER_TEMPLATE_FN("order-reject-close.mustache", event, this);
|
|
2923
|
+
};
|
|
2924
|
+
this.getOrderContinueMarkdown = async (event) => {
|
|
2925
|
+
this.loggerService.log("telegramTemplateService getOrderContinueMarkdown", {
|
|
2926
|
+
event,
|
|
2927
|
+
});
|
|
2928
|
+
const adapter = await this.getTelegramAdapter();
|
|
2929
|
+
if (adapter?.getOrderContinueMarkdown) {
|
|
2930
|
+
return await adapter.getOrderContinueMarkdown(event);
|
|
2931
|
+
}
|
|
2932
|
+
return await RENDER_TEMPLATE_FN("order-continue.mustache", event, this);
|
|
2933
|
+
};
|
|
2934
|
+
this.getOrderStopMarkdown = async (event) => {
|
|
2935
|
+
this.loggerService.log("telegramTemplateService getOrderStopMarkdown", {
|
|
2936
|
+
event,
|
|
2937
|
+
});
|
|
2938
|
+
const adapter = await this.getTelegramAdapter();
|
|
2939
|
+
if (adapter?.getOrderStopMarkdown) {
|
|
2940
|
+
return await adapter.getOrderStopMarkdown(event);
|
|
2941
|
+
}
|
|
2942
|
+
return await RENDER_TEMPLATE_FN("order-stop.mustache", event, this);
|
|
2805
2943
|
};
|
|
2806
2944
|
this.getCancelScheduledMarkdown = async (event) => {
|
|
2807
2945
|
this.loggerService.log("telegramTemplateService getCancelScheduledMarkdown", {
|
|
@@ -3553,7 +3691,7 @@ const main$i = async () => {
|
|
|
3553
3691
|
if (MODES.some((mode) => values[mode])) {
|
|
3554
3692
|
return;
|
|
3555
3693
|
}
|
|
3556
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3694
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
3557
3695
|
process.stdout.write("\n");
|
|
3558
3696
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3559
3697
|
process.stdout.write("\n");
|
|
@@ -4608,7 +4746,22 @@ const main$4 = async () => {
|
|
|
4608
4746
|
BacktestKit.addSimulatorSchema({
|
|
4609
4747
|
simulatorName: SIMULATOR_NAME,
|
|
4610
4748
|
exchangeName,
|
|
4749
|
+
gridAxes: {
|
|
4750
|
+
hardStopPercent: [1, 1.5, 2, 2.5, 3, 4, 5, 7],
|
|
4751
|
+
trailingTakePercent: [0.5, 1, 1.5, 2, 3, 4],
|
|
4752
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4753
|
+
minIdeasAligned: [1, 2, 3],
|
|
4754
|
+
minAuthorTrack: [2, 3, 5],
|
|
4755
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
4756
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
4757
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
4758
|
+
},
|
|
4611
4759
|
callbacks: {
|
|
4760
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
4761
|
+
if (values.verbose) {
|
|
4762
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
4763
|
+
}
|
|
4764
|
+
},
|
|
4612
4765
|
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
4613
4766
|
if (values.verbose) {
|
|
4614
4767
|
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
@@ -5022,12 +5175,12 @@ Simulator flags (--simulator):
|
|
|
5022
5175
|
Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
|
|
5023
5176
|
dedupe (one idea per author per direction per 8h), default-ban author filter
|
|
5024
5177
|
(ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
|
|
5025
|
-
|
|
5178
|
+
four ranking winners. The report includes the production author whitelist.
|
|
5026
5179
|
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
5027
5180
|
|
|
5028
5181
|
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
5029
|
-
simulator lifecycle callback (onIdeas, onProfiles, onAuthorsTrained,
|
|
5030
|
-
onRanking, onDone) is logged to the console as it fires.
|
|
5182
|
+
simulator lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
|
|
5183
|
+
onGridPoint, onRanking, onDone) is logged to the console as it fires.
|
|
5031
5184
|
|
|
5032
5185
|
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5033
5186
|
(register your exchange there); without it CCXT Binance is used by default.
|
|
@@ -5094,8 +5247,8 @@ Examples:
|
|
|
5094
5247
|
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
5095
5248
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
5096
5249
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5097
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/
|
|
5098
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/
|
|
5250
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5251
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/tv-ideas.normalized.jsonl
|
|
5099
5252
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
5100
5253
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
5101
5254
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -5109,7 +5262,7 @@ const main$1 = async () => {
|
|
|
5109
5262
|
if (!values.help) {
|
|
5110
5263
|
return;
|
|
5111
5264
|
}
|
|
5112
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5265
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n\n`);
|
|
5113
5266
|
process.stdout.write(HELP_TEXT);
|
|
5114
5267
|
process.exit(0);
|
|
5115
5268
|
};
|
|
@@ -5123,7 +5276,7 @@ const main = async () => {
|
|
|
5123
5276
|
if (!values.version) {
|
|
5124
5277
|
return;
|
|
5125
5278
|
}
|
|
5126
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5279
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
5127
5280
|
process.exit(0);
|
|
5128
5281
|
};
|
|
5129
5282
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit,
|
|
4
|
-
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
|
|
3
|
+
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenOrderFill, listenOrderReject, listenOrderContinue, listenOrderStop, listenSignalNotify, Exchange, addSimulatorSchema, Simulator } from 'backtest-kit';
|
|
4
|
+
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, ttl, memoize, isObject } from 'functools-kit';
|
|
5
5
|
import { exec, spawn } from 'child_process';
|
|
6
6
|
import fs, { constants, realpathSync } from 'fs';
|
|
7
7
|
import * as stackTrace from 'stack-trace';
|
|
@@ -2316,6 +2316,13 @@ const STOP_BOT_FN = singleshot(async () => {
|
|
|
2316
2316
|
const { stopBot } = await getTelegram();
|
|
2317
2317
|
stopBot();
|
|
2318
2318
|
});
|
|
2319
|
+
/**
|
|
2320
|
+
* Throttle window for the per-tick order_continue decision channel: at most one
|
|
2321
|
+
* Telegram notification per signalId per 15 minutes via functools-kit ttl. The
|
|
2322
|
+
* terminal order_stop is NOT throttled (fires once per signal) and clears the
|
|
2323
|
+
* throttle slot for its signalId.
|
|
2324
|
+
*/
|
|
2325
|
+
const ORDER_CHECK_NOTIFY_TTL = 15 * 60 * 1000;
|
|
2319
2326
|
class TelegramLogicService {
|
|
2320
2327
|
constructor() {
|
|
2321
2328
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -2464,11 +2471,24 @@ class TelegramLogicService {
|
|
|
2464
2471
|
markdown,
|
|
2465
2472
|
});
|
|
2466
2473
|
};
|
|
2467
|
-
this.
|
|
2468
|
-
this.loggerService.log("telegramLogicService
|
|
2474
|
+
this.notifyOrderFillOpen = trycatch(async (event) => {
|
|
2475
|
+
this.loggerService.log("telegramLogicService notifyOrderFillOpen", {
|
|
2476
|
+
event,
|
|
2477
|
+
});
|
|
2478
|
+
const markdown = await this.telegramTemplateService.getOrderFillOpenMarkdown(event);
|
|
2479
|
+
if (!markdown) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
await this.telegramWebService.publishNotify({
|
|
2483
|
+
symbol: event.symbol,
|
|
2484
|
+
markdown,
|
|
2485
|
+
});
|
|
2486
|
+
});
|
|
2487
|
+
this.notifyOrderFillClose = trycatch(async (event) => {
|
|
2488
|
+
this.loggerService.log("telegramLogicService notifyOrderFillClose", {
|
|
2469
2489
|
event,
|
|
2470
2490
|
});
|
|
2471
|
-
const markdown = await this.telegramTemplateService.
|
|
2491
|
+
const markdown = await this.telegramTemplateService.getOrderFillCloseMarkdown(event);
|
|
2472
2492
|
if (!markdown) {
|
|
2473
2493
|
return;
|
|
2474
2494
|
}
|
|
@@ -2477,11 +2497,64 @@ class TelegramLogicService {
|
|
|
2477
2497
|
markdown,
|
|
2478
2498
|
});
|
|
2479
2499
|
});
|
|
2480
|
-
this.
|
|
2481
|
-
this.loggerService.log("telegramLogicService
|
|
2500
|
+
this.notifyOrderRejectOpen = trycatch(async (event) => {
|
|
2501
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
|
|
2482
2502
|
event,
|
|
2483
2503
|
});
|
|
2484
|
-
const markdown = await this.telegramTemplateService.
|
|
2504
|
+
const markdown = await this.telegramTemplateService.getOrderRejectOpenMarkdown(event);
|
|
2505
|
+
if (!markdown) {
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
await this.telegramWebService.publishNotify({
|
|
2509
|
+
symbol: event.symbol,
|
|
2510
|
+
markdown,
|
|
2511
|
+
});
|
|
2512
|
+
});
|
|
2513
|
+
this.notifyOrderRejectClose = trycatch(async (event) => {
|
|
2514
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectClose", {
|
|
2515
|
+
event,
|
|
2516
|
+
});
|
|
2517
|
+
const markdown = await this.telegramTemplateService.getOrderRejectCloseMarkdown(event);
|
|
2518
|
+
if (!markdown) {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
await this.telegramWebService.publishNotify({
|
|
2522
|
+
symbol: event.symbol,
|
|
2523
|
+
markdown,
|
|
2524
|
+
});
|
|
2525
|
+
});
|
|
2526
|
+
/**
|
|
2527
|
+
* Post-verdict order-check CONTINUE (order_continue). Fires every live tick the
|
|
2528
|
+
* order survives the check, so it is throttled via functools-kit ttl:
|
|
2529
|
+
* one message per signalId per ORDER_CHECK_NOTIFY_TTL (15 minutes).
|
|
2530
|
+
*/
|
|
2531
|
+
this.notifyOrderContinue = ttl(trycatch(async (event) => {
|
|
2532
|
+
this.loggerService.log("telegramLogicService notifyOrderContinue", {
|
|
2533
|
+
event,
|
|
2534
|
+
});
|
|
2535
|
+
const markdown = await this.telegramTemplateService.getOrderContinueMarkdown(event);
|
|
2536
|
+
if (!markdown) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
await this.telegramWebService.publishNotify({
|
|
2540
|
+
symbol: event.symbol,
|
|
2541
|
+
markdown,
|
|
2542
|
+
});
|
|
2543
|
+
}), {
|
|
2544
|
+
key: ([event]) => event.signalId,
|
|
2545
|
+
timeout: ORDER_CHECK_NOTIFY_TTL,
|
|
2546
|
+
});
|
|
2547
|
+
/**
|
|
2548
|
+
* Post-verdict order-check STOP (order_stop). Terminal — fires once per signal,
|
|
2549
|
+
* so no throttle; drops the continue throttle slot for this signalId
|
|
2550
|
+
* (the signal leaves monitoring, the ttl entry would only leak otherwise).
|
|
2551
|
+
*/
|
|
2552
|
+
this.notifyOrderStop = trycatch(async (event) => {
|
|
2553
|
+
this.loggerService.log("telegramLogicService notifyOrderStop", {
|
|
2554
|
+
event,
|
|
2555
|
+
});
|
|
2556
|
+
this.notifyOrderContinue.clear(event.signalId);
|
|
2557
|
+
const markdown = await this.telegramTemplateService.getOrderStopMarkdown(event);
|
|
2485
2558
|
if (!markdown) {
|
|
2486
2559
|
return;
|
|
2487
2560
|
}
|
|
@@ -2586,26 +2659,51 @@ class TelegramLogicService {
|
|
|
2586
2659
|
return;
|
|
2587
2660
|
}
|
|
2588
2661
|
});
|
|
2589
|
-
|
|
2662
|
+
// Broker-CONFIRMED fills only (post-verdict orderFillSubject): a rejected or
|
|
2663
|
+
// transient gate attempt never reaches this channel, so "Order Filled" is
|
|
2664
|
+
// guaranteed truthful. The pre-verdict syncSubject is NOT consumed here.
|
|
2665
|
+
const unOrderFill = listenOrderFill(async (event) => {
|
|
2590
2666
|
if (event.action === "signal-open") {
|
|
2591
|
-
// type "
|
|
2592
|
-
//
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
await this.notifySignalOpen(event);
|
|
2667
|
+
// Both open outcomes notify: type "active" — the order opened at once
|
|
2668
|
+
// (immediate fill, no schedule phase), type "schedule" — the resting
|
|
2669
|
+
// order was placed on the exchange. Either way the broker confirmed a
|
|
2670
|
+
// real order; the template's Order field carries the type.
|
|
2671
|
+
await this.notifyOrderFillOpen(event);
|
|
2597
2672
|
return;
|
|
2598
2673
|
}
|
|
2599
2674
|
if (event.action === "signal-close") {
|
|
2600
|
-
await this.
|
|
2675
|
+
await this.notifyOrderFillClose(event);
|
|
2601
2676
|
return;
|
|
2602
2677
|
}
|
|
2603
2678
|
});
|
|
2679
|
+
// Terminal broker rejection (post-verdict orderRejectSubject): exactly one
|
|
2680
|
+
// event per dropped order attempt — the engine consumes the rejected signal
|
|
2681
|
+
// id, so this cannot repeat per-tick for the same signal. Dispatched by
|
|
2682
|
+
// action to the dedicated open/close notifications (single responsibility).
|
|
2683
|
+
const unOrderReject = listenOrderReject(async (event) => {
|
|
2684
|
+
if (event.action === "signal-open") {
|
|
2685
|
+
await this.notifyOrderRejectOpen(event);
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (event.action === "signal-close") {
|
|
2689
|
+
await this.notifyOrderRejectClose(event);
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
// Post-verdict check decisions: CONTINUE fires every surviving tick and is
|
|
2694
|
+
// throttled to one message per signalId per 15 minutes via ttl; terminal
|
|
2695
|
+
// STOP fires once per signal, unthrottled.
|
|
2696
|
+
const unOrderContinue = listenOrderContinue(async (event) => {
|
|
2697
|
+
await this.notifyOrderContinue(event);
|
|
2698
|
+
});
|
|
2699
|
+
const unOrderStop = listenOrderStop(async (event) => {
|
|
2700
|
+
await this.notifyOrderStop(event);
|
|
2701
|
+
});
|
|
2604
2702
|
const unSignalNotify = listenSignalNotify(async (event) => {
|
|
2605
2703
|
await this.notifySignalInfo(event);
|
|
2606
2704
|
});
|
|
2607
2705
|
const unConnect = () => this.connect.clear();
|
|
2608
|
-
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () =>
|
|
2706
|
+
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2609
2707
|
return () => {
|
|
2610
2708
|
STOP_BOT_FN();
|
|
2611
2709
|
unListen();
|
|
@@ -2758,25 +2856,65 @@ class TelegramTemplateService {
|
|
|
2758
2856
|
}
|
|
2759
2857
|
return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
|
|
2760
2858
|
};
|
|
2761
|
-
this.
|
|
2762
|
-
this.loggerService.log("telegramTemplateService
|
|
2859
|
+
this.getOrderFillOpenMarkdown = async (event) => {
|
|
2860
|
+
this.loggerService.log("telegramTemplateService getOrderFillOpenMarkdown", {
|
|
2861
|
+
event,
|
|
2862
|
+
});
|
|
2863
|
+
const adapter = await this.getTelegramAdapter();
|
|
2864
|
+
if (adapter?.getOrderFillOpenMarkdown) {
|
|
2865
|
+
return await adapter.getOrderFillOpenMarkdown(event);
|
|
2866
|
+
}
|
|
2867
|
+
return await RENDER_TEMPLATE_FN("order-fill-open.mustache", event, this);
|
|
2868
|
+
};
|
|
2869
|
+
this.getOrderFillCloseMarkdown = async (event) => {
|
|
2870
|
+
this.loggerService.log("telegramTemplateService getOrderFillCloseMarkdown", {
|
|
2763
2871
|
event,
|
|
2764
2872
|
});
|
|
2765
2873
|
const adapter = await this.getTelegramAdapter();
|
|
2766
|
-
if (adapter?.
|
|
2767
|
-
return await adapter.
|
|
2874
|
+
if (adapter?.getOrderFillCloseMarkdown) {
|
|
2875
|
+
return await adapter.getOrderFillCloseMarkdown(event);
|
|
2768
2876
|
}
|
|
2769
|
-
return await RENDER_TEMPLATE_FN("
|
|
2877
|
+
return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
|
|
2770
2878
|
};
|
|
2771
|
-
this.
|
|
2772
|
-
this.loggerService.log("telegramTemplateService
|
|
2879
|
+
this.getOrderRejectOpenMarkdown = async (event) => {
|
|
2880
|
+
this.loggerService.log("telegramTemplateService getOrderRejectOpenMarkdown", {
|
|
2773
2881
|
event,
|
|
2774
2882
|
});
|
|
2775
2883
|
const adapter = await this.getTelegramAdapter();
|
|
2776
|
-
if (adapter?.
|
|
2777
|
-
return await adapter.
|
|
2884
|
+
if (adapter?.getOrderRejectOpenMarkdown) {
|
|
2885
|
+
return await adapter.getOrderRejectOpenMarkdown(event);
|
|
2778
2886
|
}
|
|
2779
|
-
return await RENDER_TEMPLATE_FN("
|
|
2887
|
+
return await RENDER_TEMPLATE_FN("order-reject-open.mustache", event, this);
|
|
2888
|
+
};
|
|
2889
|
+
this.getOrderRejectCloseMarkdown = async (event) => {
|
|
2890
|
+
this.loggerService.log("telegramTemplateService getOrderRejectCloseMarkdown", {
|
|
2891
|
+
event,
|
|
2892
|
+
});
|
|
2893
|
+
const adapter = await this.getTelegramAdapter();
|
|
2894
|
+
if (adapter?.getOrderRejectCloseMarkdown) {
|
|
2895
|
+
return await adapter.getOrderRejectCloseMarkdown(event);
|
|
2896
|
+
}
|
|
2897
|
+
return await RENDER_TEMPLATE_FN("order-reject-close.mustache", event, this);
|
|
2898
|
+
};
|
|
2899
|
+
this.getOrderContinueMarkdown = async (event) => {
|
|
2900
|
+
this.loggerService.log("telegramTemplateService getOrderContinueMarkdown", {
|
|
2901
|
+
event,
|
|
2902
|
+
});
|
|
2903
|
+
const adapter = await this.getTelegramAdapter();
|
|
2904
|
+
if (adapter?.getOrderContinueMarkdown) {
|
|
2905
|
+
return await adapter.getOrderContinueMarkdown(event);
|
|
2906
|
+
}
|
|
2907
|
+
return await RENDER_TEMPLATE_FN("order-continue.mustache", event, this);
|
|
2908
|
+
};
|
|
2909
|
+
this.getOrderStopMarkdown = async (event) => {
|
|
2910
|
+
this.loggerService.log("telegramTemplateService getOrderStopMarkdown", {
|
|
2911
|
+
event,
|
|
2912
|
+
});
|
|
2913
|
+
const adapter = await this.getTelegramAdapter();
|
|
2914
|
+
if (adapter?.getOrderStopMarkdown) {
|
|
2915
|
+
return await adapter.getOrderStopMarkdown(event);
|
|
2916
|
+
}
|
|
2917
|
+
return await RENDER_TEMPLATE_FN("order-stop.mustache", event, this);
|
|
2780
2918
|
};
|
|
2781
2919
|
this.getCancelScheduledMarkdown = async (event) => {
|
|
2782
2920
|
this.loggerService.log("telegramTemplateService getCancelScheduledMarkdown", {
|
|
@@ -3521,7 +3659,7 @@ const main$i = async () => {
|
|
|
3521
3659
|
if (MODES.some((mode) => values[mode])) {
|
|
3522
3660
|
return;
|
|
3523
3661
|
}
|
|
3524
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3662
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
3525
3663
|
process.stdout.write("\n");
|
|
3526
3664
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3527
3665
|
process.stdout.write("\n");
|
|
@@ -4576,7 +4714,22 @@ const main$4 = async () => {
|
|
|
4576
4714
|
addSimulatorSchema({
|
|
4577
4715
|
simulatorName: SIMULATOR_NAME,
|
|
4578
4716
|
exchangeName,
|
|
4717
|
+
gridAxes: {
|
|
4718
|
+
hardStopPercent: [1, 1.5, 2, 2.5, 3, 4, 5, 7],
|
|
4719
|
+
trailingTakePercent: [0.5, 1, 1.5, 2, 3, 4],
|
|
4720
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4721
|
+
minIdeasAligned: [1, 2, 3],
|
|
4722
|
+
minAuthorTrack: [2, 3, 5],
|
|
4723
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
4724
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
4725
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
4726
|
+
},
|
|
4579
4727
|
callbacks: {
|
|
4728
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
4729
|
+
if (values.verbose) {
|
|
4730
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
4731
|
+
}
|
|
4732
|
+
},
|
|
4580
4733
|
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
4581
4734
|
if (values.verbose) {
|
|
4582
4735
|
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
@@ -4990,12 +5143,12 @@ Simulator flags (--simulator):
|
|
|
4990
5143
|
Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
|
|
4991
5144
|
dedupe (one idea per author per direction per 8h), default-ban author filter
|
|
4992
5145
|
(ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
|
|
4993
|
-
|
|
5146
|
+
four ranking winners. The report includes the production author whitelist.
|
|
4994
5147
|
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
4995
5148
|
|
|
4996
5149
|
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
4997
|
-
simulator lifecycle callback (onIdeas, onProfiles, onAuthorsTrained,
|
|
4998
|
-
onRanking, onDone) is logged to the console as it fires.
|
|
5150
|
+
simulator lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
|
|
5151
|
+
onGridPoint, onRanking, onDone) is logged to the console as it fires.
|
|
4999
5152
|
|
|
5000
5153
|
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5001
5154
|
(register your exchange there); without it CCXT Binance is used by default.
|
|
@@ -5062,8 +5215,8 @@ Examples:
|
|
|
5062
5215
|
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
5063
5216
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
5064
5217
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5065
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/
|
|
5066
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/
|
|
5218
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5219
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/tv-ideas.normalized.jsonl
|
|
5067
5220
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
5068
5221
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
5069
5222
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -5077,7 +5230,7 @@ const main$1 = async () => {
|
|
|
5077
5230
|
if (!values.help) {
|
|
5078
5231
|
return;
|
|
5079
5232
|
}
|
|
5080
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5233
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n\n`);
|
|
5081
5234
|
process.stdout.write(HELP_TEXT);
|
|
5082
5235
|
process.exit(0);
|
|
5083
5236
|
};
|
|
@@ -5091,7 +5244,7 @@ const main = async () => {
|
|
|
5091
5244
|
if (!values.version) {
|
|
5092
5245
|
return;
|
|
5093
5246
|
}
|
|
5094
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5247
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
5095
5248
|
process.exit(0);
|
|
5096
5249
|
};
|
|
5097
5250
|
main();
|
package/docker/package.json
CHANGED
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"@types/node": "25.6.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@backtest-kit/cli": "16.
|
|
19
|
-
"@backtest-kit/graph": "16.
|
|
20
|
-
"@backtest-kit/pinets": "16.
|
|
21
|
-
"@backtest-kit/signals": "16.
|
|
22
|
-
"@backtest-kit/ui": "16.
|
|
18
|
+
"@backtest-kit/cli": "16.5.0",
|
|
19
|
+
"@backtest-kit/graph": "16.5.0",
|
|
20
|
+
"@backtest-kit/pinets": "16.5.0",
|
|
21
|
+
"@backtest-kit/signals": "16.5.0",
|
|
22
|
+
"@backtest-kit/ui": "16.5.0",
|
|
23
23
|
"@tavily/core": "0.7.2",
|
|
24
24
|
"@tensorflow/tfjs": "4.22.0",
|
|
25
25
|
"@tensorflow/tfjs-backend-wasm": "4.22.0",
|
|
26
26
|
"@tensorflow/tfjs-core": "4.22.0",
|
|
27
27
|
"agent-swarm-kit": "6.0.0",
|
|
28
|
-
"backtest-kit": "16.
|
|
28
|
+
"backtest-kit": "16.5.0",
|
|
29
29
|
"dayjs": "1.11.20",
|
|
30
30
|
"functools-kit": "5.0.0",
|
|
31
31
|
"garch": "2.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.5.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",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@babel/plugin-transform-modules-umd": "7.27.1",
|
|
65
65
|
"@babel/standalone": "7.29.1",
|
|
66
|
-
"@backtest-kit/graph": "16.
|
|
67
|
-
"@backtest-kit/ollama": "16.
|
|
68
|
-
"@backtest-kit/pinets": "16.
|
|
69
|
-
"@backtest-kit/signals": "16.
|
|
70
|
-
"@backtest-kit/ui": "16.
|
|
66
|
+
"@backtest-kit/graph": "16.5.0",
|
|
67
|
+
"@backtest-kit/ollama": "16.5.0",
|
|
68
|
+
"@backtest-kit/pinets": "16.5.0",
|
|
69
|
+
"@backtest-kit/signals": "16.5.0",
|
|
70
|
+
"@backtest-kit/ui": "16.5.0",
|
|
71
71
|
"@rollup/plugin-replace": "6.0.3",
|
|
72
72
|
"@rollup/plugin-typescript": "11.1.6",
|
|
73
73
|
"@types/image-size": "0.7.0",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@types/mustache": "4.2.6",
|
|
76
76
|
"@types/node": "22.9.0",
|
|
77
77
|
"@types/stack-trace": "0.0.33",
|
|
78
|
-
"backtest-kit": "16.
|
|
78
|
+
"backtest-kit": "16.5.0",
|
|
79
79
|
"glob": "11.0.1",
|
|
80
80
|
"markdown-it": "14.1.1",
|
|
81
81
|
"rimraf": "6.0.1",
|
|
@@ -92,14 +92,14 @@
|
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
|
94
94
|
"@babel/standalone": "^7.29.1",
|
|
95
|
-
"@backtest-kit/graph": "^16.
|
|
96
|
-
"@backtest-kit/ollama": "^16.
|
|
97
|
-
"@backtest-kit/pinets": "^16.
|
|
98
|
-
"@backtest-kit/signals": "^16.
|
|
99
|
-
"@backtest-kit/ui": "^16.
|
|
95
|
+
"@backtest-kit/graph": "^16.5.0",
|
|
96
|
+
"@backtest-kit/ollama": "^16.5.0",
|
|
97
|
+
"@backtest-kit/pinets": "^16.5.0",
|
|
98
|
+
"@backtest-kit/signals": "^16.5.0",
|
|
99
|
+
"@backtest-kit/ui": "^16.5.0",
|
|
100
100
|
"pinets": "^0.9.29",
|
|
101
101
|
"ccxt": "^4.5.39",
|
|
102
|
-
"backtest-kit": "^16.
|
|
102
|
+
"backtest-kit": "^16.5.0",
|
|
103
103
|
"markdown-it": "^14.1.1",
|
|
104
104
|
"typescript": "^5.0.0"
|
|
105
105
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
**✅ Order Continue**
|
|
2
|
+
|
|
3
|
+
**Symbol:** `{{symbol}}` ({{position}})
|
|
4
|
+
|
|
5
|
+
**Order:** `{{type}}`
|
|
6
|
+
**Tolerated Failures:** `{{attempt}}`
|
|
7
|
+
**Current:** `{{currentPrice}}`
|
|
8
|
+
**Entry:** `{{priceOpen}}`
|
|
9
|
+
**Take Profit:** `{{priceTakeProfit}}`
|
|
10
|
+
**Stop Loss:** `{{priceStopLoss}}`
|
|
11
|
+
**PnL:** {{pnl.pnlPercentage}}% ({{pnl.pnlCost}} / {{pnl.pnlEntries}})
|
|
12
|
+
**Signal ID:** `{{signalId}}`
|
|
13
|
+
**Time:** `{{timestamp}}`
|
|
14
|
+
|
|
15
|
+
{{#backtest}}
|
|
16
|
+
_🧪 Backtest_
|
|
17
|
+
{{/backtest}}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
**🚫 Order Close Rejected**
|
|
2
|
+
|
|
3
|
+
**Symbol:** `{{symbol}}` ({{position}})
|
|
4
|
+
|
|
5
|
+
**Reason:** `{{message}}`
|
|
6
|
+
**Close Reason:** `{{closeReason}}`
|
|
7
|
+
**Current:** `{{currentPrice}}`
|
|
8
|
+
**Entry:** `{{priceOpen}}`
|
|
9
|
+
**Take Profit:** `{{priceTakeProfit}}`
|
|
10
|
+
**Stop Loss:** `{{priceStopLoss}}`
|
|
11
|
+
**PnL:** {{pnl.pnlPercentage}}% ({{pnl.pnlCost}} / {{pnl.pnlEntries}})
|
|
12
|
+
**Signal ID:** `{{signalId}}`
|
|
13
|
+
**Time:** `{{timestamp}}`
|
|
14
|
+
|
|
15
|
+
{{#backtest}}
|
|
16
|
+
_🧪 Backtest_
|
|
17
|
+
{{/backtest}}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
**🚫 Order Open Rejected**
|
|
2
|
+
|
|
3
|
+
**Symbol:** `{{symbol}}` ({{position}})
|
|
4
|
+
|
|
5
|
+
**Reason:** `{{message}}`
|
|
6
|
+
**Current:** `{{currentPrice}}`
|
|
7
|
+
**Entry:** `{{priceOpen}}`
|
|
8
|
+
**Cost:** `{{cost}}`
|
|
9
|
+
**Take Profit:** `{{priceTakeProfit}}`
|
|
10
|
+
**Stop Loss:** `{{priceStopLoss}}`
|
|
11
|
+
**Signal ID:** `{{signalId}}`
|
|
12
|
+
**Time:** `{{timestamp}}`
|
|
13
|
+
|
|
14
|
+
{{#backtest}}
|
|
15
|
+
_🧪 Backtest_
|
|
16
|
+
{{/backtest}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
**🛑 Order Stop**
|
|
2
|
+
|
|
3
|
+
**Symbol:** `{{symbol}}` ({{position}})
|
|
4
|
+
|
|
5
|
+
**Order:** `{{type}}`
|
|
6
|
+
**Reason:** `{{reason}}`
|
|
7
|
+
**Failed Attempts:** `{{attempt}}`
|
|
8
|
+
**Current:** `{{currentPrice}}`
|
|
9
|
+
**Entry:** `{{priceOpen}}`
|
|
10
|
+
**Take Profit:** `{{priceTakeProfit}}`
|
|
11
|
+
**Stop Loss:** `{{priceStopLoss}}`
|
|
12
|
+
**PnL:** {{pnl.pnlPercentage}}% ({{pnl.pnlCost}} / {{pnl.pnlEntries}})
|
|
13
|
+
**Signal ID:** `{{signalId}}`
|
|
14
|
+
**Time:** `{{timestamp}}`
|
|
15
|
+
|
|
16
|
+
{{#backtest}}
|
|
17
|
+
_🧪 Backtest_
|
|
18
|
+
{{/backtest}}
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"type": "commonjs",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@backtest-kit/cli": "^16.
|
|
17
|
-
"@backtest-kit/graph": "^16.
|
|
18
|
-
"@backtest-kit/pinets": "^16.
|
|
19
|
-
"@backtest-kit/ui": "^16.
|
|
16
|
+
"@backtest-kit/cli": "^16.5.0",
|
|
17
|
+
"@backtest-kit/graph": "^16.5.0",
|
|
18
|
+
"@backtest-kit/pinets": "^16.5.0",
|
|
19
|
+
"@backtest-kit/ui": "^16.5.0",
|
|
20
20
|
"agent-swarm-kit": "^6.0.0",
|
|
21
|
-
"backtest-kit": "^16.
|
|
21
|
+
"backtest-kit": "^16.5.0",
|
|
22
22
|
"functools-kit": "^5.0.0",
|
|
23
23
|
"garch": "^2.0.0",
|
|
24
24
|
"get-moment-stamp": "^3.0.0",
|
package/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as functools_kit from 'functools-kit';
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { CandleInterval, TrailingTakeCommit, TrailingStopCommit, BreakevenCommit, PartialProfitCommit, PartialLossCommit, IStrategyTickResultScheduled, IStrategyTickResultCancelled, IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract, AverageBuyCommit,
|
|
3
|
+
import { CandleInterval, TrailingTakeCommit, TrailingStopCommit, BreakevenCommit, PartialProfitCommit, PartialLossCommit, IStrategyTickResultScheduled, IStrategyTickResultCancelled, IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract, AverageBuyCommit, OrderFillOpenContract, OrderFillCloseContract, OrderRejectOpenContract, OrderRejectCloseContract, OrderContinueContract, OrderStopContract, CancelScheduledCommit, ClosePendingCommit, SignalInfoContract } from 'backtest-kit';
|
|
4
4
|
import * as BacktestKitUi from '@backtest-kit/ui';
|
|
5
5
|
import * as BacktestKitGraph from '@backtest-kit/graph';
|
|
6
6
|
import * as BacktestKitOllama from '@backtest-kit/ollama';
|
|
@@ -309,8 +309,22 @@ declare class TelegramLogicService {
|
|
|
309
309
|
private notifyClosed;
|
|
310
310
|
private notifyRisk;
|
|
311
311
|
private notifyAverageBuy;
|
|
312
|
-
private
|
|
313
|
-
private
|
|
312
|
+
private notifyOrderFillOpen;
|
|
313
|
+
private notifyOrderFillClose;
|
|
314
|
+
private notifyOrderRejectOpen;
|
|
315
|
+
private notifyOrderRejectClose;
|
|
316
|
+
/**
|
|
317
|
+
* Post-verdict order-check CONTINUE (order_continue). Fires every live tick the
|
|
318
|
+
* order survives the check, so it is throttled via functools-kit ttl:
|
|
319
|
+
* one message per signalId per ORDER_CHECK_NOTIFY_TTL (15 minutes).
|
|
320
|
+
*/
|
|
321
|
+
private notifyOrderContinue;
|
|
322
|
+
/**
|
|
323
|
+
* Post-verdict order-check STOP (order_stop). Terminal — fires once per signal,
|
|
324
|
+
* so no throttle; drops the continue throttle slot for this signalId
|
|
325
|
+
* (the signal leaves monitoring, the ttl entry would only leak otherwise).
|
|
326
|
+
*/
|
|
327
|
+
private notifyOrderStop;
|
|
314
328
|
private notifySignalInfo;
|
|
315
329
|
private notifyCancelScheduled;
|
|
316
330
|
private notifyClosePending;
|
|
@@ -330,6 +344,10 @@ interface NotificationConfig {
|
|
|
330
344
|
partial_profit: boolean;
|
|
331
345
|
order_sync: boolean;
|
|
332
346
|
order_check: boolean;
|
|
347
|
+
order_fill: boolean;
|
|
348
|
+
order_reject: boolean;
|
|
349
|
+
order_continue: boolean;
|
|
350
|
+
order_stop: boolean;
|
|
333
351
|
strategy_commit: boolean;
|
|
334
352
|
}
|
|
335
353
|
interface TelegramConfig {
|
|
@@ -344,8 +362,12 @@ interface TelegramConfig {
|
|
|
344
362
|
getClosedMarkdown(event: IStrategyTickResultClosed): Promise<string>;
|
|
345
363
|
getRiskMarkdown(event: RiskContract): Promise<string>;
|
|
346
364
|
getAverageBuyMarkdown(event: AverageBuyCommit): Promise<string>;
|
|
347
|
-
|
|
348
|
-
|
|
365
|
+
getOrderFillOpenMarkdown(event: OrderFillOpenContract): Promise<string>;
|
|
366
|
+
getOrderFillCloseMarkdown(event: OrderFillCloseContract): Promise<string>;
|
|
367
|
+
getOrderRejectOpenMarkdown(event: OrderRejectOpenContract): Promise<string>;
|
|
368
|
+
getOrderRejectCloseMarkdown(event: OrderRejectCloseContract): Promise<string>;
|
|
369
|
+
getOrderContinueMarkdown(event: OrderContinueContract): Promise<string>;
|
|
370
|
+
getOrderStopMarkdown(event: OrderStopContract): Promise<string>;
|
|
349
371
|
getCancelScheduledMarkdown(event: CancelScheduledCommit): Promise<string>;
|
|
350
372
|
getClosePendingMarkdown(event: ClosePendingCommit): Promise<string>;
|
|
351
373
|
getSignalInfoMarkdown(event: SignalInfoContract): Promise<string>;
|
|
@@ -367,8 +389,12 @@ declare class TelegramTemplateService implements TelegramConfig {
|
|
|
367
389
|
getClosedMarkdown: (event: IStrategyTickResultClosed) => Promise<string>;
|
|
368
390
|
getRiskMarkdown: (event: RiskContract) => Promise<string>;
|
|
369
391
|
getAverageBuyMarkdown: (event: AverageBuyCommit) => Promise<string>;
|
|
370
|
-
|
|
371
|
-
|
|
392
|
+
getOrderFillOpenMarkdown: (event: OrderFillOpenContract) => Promise<string>;
|
|
393
|
+
getOrderFillCloseMarkdown: (event: OrderFillCloseContract) => Promise<string>;
|
|
394
|
+
getOrderRejectOpenMarkdown: (event: OrderRejectOpenContract) => Promise<string>;
|
|
395
|
+
getOrderRejectCloseMarkdown: (event: OrderRejectCloseContract) => Promise<string>;
|
|
396
|
+
getOrderContinueMarkdown: (event: OrderContinueContract) => Promise<string>;
|
|
397
|
+
getOrderStopMarkdown: (event: OrderStopContract) => Promise<string>;
|
|
372
398
|
getCancelScheduledMarkdown: (event: CancelScheduledCommit) => Promise<string>;
|
|
373
399
|
getClosePendingMarkdown: (event: ClosePendingCommit) => Promise<string>;
|
|
374
400
|
getSignalInfoMarkdown: (event: SignalInfoContract) => Promise<string>;
|