@backtest-kit/cli 16.2.0 → 16.4.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
@@ -104,6 +104,7 @@ Every invocation is **one mode** (a primary flag) + a positional strategy/entry
104
104
  | **Candle Dump** | `--dump` | Fetch & save raw OHLCV candles to a file |
105
105
  | **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price & direction |
106
106
  | **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live adapter |
107
+ | **Simulator** | `--simulator` | Sweep exit/entry parameters over a crowd-ideas feed, train an author whitelist |
107
108
  | **Flush** | `--flush` | Delete report/log/markdown/agent folders from a strategy dump dir |
108
109
  | **Init** | `--init` | Scaffold a new project |
109
110
  | **Docker** | `--docker` | Scaffold a self-contained Docker workspace |
@@ -266,12 +267,12 @@ The same shape works for `--live --entry` / `--paper --entry` (call `Live.backgr
266
267
 
267
268
  ## 🛠️ Tooling modes
268
269
 
269
- Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
270
+ Six utilities that don't run a strategy. They share one convention, explained once here and referenced below.
270
271
 
271
272
  > **The `<mode>.module` convention.** By default the CLI auto-registers CCXT Binance. To use a different exchange (custom API keys, rate limits, a non-spot market), drop a `modules/<mode>.module.ts` that calls `addExchangeSchema` from `backtest-kit`. The CLI loads it automatically before running, trying `.ts`/`.mjs`/`.cjs`; it's searched **next to the target file first, then in the project root**. `.env` is loaded root-first then the target-file dir (override), so API keys stay out of code.
272
273
 
273
274
  <details>
274
- <summary>The shared <code>&lt;mode&gt;.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
275
+ <summary>The shared <code>&lt;mode&gt;.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug / simulator)</summary>
275
276
 
276
277
  ```typescript
277
278
  // modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
@@ -449,6 +450,34 @@ The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--sym
449
450
 
450
451
  </details>
451
452
 
453
+ ### 🎛️ Simulator (`--simulator`)
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.
456
+
457
+ ```bash
458
+ npx @backtest-kit/cli --simulator --symbol BTCUSDT ./assets/ts-ideas.normalized.jsonl
459
+ # → summary to stdout; add --json / --markdown to save into ./dump/
460
+ ```
461
+
462
+ <details>
463
+ <summary>Simulator flags, input format & behavior</summary>
464
+
465
+ | Flag | Type | Description |
466
+ |------|------|-------------|
467
+ | `--simulator` | boolean | Enable simulator mode |
468
+ | `--symbol` | string | Trading pair to simulate (default `"BTCUSDT"`) |
469
+ | `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
470
+ | `--output` | string | Output base name (default `simulator_{SYMBOL}_{TIMESTAMP}`) |
471
+ | `--json` | boolean | Save the full `ISimulatorResult` to `./dump/<output>.json` |
472
+ | `--markdown` | boolean | Save the summary report to `./dump/<output>.md` |
473
+ | `--verbose` | boolean | Log every simulator lifecycle callback to the console |
474
+
475
+ **Positional (required):** path to an ideas `.jsonl` file — one idea per line, exact shape `{ "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`. The file is validated **before any work starts** — the first line that does not match the structure aborts the run with an error naming the line and the field. Ideas of other symbols are filtered out by the engine, so one shared feed serves any `--symbol`.
476
+
477
+ Under the hood: 5-day candle horizon per idea (lazy chunked fetch through the exchange, persist cache first), flood dedupe (one idea per author per direction per 8h), author ban thresholds swept as grid axes (unproven author = banned by default), production slot semantics, time-based Sharpe/Sortino over daily equity buckets. With `--verbose` every lifecycle callback (`onIdeas`, `onProfiles`, `onAuthorsTrained`, `onGridPoint`, `onRanking`, `onDone`) is logged to the console as it fires, so long runs show progress. Exchange via `simulator.module` (see convention above).
478
+
479
+ </details>
480
+
452
481
  ### 🗑️ Flush (`--flush`)
453
482
 
454
483
  Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
package/build/index.cjs CHANGED
@@ -233,14 +233,14 @@ const getReadySubject = functoolsKit.singleshot(() => {
233
233
  return new functoolsKit.Subject();
234
234
  });
235
235
 
236
- const main$i = () => {
236
+ const main$j = () => {
237
237
  const entrySubject = getEntrySubject();
238
238
  entrySubject.subscribe(async (path) => {
239
239
  console.log("Running", path);
240
240
  await getReadySubject().next();
241
241
  });
242
242
  };
243
- main$i();
243
+ main$j();
244
244
 
245
245
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
246
246
  function dumpStackTrace(error) {
@@ -891,6 +891,10 @@ const getArgs = functoolsKit.singleshot(() => {
891
891
  type: "boolean",
892
892
  default: false,
893
893
  },
894
+ simulator: {
895
+ type: "boolean",
896
+ default: false,
897
+ },
894
898
  commit: {
895
899
  type: "string",
896
900
  default: "",
@@ -3534,14 +3538,14 @@ const cli = {
3534
3538
  };
3535
3539
  init();
3536
3540
 
3537
- const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3541
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
3538
3542
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3539
3543
  const HELP_TEXT$1 = `
3540
3544
  Example:
3541
3545
 
3542
3546
  node ${ENTRY_PATH$1} --help
3543
3547
  `.trimStart();
3544
- const main$h = async () => {
3548
+ const main$i = async () => {
3545
3549
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3546
3550
  return;
3547
3551
  }
@@ -3549,14 +3553,14 @@ const main$h = async () => {
3549
3553
  if (MODES.some((mode) => values[mode])) {
3550
3554
  return;
3551
3555
  }
3552
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n`);
3556
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n`);
3553
3557
  process.stdout.write("\n");
3554
3558
  process.stdout.write(`Run with --help to see available commands.\n`);
3555
3559
  process.stdout.write("\n");
3556
3560
  process.stdout.write(HELP_TEXT$1);
3557
3561
  process.exit(0);
3558
3562
  };
3559
- main$h();
3563
+ main$i();
3560
3564
 
3561
3565
  const notifyShutdown = functoolsKit.singleshot(async () => {
3562
3566
  console.log("Graceful shutdown initiated.");
@@ -3572,7 +3576,7 @@ const flush = async (entryPoint) => {
3572
3576
  console.log(`Removed: ${target}`);
3573
3577
  }
3574
3578
  };
3575
- const main$g = async () => {
3579
+ const main$h = async () => {
3576
3580
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3577
3581
  return;
3578
3582
  }
@@ -3589,7 +3593,7 @@ const main$g = async () => {
3589
3593
  }
3590
3594
  process.exit(0);
3591
3595
  };
3592
- main$g();
3596
+ main$h();
3593
3597
 
3594
3598
  const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3595
3599
  process.off("SIGINT", BEFORE_EXIT_FN$6);
@@ -3612,7 +3616,7 @@ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3612
3616
  const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
3613
3617
  process.on("SIGINT", BEFORE_EXIT_FN$6);
3614
3618
  });
3615
- const main$f = async () => {
3619
+ const main$g = async () => {
3616
3620
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3617
3621
  return;
3618
3622
  }
@@ -3630,7 +3634,7 @@ const main$f = async () => {
3630
3634
  await cli.backtestMainService.connect();
3631
3635
  listenGracefulShutdown$6();
3632
3636
  };
3633
- main$f();
3637
+ main$g();
3634
3638
 
3635
3639
  const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3636
3640
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -3649,7 +3653,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3649
3653
  const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3650
3654
  process.on("SIGINT", BEFORE_EXIT_FN$5);
3651
3655
  });
3652
- const main$e = async () => {
3656
+ const main$f = async () => {
3653
3657
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3654
3658
  return;
3655
3659
  }
@@ -3668,7 +3672,7 @@ const main$e = async () => {
3668
3672
  listenGracefulShutdown$5();
3669
3673
  await cli.walkerMainService.connect();
3670
3674
  };
3671
- main$e();
3675
+ main$f();
3672
3676
 
3673
3677
  const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3674
3678
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -3690,7 +3694,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3690
3694
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3691
3695
  process.on("SIGINT", BEFORE_EXIT_FN$4);
3692
3696
  });
3693
- const main$d = async () => {
3697
+ const main$e = async () => {
3694
3698
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3695
3699
  return;
3696
3700
  }
@@ -3704,7 +3708,7 @@ const main$d = async () => {
3704
3708
  await cli.paperMainService.connect();
3705
3709
  listenGracefulShutdown$4();
3706
3710
  };
3707
- main$d();
3711
+ main$e();
3708
3712
 
3709
3713
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3710
3714
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -3726,7 +3730,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3726
3730
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3727
3731
  process.on("SIGINT", BEFORE_EXIT_FN$3);
3728
3732
  });
3729
- const main$c = async () => {
3733
+ const main$d = async () => {
3730
3734
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3731
3735
  return;
3732
3736
  }
@@ -3740,7 +3744,7 @@ const main$c = async () => {
3740
3744
  await cli.liveMainService.connect();
3741
3745
  listenGracefulShutdown$3();
3742
3746
  };
3743
- main$c();
3747
+ main$d();
3744
3748
 
3745
3749
  const stopBacktestList$1 = async () => {
3746
3750
  for (const item of await BacktestKit.Backtest.list()) {
@@ -3804,7 +3808,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3804
3808
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
3805
3809
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3806
3810
  });
3807
- const main$b = async () => {
3811
+ const main$c = async () => {
3808
3812
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3809
3813
  return;
3810
3814
  }
@@ -3860,7 +3864,7 @@ const main$b = async () => {
3860
3864
  listenGracefulShutdown$2();
3861
3865
  await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3862
3866
  };
3863
- main$b();
3867
+ main$c();
3864
3868
 
3865
3869
  const MODE_MODULE = {
3866
3870
  backtest: "backtest.module",
@@ -3963,7 +3967,7 @@ const createGracefulShutdown = (mode) => {
3963
3967
  process.on("SIGINT", handler);
3964
3968
  });
3965
3969
  };
3966
- const main$a = async () => {
3970
+ const main$b = async () => {
3967
3971
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3968
3972
  return;
3969
3973
  }
@@ -4039,7 +4043,7 @@ const main$a = async () => {
4039
4043
  }
4040
4044
  await getEntrySubject().next(absolutePath);
4041
4045
  };
4042
- main$a();
4046
+ main$b();
4043
4047
 
4044
4048
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
4045
4049
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -4050,7 +4054,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
4050
4054
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
4051
4055
  process.on("SIGINT", BEFORE_EXIT_FN$1);
4052
4056
  });
4053
- const main$9 = async () => {
4057
+ const main$a = async () => {
4054
4058
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4055
4059
  return;
4056
4060
  }
@@ -4060,7 +4064,7 @@ const main$9 = async () => {
4060
4064
  }
4061
4065
  listenGracefulShutdown$1();
4062
4066
  };
4063
- main$9();
4067
+ main$a();
4064
4068
 
4065
4069
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
4066
4070
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -4071,7 +4075,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
4071
4075
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
4072
4076
  process.on("SIGINT", BEFORE_EXIT_FN);
4073
4077
  });
