@tradejs/cli 3.1.27 → 3.1.28-beta.252

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.
@@ -41,6 +41,7 @@ var import_ai = require("@tradejs/infra/ai");
41
41
  // src/lib/backtest/checkpoint.ts
42
42
  var import_node_crypto = require("crypto");
43
43
  var import_constants = require("@tradejs/core/constants");
44
+ var import_backtest = require("@tradejs/core/backtest");
44
45
  var import_redis = require("@tradejs/infra/redis");
45
46
 
46
47
  // src/lib/runFormatting.ts
@@ -61,6 +61,7 @@ var import_node_path2 = __toESM(require("path"));
61
61
  var import_node_crypto = require("crypto");
62
62
  var import_node_fs = require("fs");
63
63
  var import_promises = __toESM(require("fs/promises"));
64
+ var import_backtest = require("@tradejs/core/backtest");
64
65
  var import_node_path = __toESM(require("path"));
65
66
 
66
67
  // src/lib/coreResearch/types.ts
@@ -189,6 +190,8 @@ var validateCoreResearchSpec = (value) => {
189
190
  "variant ids must be unique"
190
191
  );
191
192
  for (const variant of spec.variants) {
193
+ if (variant.resolvedConfig)
194
+ (0, import_backtest.assertStrategyExecutionIsolation)(variant.resolvedConfig);
192
195
  assert(
193
196
  RESEARCH_ID_RE.test(variant.id),
194
197
  `variant id ${variant.id} is invalid`
@@ -267,6 +270,7 @@ var validateCoreResearchSpec = (value) => {
267
270
  }
268
271
  }
269
272
  }
