@backtest-kit/cli 17.0.0 → 17.7.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/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$l = () => {
236
+ const main$k = () => {
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$l();
243
+ main$k();
244
244
 
245
245
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
246
246
  function dumpStackTrace(error) {
@@ -891,11 +891,7 @@ const getArgs = functoolsKit.singleshot(() => {
891
891
  type: "boolean",
892
892
  default: false,
893
893
  },
894
- simulator: {
895
- type: "boolean",
896
- default: false,
897
- },
898
- tune: {
894
+ sweep: {
899
895
  type: "boolean",
900
896
  default: false,
901
897
  },
@@ -3683,14 +3679,14 @@ const cli = {
3683
3679
  };
3684
3680
  init();
3685
3681
 
3686
- const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "tune", "flush", "init", "docker", "help", "version"];
3682
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "sweep", "flush", "init", "docker", "help", "version"];
3687
3683
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3688
3684
  const HELP_TEXT$1 = `
3689
3685
  Example:
3690
3686
 
3691
3687
  node ${ENTRY_PATH$1} --help
3692
3688
  `.trimStart();
3693
- const main$k = async () => {
3689
+ const main$j = async () => {
3694
3690
  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
3691
  return;
3696
3692
  }
@@ -3698,14 +3694,14 @@ const main$k = async () => {
3698
3694
  if (MODES.some((mode) => values[mode])) {
3699
3695
  return;
3700
3696
  }
3701
- process.stdout.write(`@backtest-kit/cli ${"17.0.0"}\n`);
3697
+ process.stdout.write(`@backtest-kit/cli ${"17.7.0"}\n`);
3702
3698
  process.stdout.write("\n");
3703
3699
  process.stdout.write(`Run with --help to see available commands.\n`);
3704
3700
  process.stdout.write("\n");
3705
3701
  process.stdout.write(HELP_TEXT$1);
3706
3702
  process.exit(0);
3707
3703
  };
3708
- main$k();
3704
+ main$j();
3709
3705
 
3710
3706
  const notifyShutdown = functoolsKit.singleshot(async () => {
3711
3707
  console.log("Graceful shutdown initiated.");
@@ -3721,7 +3717,7 @@ const flush = async (entryPoint) => {
3721
3717
  console.log(`Removed: ${target}`);
3722
3718
  }
3723
3719
  };
3724
- const main$j = async () => {
3720
+ const main$i = async () => {
3725
3721
  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)))) {
3726
3722
  return;
3727
3723
  }
@@ -3738,7 +3734,7 @@ const main$j = async () => {
3738
3734
  }
3739
3735
  process.exit(0);
3740
3736
  };
3741
- main$j();
3737
+ main$i();
3742
3738
 
3743
3739
  const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3744
3740
  process.off("SIGINT", BEFORE_EXIT_FN$6);
@@ -3761,7 +3757,7 @@ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3761
3757
  const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
3762
3758
  process.on("SIGINT", BEFORE_EXIT_FN$6);
3763
3759
  });
3764
- const main$i = async () => {
3760
+ const main$h = async () => {
3765
3761
  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)))) {
3766
3762
  return;
3767
3763
  }
@@ -3779,7 +3775,7 @@ const main$i = async () => {
3779
3775
  await cli.backtestMainService.connect();
3780
3776
  listenGracefulShutdown$6();
3781
3777
  };
3782
- main$i();
3778
+ main$h();
3783
3779
 
3784
3780
  const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3785
3781
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -3798,7 +3794,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3798
3794
  const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3799
3795
  process.on("SIGINT", BEFORE_EXIT_FN$5);
3800
3796
  });
3801
- const main$h = async () => {
3797
+ const main$g = async () => {
3802
3798
  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)))) {
3803
3799
  return;
3804
3800
  }
@@ -3817,7 +3813,7 @@ const main$h = async () => {
3817
3813
  listenGracefulShutdown$5();
3818
3814
  await cli.walkerMainService.connect();
3819
3815
  };
3820
- main$h();
3816
+ main$g();
3821
3817
 
3822
3818
  const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3823
3819
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -3839,7 +3835,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3839
3835
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3840
3836
  process.on("SIGINT", BEFORE_EXIT_FN$4);
3841
3837
  });
3842
- const main$g = async () => {
3838
+ const main$f = async () => {
3843
3839
  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)))) {
3844
3840
  return;
3845
3841
  }
@@ -3853,7 +3849,7 @@ const main$g = async () => {
3853
3849
  await cli.paperMainService.connect();
3854
3850
  listenGracefulShutdown$4();
3855
3851
  };
3856
- main$g();
3852
+ main$f();
3857
3853
 
3858
3854
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3859
3855
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -3875,7 +3871,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3875
3871
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3876
3872
  process.on("SIGINT", BEFORE_EXIT_FN$3);
3877
3873
  });
3878
- const main$f = async () => {
3874
+ const main$e = async () => {
3879
3875
  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)))) {
3880
3876
  return;
3881
3877
  }
@@ -3889,7 +3885,7 @@ const main$f = async () => {
3889
3885
  await cli.liveMainService.connect();
3890
3886
  listenGracefulShutdown$3();
3891
3887
  };
3892
- main$f();
3888
+ main$e();
3893
3889
 
3894
3890
  const stopBacktestList$1 = async () => {
3895
3891
  for (const item of await BacktestKit.Backtest.list()) {
@@ -3953,7 +3949,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3953
3949
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
3954
3950
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3955
3951
  });
3956
- const main$e = async () => {
3952
+ const main$d = async () => {
3957
3953
  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)))) {
3958
3954
  return;
3959
3955
  }