4074
- const main$8 = async () => {
4078
+ const main$9 = async () => {
4075
4079
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4076
4080
  return;
4077
4081
  }
@@ -4081,7 +4085,7 @@ const main$8 = async () => {
4081
4085
  }
4082
4086
  listenGracefulShutdown();
4083
4087
  };
4084
- main$8();
4088
+ main$9();
4085
4089
 
4086
4090
  const EXTRACT_ROWS_FN = (plots, schema) => {
4087
4091
  const keys = Object.keys(schema);
@@ -4103,7 +4107,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
4103
4107
  }
4104
4108
  return rows;
4105
4109
  };
4106
- const main$7 = async () => {
4110
+ const main$8 = async () => {
4107
4111
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4108
4112
  return;
4109
4113
  }
@@ -4203,9 +4207,9 @@ const main$7 = async () => {
4203
4207
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
4204
4208
  process.exit(0);
4205
4209
  };
4206
- main$7();
4210
+ main$8();
4207
4211
 
4208
- const main$6 = async () => {
4212
+ const main$7 = async () => {
4209
4213
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4210
4214
  return;
4211
4215
  }
@@ -4268,9 +4272,9 @@ const main$6 = async () => {
4268
4272
  };
4269
4273
  process.on("SIGINT", beforeExit);
4270
4274
  };
4271
- main$6();
4275
+ main$7();
4272
4276
 
4273
- const main$5 = async () => {
4277
+ const main$6 = async () => {
4274
4278
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4275
4279
  return;
4276
4280
  }
@@ -4351,9 +4355,9 @@ const main$5 = async () => {
4351
4355
  console.log(JSON.stringify(candles, null, 2));
4352
4356
  process.exit(0);
4353
4357
  };
4354
- main$5();
4358
+ main$6();
4355
4359
 
4356
- const main$4 = async () => {
4360
+ const main$5 = async () => {
4357
4361
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4358
4362
  return;
4359
4363
  }
@@ -4467,6 +4471,221 @@ const main$4 = async () => {
4467
4471
  }
4468
4472
  process.exit(0);
4469
4473
  };
4474
+ main$5();
4475
+
4476
+ const SIMULATOR_NAME = "cli_simulator";
4477
+ const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
4478
+ const validateIdea = (idea, line) => {
4479
+ if (typeof idea !== "object" || idea === null) {
4480
+ return `line ${line}: not an object`;
4481
+ }
4482
+ if (typeof idea.id !== "number") {
4483
+ return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
4484
+ }
4485
+ if (typeof idea.ts !== "number") {
4486
+ return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
4487
+ }
4488
+ if (typeof idea.symbol !== "string" || !idea.symbol) {
4489
+ return `line ${line}: "symbol" must be a non-empty string`;
4490
+ }
4491
+ if (!IDEA_DIRECTIONS.includes(idea.direction)) {
4492
+ return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
4493
+ }
4494
+ if (typeof idea.author !== "string" || !idea.author) {
4495
+ return `line ${line}: "author" must be a non-empty string`;
4496
+ }
4497
+ return null;
4498
+ };
4499
+ const toMarkdown = (result) => {
4500
+ const lines = [];
4501
+ lines.push(`# Simulator Report — ${result.symbol}`);
4502
+ lines.push("");
4503
+ lines.push(`| Metric | Value |`);
4504
+ lines.push(`| --- | --- |`);
4505
+ lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
4506
+ lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
4507
+ lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
4508
+ lines.push(`| Grid points | ${result.reports.length} |`);
4509
+ lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
4510
+ lines.push("");
4511
+ lines.push(`## Ranking winners`);
4512
+ lines.push("");
4513
+ lines.push(`| Criterion | Stop% | Trailing% | Hold | Aligned | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
4514
+ lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
4515
+ for (const best of result.best) {
4516
+ if (!best.report) {
4517
+ lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — | — | — |`);
4518
+ continue;
4519
+ }
4520
+ const { point } = best.report;
4521
+ lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.trailingTakePercent} | ${point.holdMinutes / 60}h | ${point.minIdeasAligned} | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
4522
+ `${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
4523
+ }
4524
+ lines.push("");
4525
+ lines.push(`## Allowed authors (production whitelist)`);
4526
+ lines.push("");
4527
+ lines.push(`| Author | Ideas | Hits | HitRate |`);
4528
+ lines.push(`| --- | --- | --- | --- |`);
4529
+ for (const stat of result.authorStats.filter(({ banned }) => !banned)) {
4530
+ lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
4531
+ }
4532
+ return lines.join("\n");
4533
+ };
4534
+ const main$4 = async () => {
4535
+ if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
4536
+ return;
4537
+ }
4538
+ const { values, positionals } = getArgs();
4539
+ if (!values.simulator) {
4540
+ return;
4541
+ }
4542
+ const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
4543
+ if (!ideasPath) {
4544
+ console.error("Error: positional path to an ideas .jsonl file is required");
4545
+ process.exit(1);
4546
+ }
4547
+ let ideas = [];
4548
+ {
4549
+ let content;
4550
+ try {
4551
+ content = await fs$1.readFile(path.resolve(ideasPath), "utf-8");
4552
+ }
4553
+ catch (error) {
4554
+ console.error(`Error: cannot read ideas file: ${ideasPath}`);
4555
+ process.exit(1);
4556
+ }
4557
+ const rows = content.split("\n").filter(Boolean);
4558
+ if (!rows.length) {
4559
+ console.error(`Error: ideas file is empty: ${ideasPath}`);
4560
+ process.exit(1);
4561
+ }
4562
+ for (let i = 0; i < rows.length; i++) {
4563
+ let idea;
4564
+ try {
4565
+ idea = JSON.parse(rows[i]);
4566
+ }
4567
+ catch {
4568
+ console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
4569
+ process.exit(1);
4570
+ }
4571
+ const problem = validateIdea(idea, i + 1);
4572
+ if (problem) {
4573
+ console.error(`Error: ideas file does not match the idea structure — ${problem}`);
4574
+ console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
4575
+ process.exit(1);
4576
+ }
4577
+ ideas.push(idea);
4578
+ }
4579
+ }
4580
+ {
4581
+ const cwd = process.cwd();
4582
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
4583
+ }
4584
+ await cli.configConnectionService.loadConfig("setup.config");
4585
+ {
4586
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
4587
+ try {
4588
+ if (typeof loader === "function") {
4589
+ await loader();
4590
+ }
4591
+ if (typeof loader?.loader === "function") {
4592
+ await loader.loader();
4593
+ }
4594
+ }
4595
+ catch (error) {
4596
+ console.error("Module loader failed", error);
4597
+ process.exit(-1);
4598
+ }
4599
+ }
4600
+ await cli.moduleConnectionService.loadModule("simulator.module");
4601
+ {
4602
+ await cli.exchangeSchemaService.addSchema();
4603
+ await cli.symbolSchemaService.addSchema();
4604
+ }
4605
+ const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
4606
+ const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
4607
+ const symbol = values.symbol || "BTCUSDT";
4608
+ BacktestKit.addSimulatorSchema({
4609
+ simulatorName: SIMULATOR_NAME,
4610
+ exchangeName,
4611
+ callbacks: {
4612
+ onIdeas: (symbol, ideasTotal, ideasDirectional) => {
4613
+ if (values.verbose) {
4614
+ console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
4615
+ }
4616
+ },
4617
+ onProfiles: (symbol, profiles, truncatedCount) => {
4618
+ if (values.verbose) {
4619
+ console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
4620
+ }
4621
+ },
4622
+ onAuthorsTrained: (symbol, stats, bannedIdeas) => {
4623
+ if (values.verbose) {
4624
+ console.log("onAuthorsTrained", {
4625
+ symbol,
4626
+ authors: stats.length,
4627
+ banned: stats.filter(({ banned }) => banned).length,
4628
+ bannedIdeas,
4629
+ });
4630
+ }
4631
+ },
4632
+ onGridPoint: (symbol, report) => {
4633
+ if (values.verbose) {
4634
+ console.log("onGridPoint", {
4635
+ symbol,
4636
+ point: report.point,
4637
+ trades: report.trades,
4638
+ pnl: +report.totalPnlPercent.toFixed(2),
4639
+ sharpe: +report.sharpe.toFixed(2),
4640
+ });
4641
+ }
4642
+ },
4643
+ onRanking: (symbol, criterion, _sorted, best) => {
4644
+ if (values.verbose) {
4645
+ console.log("onRanking", {
4646
+ symbol,
4647
+ criterion,
4648
+ point: best.report?.point ?? null,
4649
+ pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
4650
+ });
4651
+ }
4652
+ },
4653
+ onDone: (symbol, result) => {
4654
+ if (values.verbose) {
4655
+ console.log("onDone", {
4656
+ symbol,
4657
+ reports: result.reports.length,
4658
+ allowedAuthors: result.allowedAuthors.length,
4659
+ bannedAuthors: result.bannedAuthors.length,
4660
+ });
4661
+ }
4662
+ },
4663
+ },
4664
+ });
4665
+ const result = await BacktestKit.Simulator.run({
4666
+ symbol,
4667
+ simulatorName: SIMULATOR_NAME,
4668
+ ideas,
4669
+ });
4670
+ const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
4671
+ const dumpDir = path.join(process.cwd(), "dump");
4672
+ if (values.json) {
4673
+ const filePath = path.resolve(dumpDir, `${dumpName}.json`);
4674
+ await fs$1.mkdir(dumpDir, { recursive: true });
4675
+ await fs$1.writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
4676
+ console.log(`Saved: ${filePath}`);
4677
+ process.exit(0);
4678
+ }
4679
+ if (values.markdown) {
4680
+ const filePath = path.resolve(dumpDir, `${dumpName}.md`);
4681
+ await fs$1.mkdir(dumpDir, { recursive: true });
4682
+ await fs$1.writeFile(filePath, toMarkdown(result), "utf-8");
4683
+ console.log(`Saved: ${filePath}`);
4684
+ process.exit(0);
4685
+ }
4686
+ console.log(toMarkdown(result));
4687
+ process.exit(0);
4688
+ };
4470
4689
  main$4();
