@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 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 three ranking winners (time-based Sharpe, Sortino, PnL) and the production whitelist.
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/ts-ideas.normalized.jsonl
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.notifySignalOpen = functoolsKit.trycatch(async (event) => {
2493
- this.loggerService.log("telegramLogicService notifySignalOpen", {
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.getSignalOpenMarkdown(event);
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.notifySignalClose = functoolsKit.trycatch(async (event) => {
2506
- this.loggerService.log("telegramLogicService notifySignalClose", {
2525
+ this.notifyOrderRejectOpen = functoolsKit.trycatch(async (event) => {
2526
+ this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
2507
2527
  event,
2508
2528
  });
2509
- const markdown = await this.telegramTemplateService.getSignalCloseMarkdown(event);
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
- const unSync = BacktestKit.listenSync(async (event) => {
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 "schedule" is a resting-order PLACEMENT, not a position open:
2617
- // the user already got the "scheduled" notification from listenSignal
2618
- if (event.type !== "active") {
2619
- return;
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.notifySignalClose(event);
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(), () => unSync(), () => unSignalNotify(), () => unConnect());
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.getSignalOpenMarkdown = async (event) => {
2787
- this.loggerService.log("telegramTemplateService getSignalOpenMarkdown", {
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?.getSignalOpenMarkdown) {
2792
- return await adapter.getSignalOpenMarkdown(event);
2899
+ if (adapter?.getOrderFillCloseMarkdown) {
2900
+ return await adapter.getOrderFillCloseMarkdown(event);
2793
2901
  }
2794
- return await RENDER_TEMPLATE_FN("signal-open.mustache", event, this);
2902
+ return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
2795
2903
  };
2796
- this.getSignalCloseMarkdown = async (event) => {
2797
- this.loggerService.log("telegramTemplateService getSignalCloseMarkdown", {
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?.getSignalCloseMarkdown) {
2802
- return await adapter.getSignalCloseMarkdown(event);
2909
+ if (adapter?.getOrderRejectOpenMarkdown) {
2910
+ return await adapter.getOrderRejectOpenMarkdown(event);
2803
2911
  }
2804
- return await RENDER_TEMPLATE_FN("signal-close.mustache", event, this);
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.4.0"}\n`);
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
- three ranking winners. The report includes the production author whitelist.
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, onGridPoint,
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/ts-ideas.normalized.jsonl
5098
- node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/ts-ideas.normalized.jsonl
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.4.0"}\n\n`);
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.4.0"}\n`);
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, listenSync, 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, 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.notifySignalOpen = trycatch(async (event) => {
2468
- this.loggerService.log("telegramLogicService notifySignalOpen", {
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.getSignalOpenMarkdown(event);
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.notifySignalClose = trycatch(async (event) => {
2481
- this.loggerService.log("telegramLogicService notifySignalClose", {
2500
+ this.notifyOrderRejectOpen = trycatch(async (event) => {
2501
+ this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
2482
2502
  event,
2483
2503
  });
2484
- const markdown = await this.telegramTemplateService.getSignalCloseMarkdown(event);
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
- const unSync = listenSync(async (event) => {
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 "schedule" is a resting-order PLACEMENT, not a position open:
2592
- // the user already got the "scheduled" notification from listenSignal
2593
- if (event.type !== "active") {
2594
- return;
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.notifySignalClose(event);
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(), () => unSync(), () => unSignalNotify(), () => unConnect());
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.getSignalOpenMarkdown = async (event) => {
2762
- this.loggerService.log("telegramTemplateService getSignalOpenMarkdown", {
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?.getSignalOpenMarkdown) {
2767
- return await adapter.getSignalOpenMarkdown(event);
2874
+ if (adapter?.getOrderFillCloseMarkdown) {
2875
+ return await adapter.getOrderFillCloseMarkdown(event);
2768
2876
  }
2769
- return await RENDER_TEMPLATE_FN("signal-open.mustache", event, this);
2877
+ return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
2770
2878
  };
2771
- this.getSignalCloseMarkdown = async (event) => {
2772
- this.loggerService.log("telegramTemplateService getSignalCloseMarkdown", {
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?.getSignalCloseMarkdown) {
2777
- return await adapter.getSignalCloseMarkdown(event);
2884
+ if (adapter?.getOrderRejectOpenMarkdown) {
2885
+ return await adapter.getOrderRejectOpenMarkdown(event);
2778
2886
  }
2779
- return await RENDER_TEMPLATE_FN("signal-close.mustache", event, this);
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.4.0"}\n`);
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
- three ranking winners. The report includes the production author whitelist.
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, onGridPoint,
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/ts-ideas.normalized.jsonl
5066
- node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/ts-ideas.normalized.jsonl
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.4.0"}\n\n`);
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.4.0"}\n`);
5247
+ process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
5095
5248
  process.exit(0);
5096
5249
  };
5097
5250
  main();
@@ -11,5 +11,9 @@ export default {
11
11
  partial_profit: false,
12
12
  order_sync: false,
13
13
  order_check: false,
14
+ order_fill: true,
15
+ order_reject: true,
16
+ order_continue: true,
17
+ order_stop: true,
14
18
  strategy_commit: true,
15
19
  };
@@ -15,17 +15,17 @@
15
15
  "@types/node": "25.6.0"
16
16
  },
17
17
  "dependencies": {
18
- "@backtest-kit/cli": "16.4.0",
19
- "@backtest-kit/graph": "16.4.0",
20
- "@backtest-kit/pinets": "16.4.0",
21
- "@backtest-kit/signals": "16.4.0",
22
- "@backtest-kit/ui": "16.4.0",
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.4.0",
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.4.0",
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.4.0",
67
- "@backtest-kit/ollama": "16.4.0",
68
- "@backtest-kit/pinets": "16.4.0",
69
- "@backtest-kit/signals": "16.4.0",
70
- "@backtest-kit/ui": "16.4.0",
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.4.0",
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.4.0",
96
- "@backtest-kit/ollama": "^16.4.0",
97
- "@backtest-kit/pinets": "^16.4.0",
98
- "@backtest-kit/signals": "^16.4.0",
99
- "@backtest-kit/ui": "^16.4.0",
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.4.0",
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}}
@@ -1,4 +1,4 @@
1
- **📭 Order Filled (Close)**
1
+ **📭 Order Close Fill**
2
2
 
3
3
  **Symbol:** `{{symbol}}` ({{position}})
4
4
 
@@ -1,7 +1,8 @@
1
- **📬 Order Filled (Open)**
1
+ **📬 Order Open Fill**
2
2
 
3
3
  **Symbol:** `{{symbol}}` ({{position}})
4
4
 
5
+ **Order:** `{{type}}`
5
6
  **Current:** `{{currentPrice}}`
6
7
  **Entry:** `{{priceOpen}}`
7
8
  **Orig Entry:** `{{originalPriceOpen}}`
@@ -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.4.0",
17
- "@backtest-kit/graph": "^16.4.0",
18
- "@backtest-kit/pinets": "^16.4.0",
19
- "@backtest-kit/ui": "^16.4.0",
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.4.0",
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, OrderOpenContract, OrderCloseContract, CancelScheduledCommit, ClosePendingCommit, SignalInfoContract } from 'backtest-kit';
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 notifySignalOpen;
313
- private notifySignalClose;
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
- getSignalOpenMarkdown(event: OrderOpenContract): Promise<string>;
348
- getSignalCloseMarkdown(event: OrderCloseContract): Promise<string>;
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
- getSignalOpenMarkdown: (event: OrderOpenContract) => Promise<string>;
371
- getSignalCloseMarkdown: (event: OrderCloseContract) => Promise<string>;
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>;