@@ -4009,7 +4005,7 @@ const main$e = async () => {
4009
4005
  listenGracefulShutdown$2();
4010
4006
  await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
4011
4007
  };
4012
- main$e();
4008
+ main$d();
4013
4009
 
4014
4010
  const MODE_MODULE = {
4015
4011
  backtest: "backtest.module",
@@ -4112,7 +4108,7 @@ const createGracefulShutdown = (mode) => {
4112
4108
  process.on("SIGINT", handler);
4113
4109
  });
4114
4110
  };
4115
- const main$d = async () => {
4111
+ const main$c = async () => {
4116
4112
  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)))) {
4117
4113
  return;
4118
4114
  }
@@ -4188,7 +4184,7 @@ const main$d = async () => {
4188
4184
  }
4189
4185
  await getEntrySubject().next(absolutePath);
4190
4186
  };
4191
- main$d();
4187
+ main$c();
4192
4188
 
4193
4189
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
4194
4190
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -4199,7 +4195,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
4199
4195
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
4200
4196
  process.on("SIGINT", BEFORE_EXIT_FN$1);
4201
4197
  });
4202
- const main$c = async () => {
4198
+ const main$b = async () => {
4203
4199
  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)))) {
4204
4200
  return;
4205
4201
  }
@@ -4209,7 +4205,7 @@ const main$c = async () => {
4209
4205
  }
4210
4206
  listenGracefulShutdown$1();
4211
4207
  };
4212
- main$c();
4208
+ main$b();
4213
4209
 
4214
4210
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
4215
4211
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -4220,7 +4216,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
4220
4216
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
4221
4217
  process.on("SIGINT", BEFORE_EXIT_FN);
4222
4218
  });
4223
- const main$b = async () => {
4219
+ const main$a = async () => {
4224
4220
  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)))) {
4225
4221
  return;
4226
4222
  }
@@ -4230,7 +4226,7 @@ const main$b = async () => {
4230
4226
  }
4231
4227
  listenGracefulShutdown();
4232
4228
  };
4233
- main$b();
4229
+ main$a();
4234
4230
 
4235
4231
  const EXTRACT_ROWS_FN = (plots, schema) => {
4236
4232
  const keys = Object.keys(schema);
@@ -4252,7 +4248,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
4252
4248
  }
4253
4249
  return rows;
4254
4250
  };
4255
- const main$a = async () => {
4251
+ const main$9 = async () => {
4256
4252
  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)))) {
4257
4253
  return;
4258
4254
  }
@@ -4352,9 +4348,9 @@ const main$a = async () => {
4352
4348
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
4353
4349
  process.exit(0);
4354
4350
  };
4355
- main$a();
4351
+ main$9();
4356
4352
 
4357
- const main$9 = async () => {
4353
+ const main$8 = async () => {
4358
4354
  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)))) {
4359
4355
  return;
4360
4356
  }
@@ -4417,9 +4413,9 @@ const main$9 = async () => {
4417
4413
  };
4418
4414
  process.on("SIGINT", beforeExit);
4419
4415
  };
4420
- main$9();
4416
+ main$8();
4421
4417
 
4422
- const main$8 = async () => {
4418
+ const main$7 = async () => {
4423
4419
  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)))) {
4424
4420
  return;
4425
4421
  }
@@ -4500,9 +4496,9 @@ const main$8 = async () => {
4500
4496
  console.log(JSON.stringify(candles, null, 2));
4501
4497
  process.exit(0);
4502
4498
  };
4503
- main$8();
4499
+ main$7();
4504
4500
 
4505
- const main$7 = async () => {
4501
+ const main$6 = async () => {
4506
4502
  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)))) {
4507
4503
  return;
4508
4504
  }
@@ -4616,7 +4612,7 @@ const main$7 = async () => {
4616
4612
  }
4617
4613
  process.exit(0);
4618
4614
  };
4619
- main$7();
4615
+ main$6();
4620
4616
 
4621
4617
  const getBroker = functoolsKit.singleshot(() => {
4622
4618
  // Reaches into BrokerAdapter internals on purpose: the public commit*
@@ -4725,7 +4721,7 @@ const ZERO_PNL = {
4725
4721
  pnlCost: 0,
4726
4722
  pnlEntries: 0,
4727
4723
  };
4728
- const main$6 = async () => {
4724
+ const main$5 = async () => {
4729
4725
  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)))) {
4730
4726
  return;
4731
4727
  }
@@ -4919,23 +4915,23 @@ const main$6 = async () => {
4919
4915
  }
4920
4916
  process.exit(0);
4921
4917
  };
4922
- main$6();
4918
+ main$5();
4923
4919
 