4471
4690
 
4472
4691
  const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
@@ -4665,6 +4884,7 @@ Modes:
4665
4884
  --dump Fetch and save raw OHLCV candles
4666
4885
  --pnldebug Simulate PnL per minute for a given entry price and direction
4667
4886
  --brokerdebug Fire a single broker commit against the live broker adapter
4887
+ --simulator <ideas.jsonl> Sweep exit/entry parameters over crowd ideas, train author whitelist
4668
4888
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4669
4889
  --init Scaffold a new project in the current directory
4670
4890
  --docker Scaffold a Docker workspace for running strategies in a container
@@ -4785,6 +5005,33 @@ Broker debug flags (--brokerdebug):
4785
5005
  Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4786
5006
  the selected broker commit with synthetic payload values derived from current price.
4787
5007
 
5008
+ Simulator flags (--simulator):
5009
+
5010
+ --symbol <string> Trading pair to simulate (default: BTCUSDT)
5011
+ --exchange <string> Exchange name (default: first registered)
5012
+ --output <string> Output file base name (default: simulator_{SYMBOL}_{TIMESTAMP})
5013
+ --json Save full ISimulatorResult to ./dump/<output>.json
5014
+ --markdown Save summary report to ./dump/<output>.md
5015
+ --verbose Log every simulator lifecycle callback to the console
5016
+
5017
+ Positional: path to an ideas .jsonl file — one idea per line with the exact shape
5018
+ { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL",
5019
+ "author": string }. The file is validated BEFORE any work; a line that does not
5020
+ match the structure aborts the run with an error.
5021
+
5022
+ Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
5023
+ dedupe (one idea per author per direction per 8h), default-ban author filter
5024
+ (ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
5025
+ three ranking winners. The report includes the production author whitelist.
5026
+ Ideas of other symbols are filtered out — one shared feed serves any --symbol.
5027
+
5028
+ 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.
5031
+
5032
+ Module file ./modules/simulator.module is loaded automatically if it exists
5033
+ (register your exchange there); without it CCXT Binance is used by default.
5034
+
4788
5035
  Flush flags (--flush):
4789
5036
 
4790
5037
  One or more positional entry points. For each entry point the following
@@ -4818,6 +5065,7 @@ Module hooks (loaded automatically by each mode):
4818
5065
  modules/dump.module --dump Exchange schema for candle dumps
4819
5066
  modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4820
5067
  modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
5068
+ modules/simulator.module --simulator Exchange schema for crowd-ideas simulation
4821
5069
 
4822
5070
  --flush has no associated module. It only removes dump subdirectories.
4823
5071
 
@@ -4846,6 +5094,8 @@ Examples:
4846
5094
  node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4847
5095
  node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4848
5096
  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
4849
5099
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4850
5100
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4851
5101
  node ${ENTRY_PATH} --init --output my-trading-bot
@@ -4859,7 +5109,7 @@ const main$1 = async () => {
4859
5109
  if (!values.help) {
4860
5110
  return;
4861
5111
  }
4862
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n\n`);
5112
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n\n`);
4863
5113
  process.stdout.write(HELP_TEXT);
4864
5114
  process.exit(0);
4865
5115
  };