273
+ (0, import_backtest.parseBacktestExecutionCosts)(spec.execution?.costs);
270
274
  assertFinite(
271
275
  spec.execution?.maxLossValue,
272
276
  "execution.maxLossValue must be finite"
@@ -1804,6 +1808,7 @@ var buildCoreResearchHtml = (params) => {
1804
1808
  // src/lib/backtest/checkpoint.ts
1805
1809
  var import_node_crypto4 = require("crypto");
1806
1810
  var import_constants = require("@tradejs/core/constants");
1811
+ var import_backtest2 = require("@tradejs/core/backtest");
1807
1812
  var import_redis = require("@tradejs/infra/redis");
1808
1813
 
1809
1814
  // src/lib/runFormatting.ts
@@ -1826,6 +1831,7 @@ var loadBacktestRunManifest = async ({
1826
1831
  );
1827
1832
 
1828
1833
  // src/lib/coreResearch/reconciliation.ts
1834
+ var import_backtest3 = require("@tradejs/core/backtest");
1829
1835
  var finite = (value) => {
1830
1836
  const numeric = Number(value);
1831
1837
  return Number.isFinite(numeric) ? numeric : 0;
@@ -1890,6 +1896,25 @@ var reconcileCoreResearchVariant = async (params) => {
1890
1896
  };
1891
1897
  const pnlTolerance = completed.length * 5e-3 + 1e-9;
1892
1898
  const reasons = [];
1899
+ for (const envelope of completed) {
1900
+ const model = envelope.result.executionCostModel;
1901
+ try {
1902
+ if (!model) throw new Error("missing");
1903
+ if (sha256Json((0, import_backtest3.executionCostsFromModel)(model)) !== sha256Json(params.spec.execution.costs)) {
1904
+ reasons.push(
1905
+ "executed costs do not match the preregistered execution costs"
1906
+ );
1907
+ break;
1908
+ }
1909
+ if (model.fees.source !== "config" || model.slippage.source !== "config" || model.funding.enabled && model.funding.source !== "historical") {
1910
+ reasons.push("execution cost provenance is not explicit or historical");
1911
+ break;
1912
+ }
1913
+ } catch {
1914
+ reasons.push("executed execution cost model is missing or invalid");
1915
+ break;
1916
+ }
1917
+ }
1893
1918
  if (manifest.status !== "completed") {
1894
1919
  reasons.push(`manifest status is ${manifest.status}`);
1895
1920
  }
@@ -2539,8 +2564,16 @@ var createCoreResearchSpecTemplate = (params) => {
2539
2564
  connector: "ByBit",
2540
2565
  interval: "15",
2541
2566
  maxLossValue: 10,
2542
- feeRate: 1e-3,
2543
- slippageBps: 10,
2567
+ costs: {
2568
+ fees: { makerRate: 1e-3, takerRate: 1e-3 },
2569
+ slippage: {
2570
+ baseBps: 10,
2571
+ spreadMultiplier: 1,
2572
+ marketImpactBps: 0,
2573
+ delayRiskMultiplier: 0
2574
+ },
2575
+ funding: { enabled: false }
2576
+ },
2544
2577
  entryDelayBars: 1
2545
2578
  },
2546
2579
  variants: [
@@ -2653,6 +2686,8 @@ var validateCoreResearchRunCommand = (params) => {
2653
2686
  const { spec, variant } = params;
2654
2687
  const command = variant.command ?? [];
2655
2688
  const failures = [];
2689
+ if (commandHas(command, ["--executionCosts"]))
2690
+ failures.push("executionCosts is owned by the research spec");
2656
2691
  if (!commandHas(command, ["--ai", "-A"])) failures.push("--ai");
2657
2692
  if (!commandHas(command, ["--fast"])) failures.push("--fast");
2658
2693
  if (!commandHas(command, ["--cacheOnly", "-C"])) failures.push("--cacheOnly");
@@ -2704,7 +2739,11 @@ var runCoreResearch = async (params) => {
2704
2739
  );
2705
2740
  try {
2706
2741
  await runCommand({
2707
- command: variant.command,
2742
+ command: [
2743
+ ...variant.command,
2744
+ "--executionCosts",
2745
+ JSON.stringify(spec.execution.costs)
2746
+ ],
2708
2747
  cwd,
2709
2748
  logPath: backtestLogPath,
2710
2749
  env: {
@@ -47,6 +47,7 @@ var import_ai = require("@tradejs/infra/ai");
47
47
  // src/lib/backtest/checkpoint.ts
48
48
  var import_node_crypto = require("crypto");
49
49
  var import_constants = require("@tradejs/core/constants");
50
+ var import_backtest = require("@tradejs/core/backtest");
50
51
  var import_redis = require("@tradejs/infra/redis");
51
52
 
52
53
  // src/lib/runFormatting.ts
@@ -59,7 +59,7 @@ module.exports = __toCommonJS(backtest_exports);
59
59
  var import_fs2 = __toESM(require("fs"));
60
60
  var import_path2 = __toESM(require("path"));
61
61
  var import_chalk10 = __toESM(require("chalk"));
62
- var import_backtest2 = require("@tradejs/core/backtest");
62
+ var import_backtest3 = require("@tradejs/core/backtest");
63
63
  var import_grid = require("@tradejs/core/grid");
64
64
  var import_data = require("@tradejs/core/data");
65
65
  var import_redis5 = require("@tradejs/infra/redis");
@@ -69,7 +69,7 @@ var import_constants6 = require("@tradejs/core/constants");
69
69
  var import_fs = __toESM(require("fs"));
70
70
  var import_path = __toESM(require("path"));
71
71
  var import_chalk9 = __toESM(require("chalk"));
72
- var import_backtest = require("@tradejs/core/backtest");
72
+ var import_backtest2 = require("@tradejs/core/backtest");
73
73
  var import_constants5 = require("@tradejs/core/constants");
74
74
  var import_time3 = require("@tradejs/core/time");
75
75
  var import_backtestArtifacts = require("@tradejs/infra/backtestArtifacts");
@@ -4210,6 +4210,10 @@ import_args.default.example(
4210
4210
  "Run tests on uploaded data for 400 tickers"
4211
4211
  );
4212
4212
  import_args.default.option(["t", "tickers"], "Selected tickers");
4213
+ import_args.default.option(
4214
+ ["J", "executionCosts"],
4215
+ "Explicit execution costs JSON: fees, slippage and funding"
4216
+ );
4213
4217
  import_args.default.option(["e", "exclude"], "Exclude tickers from tests");
4214
4218
  import_args.default.option(["l", "tickersLimit"], "Tickers limit");
4215
4219
  import_args.default.option(["n", "tests"], "Tests limit", import_constants3.TESTS_LIMIT);
@@ -4344,7 +4348,18 @@ var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || pro
4344
4348
  // src/lib/backtest/checkpoint.ts
4345
4349
  var import_node_crypto = require("crypto");
4346
4350
  var import_constants4 = require("@tradejs/core/constants");
4351
+ var import_backtest = require("@tradejs/core/backtest");
4347
4352
  var import_redis3 = require("@tradejs/infra/redis");
4353
+ var assertFrozenBacktestExecutionCosts = (testSuite) => {
4354
+ for (const test of testSuite) {
4355
+ if (test.executionCosts == null) {
4356
+ throw new Error(
4357
+ "Cannot continue a run without frozen executionCosts; start a new run with --executionCosts"
4358
+ );
4359
+ }
4360
+ (0, import_backtest.parseBacktestExecutionCosts)(test.executionCosts);
4361
+ }
4362
+ };
4348
4363
  var toStableJson = (value) => {
4349
4364
  if (value == null || typeof value !== "object") {
4350
4365
  return JSON.stringify(value);
@@ -4366,6 +4381,8 @@ var buildBacktestTestKey = (test) => hashValue({
4366
4381
  researchTrace: Boolean(test.researchTrace),
4367
4382
  options: test.options,
4368
4383
  strategyConfig: test.strategyConfig,
4384
+ executionCosts: test.executionCosts,
4385
+ executionCostsCacheOnly: Boolean(test.executionCostsCacheOnly),
4369
4386
  strategyName: test.strategyName,
4370
4387
  symbol: test.symbol,
4371
4388
  userName: test.userName
@@ -4991,7 +5008,7 @@ var setTestData = async (test, stat, orderLog) => {
4991
5008
  }
4992
5009
  )
4993
5010
  ]);
4994
- const { testId } = (0, import_backtest.parseTestName)(test.name);
5011
+ const { testId } = (0, import_backtest2.parseTestName)(test.name);
4995
5012
  setPersistedTestSummary(`${test.strategyName}:${test.name}`, {
4996
5013
  value: test.name,
4997
5014
  label: `${test.symbol}_${testId}`,
@@ -5075,6 +5092,9 @@ var buildPreparedTestSuite = async ({
5075
5092
  isReplay
5076
5093
  }) => {
5077
5094
  const mlEnabled = Boolean(flags.ml);
5095
+ const executionCosts = flags.executionCosts == null ? void 0 : (0, import_backtest2.parseBacktestExecutionCosts)(JSON.parse(String(flags.executionCosts)));
5096
+ for (const test of testSuite)
5097
+ (0, import_backtest2.assertStrategyExecutionIsolation)(test.strategyConfig);
5078
5098
  const aiEnabled = Boolean(flags.ai);
5079
5099
  const requestedTestsLimit = resolveRequestedTestsLimit({
5080
5100
  isLiveMode: isReplay,
@@ -5084,6 +5104,8 @@ var buildPreparedTestSuite = async ({
5084
5104
  const preparedSuite = testSuite.map((test) => ({
5085
5105
  ...test,
5086
5106
  interval,
5107
+ executionCosts: executionCosts ?? test.executionCosts,
5108
+ executionCostsCacheOnly: Boolean(flags.cacheOnly),
5087
5109
  strategyConfig: {
5088
5110
  ...test.strategyConfig,
5089
5111
  ENV: "BACKTEST",
@@ -5091,8 +5113,7 @@ var buildPreparedTestSuite = async ({
5091
5113
  MAKE_ORDERS: true,
5092
5114
  CLOSE_OPPOSITE_POSITIONS: false,
5093
5115
  BACKTEST_PRICE_MODE: backtestPriceMode,
5094
- BACKTEST_ENTRY_DELAY_BARS: backtestEntryDelayBars,
5095
- ...flags.cacheOnly ? { EXECUTION_COSTS_CACHE_ONLY: true } : {}
5116
+ BACKTEST_ENTRY_DELAY_BARS: backtestEntryDelayBars
5096
5117
  },
5097
5118
  options: {
5098
5119
  start: window.start,
@@ -5294,6 +5315,8 @@ var toPersistedBacktestResultEntry = (result) => ({
5294
5315
  symbol: result.test.symbol,
5295
5316
  strategyName: result.test.strategyName,
5296
5317
  strategyConfig: result.test.strategyConfig,
5318
+ executionCosts: result.test.executionCosts,
5319
+ executionCostsCacheOnly: result.test.executionCostsCacheOnly,
5297
5320
  connectorName: result.test.connectorName,
5298
5321
  universe: result.test.universe,
5299
5322
  assetClass: result.test.assetClass,
@@ -5322,7 +5345,7 @@ var resolveRenderableStat = async (result) => {
5322
5345
  hasArtifacts: false
5323
5346
  };
5324
5347
  }
5325
- const stat = (0, import_backtest2.calculateStatsFull)(positionLog);
5348
+ const stat = (0, import_backtest3.calculateStatsFull)(positionLog);
5326
5349
  if (!stat && fallbackStat.orders > 0) {
5327
5350
  throw new Error(
5328
5351
  `Position log is empty for ${name} despite ${fallbackStat.orders} closed orders`
@@ -5592,6 +5615,11 @@ var loadBacktestRunForContinue = async () => {
5592
5615
  if (!flags.continue) {
5593
5616
  return null;
5594
5617
  }
5618
+ if (flags.executionCosts != null) {
5619
+ throw new Error(
5620
+ "A continued run uses its frozen executionCosts; start a new run to change costs"
5621
+ );
5622
+ }
5595
5623
  const runId = await resolveBacktestRunIdForContinue({
5596
5624
  config: flags.config,
5597
5625
  requestedRunId: typeof flags.runId === "string" ? flags.runId : void 0,
@@ -5606,6 +5634,7 @@ var loadBacktestRunForContinue = async () => {
5606
5634
  if (!manifest) {
5607
5635
  throw new Error(`Backtest run "${runId}" was not found`);
5608
5636
  }
5637
+ assertFrozenBacktestExecutionCosts(manifest.testSuite);
5609
5638
  if (manifest.userName !== userName) {
5610
5639
  throw new Error(
5611
5640
  `Backtest run "${runId}" belongs to user "${manifest.userName}", not "${userName}"`
@@ -41,6 +41,7 @@ var import_chalk = __toESM(require("chalk"));
41
41
  var import_node_crypto = require("crypto");
42
42
  var import_node_fs = require("fs");
43
43
  var import_promises = __toESM(require("fs/promises"));
44
+ var import_backtest = require("@tradejs/core/backtest");
44
45
  var import_node_path = __toESM(require("path"));
45
46
 
46
47
  // src/lib/coreResearch/types.ts
@@ -169,6 +170,8 @@ var validateCoreResearchSpec = (value) => {
169
170
  "variant ids must be unique"
170
171
  );
171
172
  for (const variant of spec.variants) {
173
+ if (variant.resolvedConfig)
174
+ (0, import_backtest.assertStrategyExecutionIsolation)(variant.resolvedConfig);
172
175
  assert(
173
176
  RESEARCH_ID_RE.test(variant.id),
174
177
  `variant id ${variant.id} is invalid`
@@ -247,6 +250,7 @@ var validateCoreResearchSpec = (value) => {
247
250
  }
248
251
  }
249
252
  }
253
+ (0, import_backtest.parseBacktestExecutionCosts)(spec.execution?.costs);
250
254
  assertFinite(
251
255
  spec.execution?.maxLossValue,
252
256
  "execution.maxLossValue must be finite"
@@ -1795,6 +1799,7 @@ var buildCoreResearchHtml = (params) => {
1795
1799
  // src/lib/backtest/checkpoint.ts
1796
1800
  var import_node_crypto4 = require("crypto");
1797
1801
  var import_constants = require("@tradejs/core/constants");
1802
+ var import_backtest2 = require("@tradejs/core/backtest");
1798
1803
  var import_redis = require("@tradejs/infra/redis");
1799
1804
 
1800
1805
  // src/lib/runFormatting.ts
@@ -1817,6 +1822,7 @@ var loadBacktestRunManifest = async ({
1817
1822
  );
1818
1823
 
1819
1824
  // src/lib/coreResearch/reconciliation.ts
1825
+ var import_backtest3 = require("@tradejs/core/backtest");
1820
1826
  var finite = (value) => {
1821
1827
  const numeric = Number(value);
1822
1828
  return Number.isFinite(numeric) ? numeric : 0;
@@ -1881,6 +1887,25 @@ var reconcileCoreResearchVariant = async (params) => {
1881
1887
  };
1882
1888
  const pnlTolerance = completed.length * 5e-3 + 1e-9;
1883
1889
  const reasons = [];
1890
+ for (const envelope of completed) {
1891
+ const model = envelope.result.executionCostModel;
1892
+ try {
1893
+ if (!model) throw new Error("missing");
1894
+ if (sha256Json((0, import_backtest3.executionCostsFromModel)(model)) !== sha256Json(params.spec.execution.costs)) {
1895
+ reasons.push(
1896
+ "executed costs do not match the preregistered execution costs"
1897
+ );
1898
+ break;
1899
+ }
1900
+ if (model.fees.source !== "config" || model.slippage.source !== "config" || model.funding.enabled && model.funding.source !== "historical") {
1901
+ reasons.push("execution cost provenance is not explicit or historical");
1902
+ break;
1903
+ }
1904
+ } catch {
1905
+ reasons.push("executed execution cost model is missing or invalid");
1906
+ break;
1907
+ }
1908
+ }
1884
1909
  if (manifest.status !== "completed") {
1885
1910
  reasons.push(`manifest status is ${manifest.status}`);
1886
1911
  }
@@ -2558,8 +2583,16 @@ var createCoreResearchSpecTemplate = (params) => {
2558
2583
  connector: "ByBit",
2559
2584
  interval: "15",
2560
2585
  maxLossValue: 10,
2561
- feeRate: 1e-3,
2562
- slippageBps: 10,
2586
+ costs: {
2587
+ fees: { makerRate: 1e-3, takerRate: 1e-3 },
2588
+ slippage: {
2589
+ baseBps: 10,
2590
+ spreadMultiplier: 1,
2591
+ marketImpactBps: 0,
2592
+ delayRiskMultiplier: 0
2593
+ },
2594
+ funding: { enabled: false }
2595
+ },
2563
2596
  entryDelayBars: 1
2564
2597
  },
2565
2598
  variants: [
@@ -2672,6 +2705,8 @@ var validateCoreResearchRunCommand = (params) => {
2672
2705
  const { spec, variant } = params;
2673
2706
  const command = variant.command ?? [];
2674
2707
  const failures = [];
2708
+ if (commandHas(command, ["--executionCosts"]))
2709
+ failures.push("executionCosts is owned by the research spec");
2675
2710
  if (!commandHas(command, ["--ai", "-A"])) failures.push("--ai");
2676
2711
  if (!commandHas(command, ["--fast"])) failures.push("--fast");
2677
2712
  if (!commandHas(command, ["--cacheOnly", "-C"])) failures.push("--cacheOnly");
@@ -2723,7 +2758,11 @@ var runCoreResearch = async (params) => {
2723
2758
  );
2724
2759
  try {
2725
2760
  await runCommand({
2726
- command: variant.command,
2761
+ command: [
2762
+ ...variant.command,
2763
+ "--executionCosts",
2764
+ JSON.stringify(spec.execution.costs)
2765
+ ],
2727
2766
  cwd,
2728
2767
  logPath: backtestLogPath,
2729
2768
  env: {
@@ -42,6 +42,7 @@ var import_ml = require("@tradejs/infra/ml");
42
42
  // src/lib/backtest/checkpoint.ts
43
43
  var import_node_crypto = require("crypto");
44
44
  var import_constants = require("@tradejs/core/constants");
45
+ var import_backtest = require("@tradejs/core/backtest");
45
46
  var import_redis = require("@tradejs/infra/redis");
46
47
 
47
48
  // src/lib/runFormatting.ts