4924
- const SIMULATOR_NAME$1 = "cli_simulator";
4925
- const CONFIG_KEYS$1 = ["gridAxes", "reportOrder"];
4926
- const validateConfig$1 = (config) => {
4920
+ const SIMULATOR_NAME = "cli_sweep";
4921
+ const CONFIG_KEYS = ["gridAxes", "reportOrder"];
4922
+ const validateConfig = (config) => {
4927
4923
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
4928
4924
  return "config must be a JSON object";
4929
4925
  }
4930
4926
  for (const key of Object.keys(config)) {
4931
- if (!CONFIG_KEYS$1.includes(key)) {
4932
- return `unknown key "${key}" (expected: ${CONFIG_KEYS$1.join(", ")})`;
4927
+ if (!CONFIG_KEYS.includes(key)) {
4928
+ return `unknown key "${key}" (expected: ${CONFIG_KEYS.join(", ")})`;
4933
4929
  }
4934
4930
  }
4935
4931
  return null;
4936
4932
  };
4937
- const IDEA_DIRECTIONS$1 = ["LONG", "SHORT", "NEUTRAL"];
4938
- const validateIdea$1 = (idea, line) => {
4933
+ const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
4934
+ const validateIdea = (idea, line) => {
4939
4935
  if (typeof idea !== "object" || idea === null) {
4940
4936
  return `line ${line}: not an object`;
4941
4937
  }
@@ -4948,20 +4944,21 @@ const validateIdea$1 = (idea, line) => {
4948
4944
  if (typeof idea.symbol !== "string" || !idea.symbol) {
4949
4945
  return `line ${line}: "symbol" must be a non-empty string`;
4950
4946
  }
4951
- if (!IDEA_DIRECTIONS$1.includes(idea.direction)) {
4952
- return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS$1.join("|")}, got ${JSON.stringify(idea.direction)}`;
4947
+ if (!IDEA_DIRECTIONS.includes(idea.direction)) {
4948
+ return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
4953
4949
  }
4954
4950
  if (typeof idea.author !== "string" || !idea.author) {
4955
4951
  return `line ${line}: "author" must be a non-empty string`;
4956
4952
  }
4957
4953
  return null;
4958
4954
  };
4959
- const toMarkdown$1 = (result) => {
4960
- const buckets = Object.entries(result.reports).filter(([, bucket]) => bucket.reports.length > 0);
4961
- const allReports = buckets.flatMap(([, bucket]) => bucket.reports);
4955
+ const fmtSimRatio = (value) => Number.isFinite(value) ? value.toFixed(2) : "inf";
4956
+ const toMarkdown = (result) => {
4957
+ const bucket = result.reports;
4958
+ const allReports = bucket.reports;
4962
4959
  const profitable = allReports.filter(({ totalPnlPercent }) => totalPnlPercent > 0).length;
4963
4960
  const lines = [];
4964
- lines.push(`# Simulator Report — ${result.symbol}`);
4961
+ lines.push(`# Sweep Report — ${result.symbol}`);
4965
4962
  lines.push("");
4966
4963
  lines.push(`| Metric | Value |`);
4967
4964
  lines.push(`| --- | --- |`);
@@ -4969,41 +4966,44 @@ const toMarkdown$1 = (result) => {
4969
4966
  lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
4970
4967
  lines.push(`| Profitable corridor | ${profitable} / ${allReports.length} grid points |`);
4971
4968
  lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
4972
- // каждая метрика — самодостаточная корзина: свои победители и
4973
- // свои словари банов, между собой не склеиваются
4974
- for (const [metric, bucket] of buckets) {
4975
- lines.push("");
4976
- lines.push(`## Metric: ${metric}`);
4977
- lines.push("");
4978
- lines.push(`| Criterion | Stop% | Hold | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
4979
- lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
4980
- for (const best of bucket.best) {
4981
- if (!best.report) {
4982
- lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — |`);
4983
- continue;
4984
- }
4985
- const { point } = best.report;
4986
- lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.holdMinutes / 60}h | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
4987
- `${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
4988
- }
4989
- const sharpeBest = bucket.best.find(({ criterion }) => criterion === "sharpe");
4990
- lines.push("");
4991
- lines.push(`### Allowed authors (sharpe winner rule)`);
4992
- lines.push("");
4993
- lines.push(`| Author | Ideas | Hits | HitRate |`);
4994
- lines.push(`| --- | --- | --- | --- |`);
4995
- for (const stat of (sharpeBest?.authorStats ?? []).filter(({ banned }) => !banned)) {
4996
- lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
4969
+ // единственная метрика — profit-before-stop: одна корзина с
4970
+ // победителями и author tracks
4971
+ lines.push("");
4972
+ lines.push(`## Grid winners`);
4973
+ lines.push("");
4974
+ lines.push(`| Criterion | Stop% | Trail% | Hold | Lock% | Trades | PNL% | WinRate | Sharpe | Sortino |`);
4975
+ lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
4976
+ for (const best of bucket.best) {
4977
+ if (!best.report) {
4978
+ lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — |`);
4979
+ continue;
4997
4980
  }
4981
+ const { point } = best.report;
4982
+ lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.trailingTakePercent} | ${point.holdMinutes / 60}h | ${point.profitLockPercent} | ` +
4983
+ `${best.report.tradesList.length} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${fmtSimRatio(best.report.sharpe)} | ${fmtSimRatio(best.report.sortino)} |`);
4984
+ }
4985
+ // сырой author track: userspace решает, кому верить (порога/бана
4986
+ // нет). Топ по hitRate
4987
+ const tracks = [...bucket.tracks]
4988
+ .filter(({ ideas }) => ideas > 0)
4989
+ .sort((a, b) => b.hitRate - a.hitRate || b.ideas - a.ideas)
4990
+ .slice(0, 20);
4991
+ lines.push("");
4992
+ lines.push(`### Author tracks — raw ideas/hits/hitRate per rule (top ${tracks.length} by hitRate; full set in --json)`);
4993
+ lines.push("");
4994
+ lines.push(`| Author | Hold | Lock% | Stop% | Trail% | Ideas | Hits | HitRate |`);
4995
+ lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- |`);
4996
+ for (const t of tracks) {
4997
+ lines.push(`| ${t.author} | ${t.holdMinutes / 60}h | ${t.profitLockPercent} | ${t.hardStopPercent} | ${t.trailingTakePercent} | ${t.ideas} | ${t.hits} | ${(t.hitRate * 100).toFixed(0)}% |`);
4998
4998
  }
4999
4999
  return lines.join("\n");
5000
5000
  };
5001
- const main$5 = async () => {
5001
+ const main$4 = async () => {
5002
5002
  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)))) {
5003
5003
  return;
5004
5004
  }
5005
5005
  const { values, positionals } = getArgs();
5006
- if (!values.simulator) {
5006
+ if (!values.sweep) {
5007
5007
  return;
5008
5008
  }
5009
5009
  const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
@@ -5031,10 +5031,10 @@ const main$5 = async () => {
5031
5031
  console.error(`Error: invalid JSON in config file: ${configPath}`);
5032
5032
  process.exit(1);
5033
5033
  }
5034
- const problem = validateConfig$1(config);
5034
+ const problem = validateConfig(config);
5035
5035
  if (problem) {
5036
- console.error(`Error: simulator config does not match the structure — ${problem}`);
5037
- console.error(`Expected shape: { "gridAxes"?: ISimulatorGridAxes, "reportOrder"?: "sharpe"|"sortino"|"pnl"|"recovery" }`);
5036
+ console.error(`Error: sweep config does not match the structure — ${problem}`);
5037
+ console.error(`Expected shape: { "gridAxes"?: ISweepGridAxes, "reportOrder"?: "sharpe"|"sortino"|"pnl"|"recovery" }`);
5038
5038
  process.exit(1);
5039
5039
  }
5040
5040
  }
@@ -5062,7 +5062,7 @@ const main$5 = async () => {
5062
5062
  console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
5063
5063
  process.exit(1);
5064
5064
  }
5065
- const problem = validateIdea$1(idea, i + 1);
5065
+ const problem = validateIdea(idea, i + 1);
5066
5066
  if (problem) {
5067
5067
  console.error(`Error: ideas file does not match the idea structure — ${problem}`);
5068
5068
  console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
@@ -5091,7 +5091,7 @@ const main$5 = async () => {
5091
5091
  process.exit(-1);
5092
5092
  }
5093
5093
  }
5094
- await cli.moduleConnectionService.loadModule("simulator.module");
5094
+ await cli.moduleConnectionService.loadModule("sweep.module");
5095
5095
  {
5096
5096
  await cli.exchangeSchemaService.addSchema();
5097
5097
  await cli.symbolSchemaService.addSchema();
@@ -5111,8 +5111,8 @@ const main$5 = async () => {
5111
5111
  const symbol = values.symbol || "BTCUSDT";
5112
5112
  // оси и порядок отчёта приходят позиционным конфигом потребителя;
5113
5113
  // пустой конфиг — дефолтная сетка движка из connection-сервиса
5114
- BacktestKit.addSimulatorSchema({
5115
- simulatorName: SIMULATOR_NAME$1,
5114
+ BacktestKit.addSweepSchema({
5115
+ sweepName: SIMULATOR_NAME,
5116
5116
  exchangeName,
5117
5117
  ...(config.gridAxes ? { gridAxes: config.gridAxes } : {}),
5118
5118
  ...(config.reportOrder ? { reportOrder: config.reportOrder } : {}),
@@ -5132,22 +5132,20 @@ const main$5 = async () => {
5132
5132
  console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
5133
5133
  }
5134
5134
  },
5135
- onAuthorsTrained: (symbol, stats, bannedIdeas) => {
5135
+ onAuthorsTrained: (symbol, tracks) => {
5136
5136
  if (values.verbose) {
5137
5137
  console.log("onAuthorsTrained", {
5138
5138
  symbol,
5139
- authors: stats.length,
5140
- banned: stats.filter(({ banned }) => banned).length,
5141
- bannedIdeas,
5139
+ authors: tracks.length,
5142
5140
  });
5143
5141
  }
5144
5142
  },
5145
- onGridPoint: (symbol, report) => {
5143
+ onGridPoint: (symbol, report, trades) => {
5146
5144
  if (values.verbose) {
5147
5145
  console.log("onGridPoint", {
5148
5146
  symbol,
5149
5147
  point: report.point,
5150
- trades: report.trades,
5148
+ trades: trades.length,
5151
5149
  pnl: +report.totalPnlPercent.toFixed(2),
5152
5150
  sharpe: +report.sharpe.toFixed(2),
5153
5151
  });
@@ -5167,19 +5165,19 @@ const main$5 = async () => {
5167
5165
  if (values.verbose) {
5168
5166
  console.log("onDone", {
5169
5167
  symbol,
5170
- reports: Object.values(result.reports).flatMap((bucket) => bucket.reports).length,
5171
- bans: Object.values(result.reports).flatMap((bucket) => bucket.bans).length,
5168
+ reports: result.reports.reports.length,
5169
+ tracks: result.reports.tracks.length,
5172
5170
  });
5173
5171
  }
5174
5172
  },
5175
5173
  },
5176
5174
  });
5177
- const result = await BacktestKit.Simulator.run({
5175
+ const result = await BacktestKit.Sweep.run({
5178
5176
  symbol,
5179
- simulatorName: SIMULATOR_NAME$1,
5177
+ sweepName: SIMULATOR_NAME,
5180
5178
  ideas,
5181
5179
  });
5182
- const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
5180
+ const dumpName = values.output || `sweep_${symbol}_${Date.now()}`;
5183
5181
  const dumpDir = path.join(process.cwd(), "dump");
5184
5182
  if (values.json) {
5185
5183
  const filePath = path.resolve(dumpDir, `${dumpName}.json`);
@@ -5191,308 +5189,11 @@ const main$5 = async () => {
5191
5189
  if (values.markdown) {
5192
5190
  const filePath = path.resolve(dumpDir, `${dumpName}.md`);
5193
5191
  await fs$1.mkdir(dumpDir, { recursive: true });
5194
- await fs$1.writeFile(filePath, toMarkdown$1(result), "utf-8");
5192
+ await fs$1.writeFile(filePath, toMarkdown(result), "utf-8");
5195
5193
  console.log(`Saved: ${filePath}`);
5196
5194
  process.exit(0);
5197
5195
  }
5198
- console.log(toMarkdown$1(result));
5199
- process.exit(0);
5200
- };
5201
- main$5();
5202
-
5203
- const SIMULATOR_NAME = "cli_tune";
5204
- const CONFIG_KEYS = ["point", "authorStats", "gridAxes", "reportOrder"];
5205
- const POINT_NUMBER_FIELDS = [
5206
- "hardStopPercent",
5207
- "trailingTakePercent",
5208
- "holdMinutes",
5209
- "minAuthorTrack",
5210
- "minAuthorHitRate",
5211
- "profitLockPercent",
5212
- ];
5213
- const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
5214
- const validateIdea = (idea, line) => {
5215
- if (typeof idea !== "object" || idea === null) {
5216
- return `line ${line}: not an object`;
5217
- }
5218
- if (typeof idea.id !== "number") {
5219
- return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
5220
- }
5221
- if (typeof idea.ts !== "number") {
5222
- return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
5223
- }
5224
- if (typeof idea.symbol !== "string" || !idea.symbol) {
5225
- return `line ${line}: "symbol" must be a non-empty string`;
5226
- }
5227
- if (!IDEA_DIRECTIONS.includes(idea.direction)) {
5228
- return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
5229
- }
5230
- if (typeof idea.author !== "string" || !idea.author) {
5231
- return `line ${line}: "author" must be a non-empty string`;
5232
- }
5233
- return null;
5234
- };
5235
- const validateConfig = (config) => {
5236
- if (typeof config !== "object" || config === null || Array.isArray(config)) {
5237
- return "config must be a JSON object";
5238
- }
5239
- for (const key of Object.keys(config)) {
5240
- if (!CONFIG_KEYS.includes(key)) {
5241
- return `unknown key "${key}" (expected: ${CONFIG_KEYS.join(", ")})`;
5242
- }
5243
- }
5244
- if (typeof config.point !== "object" || config.point === null) {
5245
- return `"point" is required — the frozen grid point of a training run`;
5246
- }
5247
- for (const field of POINT_NUMBER_FIELDS) {
5248
- if (typeof config.point[field] !== "number") {
5249
- return `"point.${field}" must be a number, got ${typeof config.point[field]}`;
5250
- }
5251
- }
5252
- if (typeof config.point.authorMetric !== "string") {
5253
- return `"point.authorMetric" must be a string, got ${typeof config.point.authorMetric}`;
5254
- }
5255
- if (!Array.isArray(config.authorStats) || !config.authorStats.length) {
5256
- return `"authorStats" is required — the frozen raw track record (non-empty array)`;
5257
- }
5258
- for (let i = 0; i < config.authorStats.length; i++) {
5259
- const stat = config.authorStats[i];
5260
- if (typeof stat !== "object" ||
5261
- stat === null ||
5262
- typeof stat.author !== "string" ||
5263
- typeof stat.ideas !== "number" ||
5264
- typeof stat.hits !== "number") {
5265
- return `"authorStats[${i}]" must be { author: string, ideas: number, hits: number }`;
5266
- }
5267
- }
5268
- return null;
5269
- };
5270
- /** Одноточечные оси, зеркалящие замороженную точку — сетка инертна. */
5271
- const axesFromPoint = (point) => ({
5272
- hardStopPercent: [point.hardStopPercent],
5273
- trailingTakePercent: [point.trailingTakePercent],
5274
- holdMinutes: [point.holdMinutes],
5275
- minAuthorTrack: [point.minAuthorTrack],
5276
- minAuthorHitRate: [point.minAuthorHitRate],
5277
- profitLockPercent: [point.profitLockPercent],
5278
- authorMetric: [point.authorMetric],
5279
- });
5280
- const pointLabel = (point) => `H=${point.hardStopPercent} TT=${point.trailingTakePercent} hold=${point.holdMinutes / 60}h ` +
5281
- `track=${point.minAuthorTrack} rate=${point.minAuthorHitRate} ` +
5282
- `lock=${point.profitLockPercent} metric=${point.authorMetric}`;
5283
- const fmtRatio = (value) => Number.isFinite(value) ? value.toFixed(2) : "inf";
5284
- const toMarkdown = (test) => {
5285
- const lines = [];
5286
- lines.push(`# Tune Report — ${test.symbol}`);
5287
- lines.push("");
5288
- lines.push(`ONE out-of-sample shot of a FROZEN training artifact: nothing is trained here — the point and the raw author track record come from the input config verbatim, bans are re-derived from the frozen numbers under the point's rule, unseen authors are banned by default.`);
5289
- lines.push("");
5290
- lines.push(`Frozen point: ${pointLabel(test.point)}`);
5291
- lines.push("");
5292
- lines.push(`## Out-of-sample (${test.ideasDirectional} directional ideas)`);
5293
- lines.push("");
5294
- lines.push(`| Metric | Value |`);
5295
- lines.push(`| --- | --- |`);
5296
- lines.push(`| Trades (skipped busy) | ${test.report.trades} (${test.report.skippedBusy}) |`);
5297
- lines.push(`| PNL | ${test.report.totalPnlPercent.toFixed(2)}% |`);
5298
- lines.push(`| Win rate | ${(test.report.winRate * 100).toFixed(0)}% |`);
5299
- lines.push(`| Profit factor | ${fmtRatio(test.report.profitFactor)} |`);
5300
- lines.push(`| Max series drawdown | ${test.report.maxSeriesDrawdownPercent.toFixed(2)}% |`);
5301
- lines.push(`| Sharpe / Sortino | ${fmtRatio(test.report.sharpe)} / ${fmtRatio(test.report.sortino)} |`);
5302
- lines.push(`| Recovery factor | ${fmtRatio(test.report.recoveryFactor)} |`);
5303
- lines.push(`| Exits | ${Object.entries(test.report.exitReasons).map(([reason, count]) => `${reason}=${count}`).join(", ")} |`);
5304
- lines.push("");
5305
- lines.push(`## Test trades`);
5306
- lines.push("");
5307
- lines.push(`| Direction | Exit | PNL% | Hold | Entry (UTC) |`);
5308
- lines.push(`| --- | --- | --- | --- | --- |`);
5309
- for (const trade of test.trades) {
5310
- lines.push(`| ${trade.direction} | ${trade.exitReason} | ${trade.pnlPercent.toFixed(2)} | ${trade.holdMinutesActual}m | ${new Date(trade.entryTimestamp).toISOString()} |`);
5311
- }
5312
- lines.push("");
5313
- lines.push(`## Frozen author track record (re-derived bans under the point's rule)`);
5314
- lines.push("");
5315
- lines.push(`| Author | Ideas | Hits | Banned |`);
5316
- lines.push(`| --- | --- | --- | --- |`);
5317
- for (const stat of test.authorStats) {
5318
- lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${stat.banned ? "yes" : ""} |`);
5319
- }
5320
- return lines.join("\n");
5321
- };
5322
- const main$4 = async () => {
5323
- 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)))) {
5324
- return;
5325
- }
5326
- const { values, positionals } = getArgs();
5327
- if (!values.tune) {
5328
- return;
5329
- }
5330
- const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
5331
- if (!ideasPath) {
5332
- console.error("Error: positional path to an ideas .jsonl file is required");
5333
- process.exit(1);
5334
- }
5335
- // конфиг — позиционный JSON; без него подтягивается пустой объект
5336
- // (и валидация честно скажет, что замороженной точки нет)
5337
- const [configPath = null] = positionals.filter((value) => value.endsWith(".json") && !value.endsWith(".jsonl"));
5338
- let config = {};
5339
- if (configPath) {
5340
- let content;
5341
- try {
5342
- content = await fs$1.readFile(path.resolve(configPath), "utf-8");
5343
- }
5344
- catch (error) {
5345
- console.error(`Error: cannot read config file: ${configPath}`);
5346
- process.exit(1);
5347
- }
5348
- try {
5349
- config = JSON.parse(content);
5350
- }
5351
- catch {
5352
- console.error(`Error: invalid JSON in config file: ${configPath}`);
5353
- process.exit(1);
5354
- }
5355
- }
5356
- {
5357
- const problem = validateConfig(config);
5358
- if (problem) {
5359
- console.error(`Error: tune config does not match the structure — ${problem}`);
5360
- console.error(`Expected shape: { "point": ISimulatorGridPoint, "authorStats": [{ "author", "ideas", "hits" }], "gridAxes"?, "reportOrder"? }`);
5361
- process.exit(1);
5362
- }
5363
- }
5364
- let ideas = [];
5365
- {
5366
- let content;
5367
- try {
5368
- content = await fs$1.readFile(path.resolve(ideasPath), "utf-8");
5369
- }
5370
- catch (error) {
5371
- console.error(`Error: cannot read ideas file: ${ideasPath}`);
5372
- process.exit(1);
5373
- }
5374
- const rows = content.split("\n").filter(Boolean);
5375
- if (!rows.length) {
5376
- console.error(`Error: ideas file is empty: ${ideasPath}`);
5377
- process.exit(1);
5378
- }
5379
- for (let i = 0; i < rows.length; i++) {
5380
- let idea;
5381
- try {
5382
- idea = JSON.parse(rows[i]);
5383
- }
5384
- catch {
5385
- console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
5386
- process.exit(1);
5387
- }
5388
- const problem = validateIdea(idea, i + 1);
5389
- if (problem) {
5390
- console.error(`Error: ideas file does not match the idea structure — ${problem}`);
5391
- console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
5392
- process.exit(1);
5393
- }
5394
- ideas.push(idea);
5395
- }
5396
- }
5397
- {
5398
- const cwd = process.cwd();
5399
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
5400
- }
5401
- await cli.configConnectionService.loadConfig("setup.config");
5402
- {
5403
- const loader = await cli.configConnectionService.loadConfig("loader.config");
5404
- try {
5405
- if (typeof loader === "function") {
5406
- await loader();
5407
- }
5408
- if (typeof loader?.loader === "function") {
5409
- await loader.loader();
5410
- }
5411
- }
5412
- catch (error) {
5413
- console.error("Module loader failed", error);
5414
- process.exit(-1);
5415
- }
5416
- }
5417
- await cli.moduleConnectionService.loadModule("tune.module");
5418
- {
5419
- await cli.exchangeSchemaService.addSchema();
5420
- await cli.symbolSchemaService.addSchema();
5421
- }
5422
- const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
5423
- const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
5424
- if (values.verbose) {
5425
- BacktestKit.overrideExchangeSchema({
5426
- exchangeName,
5427
- callbacks: {
5428
- onCandleData(symbol, interval, since) {
5429
- console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
5430
- },
5431
- },
5432
- });
5433
- }
5434
- const symbol = values.symbol || "BTCUSDT";
5435
- // сетка для test() инертна (оценивается ровно замороженная точка):
5436
- // без явных осей в конфиге они одноточечно зеркалят точку
5437
- BacktestKit.addSimulatorSchema({
5438
- simulatorName: SIMULATOR_NAME,
5439
- exchangeName,
5440
- gridAxes: config.gridAxes ?? axesFromPoint(config.point),
5441
- ...(config.reportOrder ? { reportOrder: config.reportOrder } : {}),
5442
- callbacks: {
5443
- onProgress: (symbol, stage, processed, total) => {
5444
- if (values.verbose) {
5445
- console.log("onProgress", { symbol, stage, processed, total });
5446
- }
5447
- },
5448
- onIdeas: (symbol, ideasTotal, ideasDirectional) => {
5449
- if (values.verbose) {
5450
- console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
5451
- }
5452
- },
5453
- onProfiles: (symbol, profiles, truncatedCount) => {
5454
- if (values.verbose) {
5455
- console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
5456
- }
5457
- },
5458
- onTestDone: (symbol, result) => {
5459
- if (values.verbose) {
5460
- console.log("onTestDone", {
5461
- symbol,
5462
- trades: result.report.trades,
5463
- pnl: +result.report.totalPnlPercent.toFixed(2),
5464
- });
5465
- }
5466
- },
5467
- },
5468
- });
5469
- // только out-of-sample: обучения нет, точка и трек-рекорд приходят
5470
- // из конфига verbatim — test() перевыводит баны под правило точки,
5471
- // невиданные авторы забанены по умолчанию
5472
- const test = await BacktestKit.Simulator.test({
5473
- symbol,
5474
- simulatorName: SIMULATOR_NAME,
5475
- ideas,
5476
- point: config.point,
5477
- authorStats: config.authorStats,
5478
- });
5479
- const dumpName = values.output || `tune_${symbol}_${Date.now()}`;
5480
- const dumpDir = path.join(process.cwd(), "dump");
5481
- if (values.json) {
5482
- const filePath = path.resolve(dumpDir, `${dumpName}.json`);
5483
- await fs$1.mkdir(dumpDir, { recursive: true });
5484
- await fs$1.writeFile(filePath, JSON.stringify(test, null, 2), "utf-8");
5485
- console.log(`Saved: ${filePath}`);
5486
- process.exit(0);
5487
- }
5488
- if (values.markdown) {
5489
- const filePath = path.resolve(dumpDir, `${dumpName}.md`);
5490
- await fs$1.mkdir(dumpDir, { recursive: true });
5491
- await fs$1.writeFile(filePath, toMarkdown(test), "utf-8");
5492
- console.log(`Saved: ${filePath}`);
5493
- process.exit(0);
5494
- }
5495
- console.log(toMarkdown(test));
5196
+ console.log(toMarkdown(result));
5496
5197
  process.exit(0);
5497
5198
  };
5498
5199
  main$4();
@@ -5693,8 +5394,7 @@ Modes:
5693
5394
  --dump Fetch and save raw OHLCV candles
5694
5395
  --pnldebug Simulate PnL per minute for a given entry price and direction
5695
5396
  --brokerdebug Fire a single broker commit against the live broker adapter
5696
- --simulator <ideas.jsonl> [config.json] Feasibility probe over crowd ideas: is there a profitable corridor at all
5697
- --tune <ideas.jsonl> <config.json> ONE out-of-sample shot of a frozen training artifact (point + author track)
5397
+ --sweep <ideas.jsonl> [config.json] Grid sweep over crowd ideas: profit-before-stop corridor + raw author tracks
5698
5398
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
5699
5399
  --init Scaffold a new project in the current directory
5700
5400
  --docker Scaffold a Docker workspace for running strategies in a container
@@ -5815,64 +5515,36 @@ Broker debug flags (--brokerdebug):
5815
5515
  Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
5816
5516
  the selected broker commit with synthetic payload values derived from current price.
5817
5517
 
5818
- Simulator flags (--simulator):
5518
+ Sweep flags (--sweep):
5819
5519
 
5820
5520
  --symbol <string> Trading pair to simulate (default: BTCUSDT)
5821
5521
  --exchange <string> Exchange name (default: first registered)
5822
- --output <string> Output file base name (default: simulator_{SYMBOL}_{TIMESTAMP})
5823
- --json Save full ISimulatorResult to ./dump/<output>.json
5522
+ --output <string> Output file base name (default: sweep_{SYMBOL}_{TIMESTAMP})
5523
+ --json Save full ISweepResult to ./dump/<output>.json
5824
5524
  --markdown Save summary report to ./dump/<output>.md
5825
- --verbose Log every simulator lifecycle callback to the console
5525
+ --verbose Log every sweep lifecycle callback to the console
5826
5526
 
5827
5527
  Positionals: path to an ideas .jsonl file — one idea per line with the exact shape
5828
5528
  { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL",
5829
5529
  "author": string } — and an OPTIONAL config .json with the shape
5830
- { "gridAxes"?: ISimulatorGridAxes, "reportOrder"?: "sharpe"|"sortino"|"pnl"|"recovery" }.
5530
+ { "gridAxes"?: ISweepGridAxes, "reportOrder"?: "sharpe"|"sortino"|"pnl"|"recovery" }.
5831
5531
  Both files are validated BEFORE any work; a mismatch aborts the run with an error.
5832
5532
  No config -> an empty object is used and the engine defaults apply (the full
5833
5533
  default grid axes and reportOrder "sharpe" of the connection service).
5834
5534
 
5835
- A FEASIBILITY PROBE, not an out-of-sample shot (that is --tune): the grid from
5836
- the config (or the engine default) answers one question — does the feed contain
5837
- a profitable corridor at all. One candle pass per idea to the grid's longest
5838
- hold, flood dedupe (one idea per author per direction per 8h), default-ban
5839
- author filter graded inside each point's own hold window, time-based
5840
- Sharpe/Sortino, per-metric buckets with their own ranking winners and ban
5841
- dictionaries. Ideas of other symbols are filtered out — one shared feed serves
5842
- any --symbol.
5535
+ A GRID SWEEP: the grid from the config (or the engine default) is evaluated
5536
+ per point. One candle pass per idea to the grid's longest hold, flood dedupe
5537
+ (one idea per author per direction per 8h), EVERY author traded (no ban — the
5538
+ engine reports the raw per-author track graded inside each point's own hold
5539
+ window; who to trust is userspace), time-based Sharpe/Sortino, per-metric
5540
+ buckets with their own ranking winners and author tracks. Ideas of other
5541
+ symbols are filtered out — one shared feed serves any --symbol.
5843
5542
 
5844
5543
  No output flag → print the Markdown summary to stdout. With --verbose every
5845
- simulator lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
5544
+ sweep lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
5846
5545
  onGridPoint, onRanking, onDone) is logged to the console as it fires.
5847
5546
 
5848
- Module file ./modules/simulator.module is loaded automatically if it exists
5849
- (register your exchange there); without it CCXT Binance is used by default.
5850
-
5851
- Tune flags (--tune):
5852
-
5853
- --symbol <string> Trading pair to test (default: BTCUSDT)
5854
- --exchange <string> Exchange name (default: first registered)
5855
- --output <string> Output file base name (default: tune_{SYMBOL}_{TIMESTAMP})
5856
- --json Save the full ISimulatorTestResult to ./dump/<output>.json
5857
- --markdown Save the out-of-sample report to ./dump/<output>.md
5858
- --verbose Log simulator lifecycle callbacks to the console
5859
-
5860
- Positionals: path to an ideas .jsonl file (same shape and validation as
5861
- --simulator) and a config .json carrying the FROZEN training artifact:
5862
- { "point": ISimulatorGridPoint, "authorStats": [{ "author", "ideas", "hits" }],
5863
- "gridAxes"?, "reportOrder"? }. The point and authorStats are REQUIRED — without
5864
- them there is nothing to test and the run aborts with an error. gridAxes are
5865
- optional: by default they mirror the frozen point one value per axis (the grid
5866
- is inert for a test).
5867
-
5868
- ONE OUT-OF-SAMPLE SHOT, no training: the CLI never runs the sweep — pick your
5869
- candidate elsewhere (a Simulator.run of your own), freeze its point and raw
5870
- author track record into the config, and fire it once here via Simulator.test.
5871
- Bans are re-derived from the frozen numbers under the point's rule; authors
5872
- unseen in the config are banned by default. The report carries the result with
5873
- the trade list and the frozen track record with re-derived ban flags.
5874
-
5875
- Module file ./modules/tune.module is loaded automatically if it exists
5547
+ Module file ./modules/sweep.module is loaded automatically if it exists
5876
5548
  (register your exchange there); without it CCXT Binance is used by default.
5877
5549
 
5878
5550
  Flush flags (--flush):
@@ -5908,8 +5580,7 @@ Module hooks (loaded automatically by each mode):
5908
5580
  modules/dump.module --dump Exchange schema for candle dumps
5909
5581
  modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
5910
5582
  modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
5911
- modules/simulator.module --simulator Exchange schema for the crowd-ideas feasibility probe
5912
- modules/tune.module --tune Exchange schema for the walk-forward parameter search
5583
+ modules/sweep.module --sweep Exchange schema for the crowd-ideas feasibility probe
5913
5584
 
5914
5585
  --flush has no associated module. It only removes dump subdirectories.
5915
5586
 
@@ -5938,10 +5609,8 @@ Examples:
5938
5609
  node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
5939
5610
  node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
5940
5611
  node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
5941
- node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
5942
- node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_probe ./assets/tv-ideas.normalized.jsonl ./assets/probe.config.json
5943
- node ${ENTRY_PATH} --tune --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
5944
- node ${ENTRY_PATH} --tune --symbol BTCUSDT --markdown --output jun_2026_tune ./assets/tv-ideas.normalized.jsonl ./assets/tune.config.json
5612
+ node ${ENTRY_PATH} --sweep --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
5613
+ node ${ENTRY_PATH} --sweep --symbol BTCUSDT --json --output jun_2026_probe ./assets/tv-ideas.normalized.jsonl ./assets/probe.config.json
5945
5614
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
5946
5615
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
5947
5616
  node ${ENTRY_PATH} --init --output my-trading-bot
@@ -5955,7 +5624,7 @@ const main$1 = async () => {
5955
5624
  if (!values.help) {
5956
5625
  return;
5957
5626
  }
5958
- process.stdout.write(`@backtest-kit/cli ${"17.0.0"}\n\n`);
5627
+ process.stdout.write(`@backtest-kit/cli ${"17.7.0"}\n\n`);
5959
5628
  process.stdout.write(HELP_TEXT);
5960
5629
  process.exit(0);
5961
5630
  };
@@ -5969,7 +5638,7 @@ const main = async () => {
5969
5638
  if (!values.version) {
5970
5639
  return;
5971
5640
  }
5972
- process.stdout.write(`@backtest-kit/cli ${"17.0.0"}\n`);
5641
+ process.stdout.write(`@backtest-kit/cli ${"17.7.0"}\n`);
5973
5642
  process.exit(0);
5974
5643
  };
5975
5644
  main();