@@ -4873,7 +5123,7 @@ const main = async () => {
4873
5123
  if (!values.version) {
4874
5124
  return;
4875
5125
  }
4876
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n`);
5126
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n`);
4877
5127
  process.exit(0);
4878
5128
  };
4879
5129
  main();
package/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import * as BacktestKit from 'backtest-kit';
3
- import { setConfig, 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 } 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
4
  import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
5
5
  import { exec, spawn } from 'child_process';
6
6
  import fs, { constants, realpathSync } from 'fs';
@@ -28,7 +28,7 @@ import Module, { createRequire } from 'module';
28
28
  import * as BacktestKitGraph from '@backtest-kit/graph';
29
29
  import * as BacktestKitOllama from '@backtest-kit/ollama';
30
30
  import * as BacktestKitPinets from '@backtest-kit/pinets';
31
- import { run as run$1, Code, toMarkdown } from '@backtest-kit/pinets';
31
+ import { run as run$1, Code, toMarkdown as toMarkdown$1 } from '@backtest-kit/pinets';
32
32
  import * as BacktestKitSignals from '@backtest-kit/signals';
33
33
  import open from 'open';
34
34
 
@@ -208,14 +208,14 @@ const getReadySubject = singleshot(() => {
208
208
  return new Subject();
209
209
  });
210
210
 
211
- const main$i = () => {
211
+ const main$j = () => {
212
212
  const entrySubject = getEntrySubject();
213
213
  entrySubject.subscribe(async (path) => {
214
214
  console.log("Running", path);
215
215
  await getReadySubject().next();
216
216
  });
217
217
  };
218
- main$i();
218
+ main$j();
219
219
 
220
220
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
221
221
  function dumpStackTrace(error) {
@@ -866,6 +866,10 @@ const getArgs = singleshot(() => {
866
866
  type: "boolean",
867
867
  default: false,
868
868
  },
869
+ simulator: {
870
+ type: "boolean",
871
+ default: false,
872
+ },
869
873
  commit: {
870
874
  type: "string",
871
875
  default: "",
@@ -3502,14 +3506,14 @@ const cli = {
3502
3506
  };
3503
3507
  init();
3504
3508
 
3505
- const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3509
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
3506
3510
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3507
3511
  const HELP_TEXT$1 = `
3508
3512
  Example:
3509
3513
 
3510
3514
  node ${ENTRY_PATH$1} --help
3511
3515
  `.trimStart();
3512
- const main$h = async () => {
3516
+ const main$i = async () => {
3513
3517
  if (!getEntry(import.meta.url)) {
3514
3518
  return;
3515
3519
  }
@@ -3517,14 +3521,14 @@ const main$h = async () => {
3517
3521
  if (MODES.some((mode) => values[mode])) {
3518
3522
  return;
3519
3523
  }
3520
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n`);
3524
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n`);
3521
3525
  process.stdout.write("\n");
3522
3526
  process.stdout.write(`Run with --help to see available commands.\n`);
3523
3527
  process.stdout.write("\n");
3524
3528
  process.stdout.write(HELP_TEXT$1);
3525
3529
  process.exit(0);
3526
3530
  };
3527
- main$h();
3531
+ main$i();
3528
3532
 
3529
3533
  const notifyShutdown = singleshot(async () => {
3530
3534
  console.log("Graceful shutdown initiated.");
@@ -3540,7 +3544,7 @@ const flush = async (entryPoint) => {
3540
3544
  console.log(`Removed: ${target}`);
3541
3545
  }
3542
3546
  };
3543
- const main$g = async () => {
3547
+ const main$h = async () => {
3544
3548
  if (!getEntry(import.meta.url)) {
3545
3549
  return;
3546
3550
  }
@@ -3557,7 +3561,7 @@ const main$g = async () => {
3557
3561
  }
3558
3562
  process.exit(0);
3559
3563
  };
3560
- main$g();
3564
+ main$h();
3561
3565
 
3562
3566
  const BEFORE_EXIT_FN$6 = singleshot(async () => {
3563
3567
  process.off("SIGINT", BEFORE_EXIT_FN$6);
@@ -3580,7 +3584,7 @@ const BEFORE_EXIT_FN$6 = singleshot(async () => {
3580
3584
  const listenGracefulShutdown$6 = singleshot(() => {
3581
3585
  process.on("SIGINT", BEFORE_EXIT_FN$6);
3582
3586
  });
3583
- const main$f = async () => {
3587
+ const main$g = async () => {
3584
3588
  if (!getEntry(import.meta.url)) {
3585
3589
  return;
3586
3590
  }
@@ -3598,7 +3602,7 @@ const main$f = async () => {
3598
3602
  await cli.backtestMainService.connect();
3599
3603
  listenGracefulShutdown$6();
3600
3604
  };
3601
- main$f();
3605
+ main$g();
3602
3606
 
3603
3607
  const BEFORE_EXIT_FN$5 = singleshot(async () => {
3604
3608
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -3617,7 +3621,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
3617
3621
  const listenGracefulShutdown$5 = singleshot(() => {
3618
3622
  process.on("SIGINT", BEFORE_EXIT_FN$5);
3619
3623
  });
3620
- const main$e = async () => {
3624
+ const main$f = async () => {
3621
3625
  if (!getEntry(import.meta.url)) {
3622
3626
  return;
3623
3627
  }
@@ -3636,7 +3640,7 @@ const main$e = async () => {
3636
3640
  listenGracefulShutdown$5();
3637
3641
  await cli.walkerMainService.connect();
3638
3642
  };
3639
- main$e();
3643
+ main$f();
3640
3644
 
3641
3645
  const BEFORE_EXIT_FN$4 = singleshot(async () => {
3642
3646
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -3658,7 +3662,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
3658
3662
  const listenGracefulShutdown$4 = singleshot(() => {
3659
3663
  process.on("SIGINT", BEFORE_EXIT_FN$4);
3660
3664
  });
3661
- const main$d = async () => {
3665
+ const main$e = async () => {
3662
3666
  if (!getEntry(import.meta.url)) {
3663
3667
  return;
3664
3668
  }
@@ -3672,7 +3676,7 @@ const main$d = async () => {
3672
3676
  await cli.paperMainService.connect();
3673
3677
  listenGracefulShutdown$4();
3674
3678
  };
3675
- main$d();
3679
+ main$e();
3676
3680
 
3677
3681
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
3678
3682
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -3694,7 +3698,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
3694
3698
  const listenGracefulShutdown$3 = singleshot(() => {
3695
3699
  process.on("SIGINT", BEFORE_EXIT_FN$3);
3696
3700
  });
3697
- const main$c = async () => {
3701
+ const main$d = async () => {
3698
3702
  if (!getEntry(import.meta.url)) {
3699
3703
  return;
3700
3704
  }
@@ -3708,7 +3712,7 @@ const main$c = async () => {
3708
3712
  await cli.liveMainService.connect();
3709
3713
  listenGracefulShutdown$3();
3710
3714
  };
3711
- main$c();
3715
+ main$d();
3712
3716
 
3713
3717
  const stopBacktestList$1 = async () => {
3714
3718
  for (const item of await Backtest.list()) {
@@ -3772,7 +3776,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
3772
3776
  const listenGracefulShutdown$2 = singleshot(() => {
3773
3777
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3774
3778
  });
3775
- const main$b = async () => {
3779
+ const main$c = async () => {
3776
3780
  if (!getEntry(import.meta.url)) {
3777
3781
  return;
3778
3782
  }
@@ -3828,7 +3832,7 @@ const main$b = async () => {
3828
3832
  listenGracefulShutdown$2();
3829
3833
  await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3830
3834
  };
3831
- main$b();
3835
+ main$c();
3832
3836
 
3833
3837
  const MODE_MODULE = {
3834
3838
  backtest: "backtest.module",
@@ -3931,7 +3935,7 @@ const createGracefulShutdown = (mode) => {
3931
3935
  process.on("SIGINT", handler);
3932
3936
  });
3933
3937
  };
3934
- const main$a = async () => {
3938
+ const main$b = async () => {
3935
3939
  if (!getEntry(import.meta.url)) {
3936
3940
  return;
3937
3941
  }
@@ -4007,7 +4011,7 @@ const main$a = async () => {
4007
4011
  }
4008
4012
  await getEntrySubject().next(absolutePath);
4009
4013
  };
4010
- main$a();
4014
+ main$b();
4011
4015
 
4012
4016
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
4013
4017
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -4018,7 +4022,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
4018
4022
  const listenGracefulShutdown$1 = singleshot(() => {
4019
4023
  process.on("SIGINT", BEFORE_EXIT_FN$1);
4020
4024
  });
4021
- const main$9 = async () => {
4025
+ const main$a = async () => {
4022
4026
  if (!getEntry(import.meta.url)) {
4023
4027
  return;
4024
4028
  }
@@ -4028,7 +4032,7 @@ const main$9 = async () => {
4028
4032
  }
4029
4033
  listenGracefulShutdown$1();
4030
4034
  };
4031
- main$9();
4035
+ main$a();
4032
4036
 
4033
4037
  const BEFORE_EXIT_FN = singleshot(async () => {
4034
4038
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -4039,7 +4043,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
4039
4043
  const listenGracefulShutdown = singleshot(() => {
4040
4044
  process.on("SIGINT", BEFORE_EXIT_FN);
4041
4045
  });
4042
- const main$8 = async () => {
4046
+ const main$9 = async () => {
4043
4047
  if (!getEntry(import.meta.url)) {
4044
4048
  return;
4045
4049
  }
@@ -4049,7 +4053,7 @@ const main$8 = async () => {
4049
4053
  }
4050
4054
  listenGracefulShutdown();
4051
4055
  };
4052
- main$8();
4056
+ main$9();
4053
4057
 
4054
4058
  const EXTRACT_ROWS_FN = (plots, schema) => {
4055
4059
  const keys = Object.keys(schema);
@@ -4071,7 +4075,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
4071
4075
  }
4072
4076
  return rows;
4073
4077
  };
4074
- const main$7 = async () => {
4078
+ const main$8 = async () => {
4075
4079
  if (!getEntry(import.meta.url)) {
4076
4080
  return;
4077
4081
  }
@@ -4163,17 +4167,17 @@ const main$7 = async () => {
4163
4167
  if (values.markdown) {
4164
4168
  const filePath = resolve(dumpDir, `${dumpName}.md`);
4165
4169
  await mkdir(dumpDir, { recursive: true });
4166
- await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
4170
+ await writeFile(filePath, await toMarkdown$1(signalId, plots, signalSchema), "utf-8");
4167
4171
  console.log(`Saved: ${filePath}`);
4168
4172
  process.exit(0);
4169
4173
  return;
4170
4174
  }
4171
- console.log(await toMarkdown(signalId, plots, signalSchema));
4175
+ console.log(await toMarkdown$1(signalId, plots, signalSchema));
4172
4176
  process.exit(0);
4173
4177
  };
4174
- main$7();
4178
+ main$8();
4175
4179
 
4176
- const main$6 = async () => {
4180
+ const main$7 = async () => {
4177
4181
  if (!getEntry(import.meta.url)) {
4178
4182
  return;
4179
4183
  }
@@ -4236,9 +4240,9 @@ const main$6 = async () => {
4236
4240
  };
4237
4241
  process.on("SIGINT", beforeExit);
4238
4242
  };
4239
- main$6();
4243
+ main$7();
4240
4244
 
4241
- const main$5 = async () => {
4245
+ const main$6 = async () => {
4242
4246
  if (!getEntry(import.meta.url)) {
4243
4247
  return;
4244
4248
  }
@@ -4319,9 +4323,9 @@ const main$5 = async () => {
4319
4323
  console.log(JSON.stringify(candles, null, 2));
4320
4324
  process.exit(0);
4321
4325
  };
4322
- main$5();
4326
+ main$6();
4323
4327
 
4324
- const main$4 = async () => {
4328
+ const main$5 = async () => {
4325
4329
  if (!getEntry(import.meta.url)) {
4326
4330
  return;
4327
4331
  }
@@ -4435,6 +4439,221 @@ const main$4 = async () => {
4435
4439
  }
4436
4440
  process.exit(0);
4437
4441
  };
4442
+ main$5();
4443
+
4444
+ const SIMULATOR_NAME = "cli_simulator";
4445
+ const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
4446
+ const validateIdea = (idea, line) => {
4447
+ if (typeof idea !== "object" || idea === null) {
4448
+ return `line ${line}: not an object`;
4449
+ }
4450
+ if (typeof idea.id !== "number") {
4451
+ return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
4452
+ }
4453
+ if (typeof idea.ts !== "number") {
4454
+ return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
4455
+ }
4456
+ if (typeof idea.symbol !== "string" || !idea.symbol) {
4457
+ return `line ${line}: "symbol" must be a non-empty string`;
4458
+ }
4459
+ if (!IDEA_DIRECTIONS.includes(idea.direction)) {
4460
+ return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
4461
+ }
4462
+ if (typeof idea.author !== "string" || !idea.author) {
4463
+ return `line ${line}: "author" must be a non-empty string`;
4464
+ }
4465
+ return null;
4466
+ };
4467
+ const toMarkdown = (result) => {
4468
+ const lines = [];
4469
+ lines.push(`# Simulator Report — ${result.symbol}`);
4470
+ lines.push("");
4471
+ lines.push(`| Metric | Value |`);
4472
+ lines.push(`| --- | --- |`);
4473
+ lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
4474
+ lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
4475
+ lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
4476
+ lines.push(`| Grid points | ${result.reports.length} |`);
4477
+ lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
4478
+ lines.push("");
4479
+ lines.push(`## Ranking winners`);
4480
+ lines.push("");
4481
+ lines.push(`| Criterion | Stop% | Trailing% | Hold | Aligned | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
4482
+ lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
4483
+ for (const best of result.best) {
4484
+ if (!best.report) {
4485
+ lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — | — | — |`);
4486
+ continue;
4487
+ }
4488
+ const { point } = best.report;
4489
+ lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.trailingTakePercent} | ${point.holdMinutes / 60}h | ${point.minIdeasAligned} | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
4490
+ `${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
4491
+ }
4492
+ lines.push("");
4493
+ lines.push(`## Allowed authors (production whitelist)`);
4494
+ lines.push("");
4495
+ lines.push(`| Author | Ideas | Hits | HitRate |`);
4496
+ lines.push(`| --- | --- | --- | --- |`);
4497
+ for (const stat of result.authorStats.filter(({ banned }) => !banned)) {
4498
+ lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
4499
+ }
4500
+ return lines.join("\n");
4501
+ };
4502
+ const main$4 = async () => {
4503
+ if (!getEntry(import.meta.url)) {
4504
+ return;
4505
+ }
4506
+ const { values, positionals } = getArgs();
4507
+ if (!values.simulator) {
4508
+ return;
4509
+ }
4510
+ const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
4511
+ if (!ideasPath) {
4512
+ console.error("Error: positional path to an ideas .jsonl file is required");
4513
+ process.exit(1);
4514
+ }
4515
+ let ideas = [];
4516
+ {
4517
+ let content;
4518
+ try {
4519
+ content = await readFile(resolve(ideasPath), "utf-8");
4520
+ }
4521
+ catch (error) {
4522
+ console.error(`Error: cannot read ideas file: ${ideasPath}`);
4523
+ process.exit(1);
4524
+ }
4525
+ const rows = content.split("\n").filter(Boolean);
4526
+ if (!rows.length) {
4527
+ console.error(`Error: ideas file is empty: ${ideasPath}`);
4528
+ process.exit(1);
4529
+ }
4530
+ for (let i = 0; i < rows.length; i++) {
4531
+ let idea;
4532
+ try {
4533
+ idea = JSON.parse(rows[i]);
4534
+ }
4535
+ catch {
4536
+ console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
4537
+ process.exit(1);
4538
+ }
4539
+ const problem = validateIdea(idea, i + 1);
4540
+ if (problem) {
4541
+ console.error(`Error: ideas file does not match the idea structure — ${problem}`);
4542
+ console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
4543
+ process.exit(1);
4544
+ }
4545
+ ideas.push(idea);
4546
+ }
4547
+ }
4548
+ {
4549
+ const cwd = process.cwd();
4550
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
4551
+ }
4552
+ await cli.configConnectionService.loadConfig("setup.config");
4553
+ {
4554
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
4555
+ try {
4556
+ if (typeof loader === "function") {
4557
+ await loader();
4558
+ }
4559
+ if (typeof loader?.loader === "function") {
4560
+ await loader.loader();
4561
+ }
4562
+ }
4563
+ catch (error) {
4564
+ console.error("Module loader failed", error);
4565
+ process.exit(-1);
4566
+ }
4567
+ }
4568
+ await cli.moduleConnectionService.loadModule("simulator.module");
4569
+ {
4570
+ await cli.exchangeSchemaService.addSchema();
4571
+ await cli.symbolSchemaService.addSchema();
4572
+ }
4573
+ const [defaultExchangeName = null] = await listExchangeSchema();
4574
+ const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
4575
+ const symbol = values.symbol || "BTCUSDT";
4576
+ addSimulatorSchema({
4577
+ simulatorName: SIMULATOR_NAME,
4578
+ exchangeName,
4579
+ callbacks: {
4580
+ onIdeas: (symbol, ideasTotal, ideasDirectional) => {
4581
+ if (values.verbose) {
4582
+ console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
4583
+ }
4584
+ },
4585
+ onProfiles: (symbol, profiles, truncatedCount) => {
4586
+ if (values.verbose) {
4587
+ console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
4588
+ }
4589
+ },
4590
+ onAuthorsTrained: (symbol, stats, bannedIdeas) => {
4591
+ if (values.verbose) {
4592
+ console.log("onAuthorsTrained", {
4593
+ symbol,
4594
+ authors: stats.length,
4595
+ banned: stats.filter(({ banned }) => banned).length,
4596
+ bannedIdeas,
4597
+ });
4598
+ }
4599
+ },
4600
+ onGridPoint: (symbol, report) => {
4601
+ if (values.verbose) {
4602
+ console.log("onGridPoint", {
4603
+ symbol,
4604
+ point: report.point,
4605
+ trades: report.trades,
4606
+ pnl: +report.totalPnlPercent.toFixed(2),
4607
+ sharpe: +report.sharpe.toFixed(2),
4608
+ });
4609
+ }
4610
+ },
4611
+ onRanking: (symbol, criterion, _sorted, best) => {
4612
+ if (values.verbose) {
4613
+ console.log("onRanking", {
4614
+ symbol,
4615
+ criterion,
4616
+ point: best.report?.point ?? null,
4617
+ pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
4618
+ });
4619
+ }
4620
+ },
4621
+ onDone: (symbol, result) => {
4622
+ if (values.verbose) {
4623
+ console.log("onDone", {
4624
+ symbol,
4625
+ reports: result.reports.length,
4626
+ allowedAuthors: result.allowedAuthors.length,
4627
+ bannedAuthors: result.bannedAuthors.length,
4628
+ });
4629
+ }
4630
+ },
4631
+ },
4632
+ });
4633
+ const result = await Simulator.run({
4634
+ symbol,
4635
+ simulatorName: SIMULATOR_NAME,
4636
+ ideas,
4637
+ });
4638
+ const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
4639
+ const dumpDir = join(process.cwd(), "dump");
4640
+ if (values.json) {
4641
+ const filePath = resolve(dumpDir, `${dumpName}.json`);
4642
+ await mkdir(dumpDir, { recursive: true });
4643
+ await writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
4644
+ console.log(`Saved: ${filePath}`);
4645
+ process.exit(0);
4646
+ }
4647
+ if (values.markdown) {
4648
+ const filePath = resolve(dumpDir, `${dumpName}.md`);
4649
+ await mkdir(dumpDir, { recursive: true });
4650
+ await writeFile(filePath, toMarkdown(result), "utf-8");
4651
+ console.log(`Saved: ${filePath}`);
4652
+ process.exit(0);
4653
+ }
4654
+ console.log(toMarkdown(result));
4655
+ process.exit(0);
4656
+ };
4438
4657
  main$4();
4439
4658
 
4440
4659
  const __filename$1 = fileURLToPath(import.meta.url);
@@ -4633,6 +4852,7 @@ Modes:
4633
4852
  --dump Fetch and save raw OHLCV candles
4634
4853
  --pnldebug Simulate PnL per minute for a given entry price and direction
4635
4854
  --brokerdebug Fire a single broker commit against the live broker adapter
4855
+ --simulator <ideas.jsonl> Sweep exit/entry parameters over crowd ideas, train author whitelist
4636
4856
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4637
4857
  --init Scaffold a new project in the current directory
4638
4858
  --docker Scaffold a Docker workspace for running strategies in a container
@@ -4753,6 +4973,33 @@ Broker debug flags (--brokerdebug):
4753
4973
  Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4754
4974
  the selected broker commit with synthetic payload values derived from current price.
4755
4975
 
4976
+ Simulator flags (--simulator):
4977
+
4978
+ --symbol <string> Trading pair to simulate (default: BTCUSDT)
4979
+ --exchange <string> Exchange name (default: first registered)
4980
+ --output <string> Output file base name (default: simulator_{SYMBOL}_{TIMESTAMP})
4981
+ --json Save full ISimulatorResult to ./dump/<output>.json
4982
+ --markdown Save summary report to ./dump/<output>.md
4983
+ --verbose Log every simulator lifecycle callback to the console
4984
+
4985
+ Positional: path to an ideas .jsonl file — one idea per line with the exact shape
4986
+ { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL",
4987
+ "author": string }. The file is validated BEFORE any work; a line that does not
4988
+ match the structure aborts the run with an error.
4989
+
4990
+ Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
4991
+ dedupe (one idea per author per direction per 8h), default-ban author filter
4992
+ (ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
4993
+ three ranking winners. The report includes the production author whitelist.
4994
+ Ideas of other symbols are filtered out — one shared feed serves any --symbol.
4995
+
4996
+ 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.
4999
+
5000
+ Module file ./modules/simulator.module is loaded automatically if it exists
5001
+ (register your exchange there); without it CCXT Binance is used by default.
5002
+
4756
5003
  Flush flags (--flush):
4757
5004
 
4758
5005
  One or more positional entry points. For each entry point the following
@@ -4786,6 +5033,7 @@ Module hooks (loaded automatically by each mode):
4786
5033
  modules/dump.module --dump Exchange schema for candle dumps
4787
5034
  modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4788
5035
  modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
5036
+ modules/simulator.module --simulator Exchange schema for crowd-ideas simulation
4789
5037
 
4790
5038
  --flush has no associated module. It only removes dump subdirectories.
4791
5039
 
@@ -4814,6 +5062,8 @@ Examples:
4814
5062
  node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4815
5063
  node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4816
5064
  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
4817
5067
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4818
5068
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4819
5069
  node ${ENTRY_PATH} --init --output my-trading-bot
@@ -4827,7 +5077,7 @@ const main$1 = async () => {
4827
5077
  if (!values.help) {
4828
5078
  return;
4829
5079
  }
4830
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n\n`);
5080
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n\n`);
4831
5081
  process.stdout.write(HELP_TEXT);
4832
5082
  process.exit(0);
4833
5083
  };
@@ -4841,7 +5091,7 @@ const main = async () => {
4841
5091
  if (!values.version) {
4842
5092
  return;
4843
5093
  }
4844
- process.stdout.write(`@backtest-kit/cli ${"16.2.0"}\n`);
5094
+ process.stdout.write(`@backtest-kit/cli ${"16.4.0"}\n`);
4845
5095
  process.exit(0);
4846
5096
  };
4847
5097
  main();
@@ -1,6 +1,7 @@
1
1
  export default {
2
2
  signal: true,
3
3
  risk: true,
4
+ pause: true,
4
5
  info: true,
5
6
  breakeven: true,
6
7
  common_error: true,
@@ -15,17 +15,17 @@
15
15
  "@types/node": "25.6.0"
16
16
  },
17
17
  "dependencies": {
18
- "@backtest-kit/cli": "16.2.0",
19
- "@backtest-kit/graph": "16.2.0",
20
- "@backtest-kit/pinets": "16.2.0",
21
- "@backtest-kit/signals": "16.2.0",
22
- "@backtest-kit/ui": "16.2.0",
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",
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.2.0",
28
+ "backtest-kit": "16.4.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.2.0",
3
+ "version": "16.4.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.2.0",
67
- "@backtest-kit/ollama": "16.2.0",
68
- "@backtest-kit/pinets": "16.2.0",
69
- "@backtest-kit/signals": "16.2.0",
70
- "@backtest-kit/ui": "16.2.0",
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",
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.2.0",
78
+ "backtest-kit": "16.4.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.2.0",
96
- "@backtest-kit/ollama": "^16.2.0",
97
- "@backtest-kit/pinets": "^16.2.0",
98
- "@backtest-kit/signals": "^16.2.0",
99
- "@backtest-kit/ui": "^16.2.0",
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",
100
100
  "pinets": "^0.9.29",
101
101
  "ccxt": "^4.5.39",
102
- "backtest-kit": "^16.2.0",
102
+ "backtest-kit": "^16.4.0",
103
103
  "markdown-it": "^14.1.1",
104
104
  "typescript": "^5.0.0"
105
105
  },
@@ -13,12 +13,12 @@
13
13
  "license": "ISC",
14
14
  "type": "commonjs",
15
15
  "dependencies": {
16
- "@backtest-kit/cli": "^16.2.0",
17
- "@backtest-kit/graph": "^16.2.0",
18
- "@backtest-kit/pinets": "^16.2.0",
19
- "@backtest-kit/ui": "^16.2.0",
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",
20
20
  "agent-swarm-kit": "^6.0.0",
21
- "backtest-kit": "^16.2.0",
21
+ "backtest-kit": "^16.4.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
@@ -320,6 +320,7 @@ declare class TelegramLogicService {
320
320
  interface NotificationConfig {
321
321
  signal: boolean;
322
322
  risk: boolean;
323
+ pause: boolean;
323
324
  info: boolean;
324
325
  breakeven: boolean;
325
326
  common_error: boolean;