@tradejs/cli 2.0.15 → 2.0.17

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.
@@ -2594,6 +2594,20 @@ var DERIVATIVES_CONTEXT_DATA_MODEL = {
2594
2594
  hyperliquidWhaleMinCoveragePct: 0.8,
2595
2595
  hyperliquidWhaleDataModelVersion: import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2596
2596
  };
2597
+ var writeAiTrainResearchSnapshot = async ({
2598
+ outputPath,
2599
+ result
2600
+ }) => {
2601
+ const resolvedPath = import_node_path.default.resolve(outputPath);
2602
+ await import_promises.default.mkdir(import_node_path.default.dirname(resolvedPath), { recursive: true });
2603
+ await import_promises.default.writeFile(
2604
+ resolvedPath,
2605
+ `${JSON.stringify(result, null, 2)}
2606
+ `,
2607
+ "utf8"
2608
+ );
2609
+ return resolvedPath;
2610
+ };
2597
2611
  var normalizeForStableJson = (value) => {
2598
2612
  if (Array.isArray(value)) {
2599
2613
  return value.map(normalizeForStableJson);
@@ -2708,6 +2722,7 @@ var resolveGateFingerprint = async (projectRoot, strategyName, gitSha) => {
2708
2722
  `packages/strategies/src/${strategyName}/guardrails.ts`,
2709
2723
  `packages/strategies/src/${strategyName}/pockets.ts`,
2710
2724
  `packages/strategies/src/${strategyName}/config.ts`,
2725
+ "packages/strategies/src/shared/localAiGate.ts",
2711
2726
  "packages/node/src/ai.ts"
2712
2727
  ];
2713
2728
  const optionalSourceEntries = await Promise.all(
@@ -3000,6 +3015,11 @@ import_args.default.option(
3000
3015
  false
3001
3016
  );
3002
3017
  import_args.default.option(["j", "json"], "Print structured JSON summary", false);
3018
+ import_args.default.option(
3019
+ ["O", "output"],
3020
+ "Write the structured JSON summary to a file and suppress table output",
3021
+ ""
3022
+ );
3003
3023
  import_args.default.option(
3004
3024
  ["S", "since"],
3005
3025
  "Only evaluate rows at or after this timestamp (ISO date or epoch ms)",
@@ -3358,7 +3378,9 @@ var main = async () => {
3358
3378
  const minQuality = normalizeInt(flags.minQuality, 4);
3359
3379
  const localOnly = Boolean(flags.localOnly);
3360
3380
  const saveChart = Boolean(flags.chart);
3361
- const jsonOutput = Boolean(flags.json);
3381
+ const printJsonOutput = Boolean(flags.json);
3382
+ const jsonOutputPath = String(flags.output || "").trim();
3383
+ const jsonOutput = printJsonOutput || Boolean(jsonOutputPath);
3362
3384
  const sinceInput = parseTimestampFilter(flags.since);
3363
3385
  const untilInput = parseTimestampFilter(flags.until);
3364
3386
  const trailingPeriodMs = parseTrailingPeriodMs(flags.period);
@@ -3638,6 +3660,12 @@ var main = async () => {
3638
3660
  providerErrors: errorMessages
3639
3661
  }
3640
3662
  };
3663
+ if (jsonOutputPath) {
3664
+ await writeAiTrainResearchSnapshot({
3665
+ outputPath: jsonOutputPath,
3666
+ result
3667
+ });
3668
+ }
3641
3669
  if (dumpEvaluationsPath) {
3642
3670
  await import_promises2.default.mkdir(import_path.default.dirname(import_path.default.resolve(dumpEvaluationsPath)), {
3643
3671
  recursive: true
@@ -3681,7 +3709,9 @@ var main = async () => {
3681
3709
  });
3682
3710
  }
3683
3711
  if (jsonOutput) {
3684
- console.log(JSON.stringify(result));
3712
+ if (printJsonOutput) {
3713
+ console.log(JSON.stringify(result));
3714
+ }
3685
3715
  process.exit(failed === selectedRows ? 1 : 0);
3686
3716
  }
3687
3717
  console.log("");
@@ -1705,6 +1705,8 @@ var timeOperation = async (label, operation, log = console.log) => {
1705
1705
 
1706
1706
  // src/lib/marketContextPrepare.ts
1707
1707
  var import_chalk4 = __toESM(require("chalk"));
1708
+ var import_timescale5 = require("@tradejs/infra/timescale");
1709
+ var import_registry = require("@tradejs/node/registry");
1708
1710
 
1709
1711
  // src/lib/binanceMarketContextBackfill.ts
1710
1712
  var import_progress = __toESM(require("progress"));
@@ -4540,6 +4542,7 @@ var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4540
4542
  var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4541
4543
 
4542
4544
  // src/lib/hyperliquidWhaleBackfill.ts
4545
+ var import_progress3 = __toESM(require("progress"));
4543
4546
  var import_timescale4 = require("@tradejs/infra/timescale");
4544
4547
  var import_strategies2 = require("@tradejs/node/strategies");
4545
4548
 
@@ -4914,11 +4917,36 @@ var backfillHyperliquidWhaleContext = async (params) => {
4914
4917
  toMs,
4915
4918
  ...identity
4916
4919
  });
4920
+ const totalCoverageBuckets = Math.ceil(
4921
+ (toMs - fromMs) / HYPERLIQUID_WHALE_BUCKET_MS
4922
+ );
4923
+ const coverageBar = new import_progress3.default(
4924
+ "Hyperliquid coverage :current/:total [:bar][:percent] :eta(s) rows=:rows chunk=:chunk",
4925
+ {
4926
+ total: Math.max(1, totalCoverageBuckets),
4927
+ width: 24
4928
+ }
4929
+ );
4930
+ let displayedCoverageBuckets = 0;
4917
4931
  const coverageBuckets = await (0, import_timescale4.rebuildHyperliquidWhaleCoverageRows)({
4918
4932
  fromMs,
4919
4933
  toMs,
4920
4934
  expectedWhales: whales.addresses.length,
4921
- ...identity
4935
+ ...identity,
4936
+ onProgress: (progress) => {
4937
+ const next = Math.max(
4938
+ displayedCoverageBuckets,
4939
+ Math.min(progress.completedBuckets, coverageBar.total)
4940
+ );
4941
+ const delta = next - displayedCoverageBuckets;
4942
+ if (delta > 0) {
4943
+ coverageBar.tick(delta, {
4944
+ rows: progress.rows,
4945
+ chunk: `${progress.chunkIndex}/${progress.totalChunks}`
4946
+ });
4947
+ displayedCoverageBuckets = next;
4948
+ }
4949
+ }
4922
4950
  });
4923
4951
  const result = {
4924
4952
  cached: false,
@@ -4946,6 +4974,9 @@ var backfillHyperliquidWhaleContext = async (params) => {
4946
4974
  };
4947
4975
 
4948
4976
  // src/lib/marketContextPrepare.ts
4977
+ var uniqueReasons = (reasons) => [
4978
+ ...new Set(reasons)
4979
+ ];
4949
4980
  var shouldPrepareDerivativesContextForRun = (params) => {
4950
4981
  if (params.universe === "tradfi") return false;
4951
4982
  if (params.mode === "signals") {
@@ -4953,6 +4984,9 @@ var shouldPrepareDerivativesContextForRun = (params) => {
4953
4984
  cacheOnly: params.cacheOnly
4954
4985
  });
4955
4986
  }
4987
+ if (params.mode === "backtest" && !params.aiEnabled && !params.mlEnabled) {
4988
+ return false;
4989
+ }
4956
4990
  return shouldBackfillDerivativesContextForBacktest({
4957
4991
  aiEnabled: Boolean(params.aiEnabled),
4958
4992
  cacheOnly: params.cacheOnly,
@@ -4962,6 +4996,7 @@ var shouldPrepareDerivativesContextForRun = (params) => {
4962
4996
  var shouldPrepareBinanceMarketContextForRun = (params) => {
4963
4997
  if (params.universe === "tradfi") return false;
4964
4998
  if (params.mode === "backtest") {
4999
+ if (!params.aiEnabled && !params.mlEnabled) return false;
4965
5000
  return shouldBackfillBinanceMarketContextForBacktest({
4966
5001
  aiEnabled: Boolean(params.aiEnabled),
4967
5002
  cacheOnly: params.cacheOnly,
@@ -4980,6 +5015,7 @@ var shouldPrepareBinanceMarketContextForRun = (params) => {
4980
5015
  var shouldPrepareCoinMarketCapContextForRun = (params) => {
4981
5016
  if (params.universe === "tradfi") return false;
4982
5017
  if (params.mode === "backtest") {
5018
+ if (!params.aiEnabled && !params.mlEnabled) return false;
4983
5019
  return shouldBackfillCoinMarketCapContextForBacktest({
4984
5020
  aiEnabled: Boolean(params.aiEnabled),
4985
5021
  cacheOnly: params.cacheOnly,
@@ -4995,10 +5031,8 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
4995
5031
  cacheOnly: params.cacheOnly
4996
5032
  });
4997
5033
  };
4998
- var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
4999
- const strategyRequiresContext = params.strategyNames?.includes(
5000
- "HyperliquidConsensus"
5001
- );
5034
+ var shouldPrepareHyperliquidWhaleContextForRun = (params, coreContextSources = []) => {
5035
+ const strategyRequiresContext = coreContextSources.includes("hyperliquidWhales");
5002
5036
  if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
5003
5037
  return false;
5004
5038
  }
@@ -5009,6 +5043,64 @@ var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5009
5043
  if (strategyRequiresContext) return true;
5010
5044
  return ["1", "true", "yes", "on"].includes(configured);
5011
5045
  };
5046
+ var resolveCoreContextSources = async (params) => {
5047
+ const strategyNames = [...new Set(params.strategyNames ?? [])];
5048
+ if (!strategyNames.length) return [];
5049
+ await (0, import_registry.ensureStrategyPluginsLoaded)(params.projectRoot);
5050
+ return [
5051
+ ...new Set(
5052
+ strategyNames.flatMap(
5053
+ (strategyName) => (0, import_registry.getStrategyManifest)(strategyName, params.projectRoot)?.contextRequirements?.core ?? []
5054
+ )
5055
+ )
5056
+ ];
5057
+ };
5058
+ var resolveMarketContextRunRequirements = async (params) => {
5059
+ const coreContextSources = await resolveCoreContextSources(params);
5060
+ const paramsWithCoreDemand = (source) => coreContextSources.includes(source) ? { ...params, aiEnabled: true } : params;
5061
+ const isCrypto = params.universe !== "tradfi";
5062
+ const capturesPayload = Boolean(params.aiEnabled || params.mlEnabled);
5063
+ const runtimeReadsContext = params.mode !== "backtest";
5064
+ const reasonsFor = (source) => uniqueReasons([
5065
+ ...coreContextSources.includes(source) ? ["core"] : [],
5066
+ ...params.aiEnabled ? ["ai"] : [],
5067
+ ...params.mlEnabled ? ["ml"] : [],
5068
+ ...runtimeReadsContext ? ["runtime"] : []
5069
+ ]);
5070
+ const readsStandardContext = isCrypto && (capturesPayload || runtimeReadsContext);
5071
+ const readsHyperliquid = isCrypto && (readsStandardContext || coreContextSources.includes("hyperliquidWhales"));
5072
+ return {
5073
+ binance: {
5074
+ read: readsStandardContext || coreContextSources.includes("binance"),
5075
+ backfill: shouldPrepareBinanceMarketContextForRun(
5076
+ paramsWithCoreDemand("binance")
5077
+ ),
5078
+ requiredBy: reasonsFor("binance")
5079
+ },
5080
+ coinmarketcap: {
5081
+ read: readsStandardContext || coreContextSources.includes("coinmarketcap"),
5082
+ backfill: shouldPrepareCoinMarketCapContextForRun(
5083
+ paramsWithCoreDemand("coinmarketcap")
5084
+ ),
5085
+ requiredBy: reasonsFor("coinmarketcap")
5086
+ },
5087
+ derivatives: {
5088
+ read: readsStandardContext || coreContextSources.includes("derivatives"),
5089
+ backfill: shouldPrepareDerivativesContextForRun(
5090
+ paramsWithCoreDemand("derivatives")
5091
+ ),
5092
+ requiredBy: reasonsFor("derivatives")
5093
+ },
5094
+ hyperliquidWhales: {
5095
+ read: readsHyperliquid,
5096
+ backfill: shouldPrepareHyperliquidWhaleContextForRun(
5097
+ params,
5098
+ coreContextSources
5099
+ ),
5100
+ requiredBy: reasonsFor("hyperliquidWhales")
5101
+ }
5102
+ };
5103
+ };
5012
5104
  var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
5013
5105
  var buildCoinMarketCapBackfillParams = (params) => ({
5014
5106
  userName: params.userName,
@@ -5037,7 +5129,15 @@ var prepareMarketContextForRun = async (params) => {
5037
5129
  console.log(import_chalk4.default.gray(message));
5038
5130
  });
5039
5131
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
5040
- if (shouldPrepareDerivativesContextForRun(params)) {
5132
+ const requirements = await resolveMarketContextRunRequirements(params);
5133
+ const readableSources = Object.entries(requirements).filter(([, requirement]) => requirement.read).map(([source]) => source);
5134
+ if (readableSources.length) {
5135
+ await timeOperation3(
5136
+ "market context schema ensure",
5137
+ () => (0, import_timescale5.ensureMarketContextSchemas)(readableSources)
5138
+ );
5139
+ }
5140
+ if (requirements.derivatives.backfill) {
5041
5141
  await timeOperation3(
5042
5142
  "derivatives context backfill",
5043
5143
  () => (params.mode === "signals" ? backfillDerivativesContextForSignals : backfillDerivativesContextForBacktest)(
@@ -5045,7 +5145,7 @@ var prepareMarketContextForRun = async (params) => {
5045
5145
  )
5046
5146
  );
5047
5147
  }
5048
- if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
5148
+ if (requirements.hyperliquidWhales.backfill) {
5049
5149
  await timeOperation3(
5050
5150
  "hyperliquid whale context backfill",
5051
5151
  () => backfillHyperliquidWhaleContext({
@@ -5057,13 +5157,13 @@ var prepareMarketContextForRun = async (params) => {
5057
5157
  })
5058
5158
  );
5059
5159
  }
5060
- if (shouldPrepareBinanceMarketContextForRun(params)) {
5160
+ if (requirements.binance.backfill) {
5061
5161
  await timeOperation3("binance market context backfill", () => {
5062
5162
  const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
5063
5163
  return backfill(buildBinanceMarketBackfillParams(params));
5064
5164
  });
5065
5165
  }
5066
- if (shouldPrepareCoinMarketCapContextForRun(params)) {
5166
+ if (requirements.coinmarketcap.backfill) {
5067
5167
  await timeOperation3(
5068
5168
  "coinmarketcap historical context backfill",
5069
5169
  () => resolveCoinMarketCapBackfillForMode(params.mode)(
@@ -5071,6 +5171,7 @@ var prepareMarketContextForRun = async (params) => {
5071
5171
  )
5072
5172
  );
5073
5173
  }
5174
+ return requirements;
5074
5175
  };
5075
5176
 
5076
5177
  // src/lib/runEnvironment.ts
@@ -6198,10 +6299,27 @@ var resetRunState = () => {
6198
6299
  };
6199
6300
 
6200
6301
  // src/lib/backtest/workerPool.ts
6201
- var import_progress3 = __toESM(require("progress"));
6302
+ var import_progress4 = __toESM(require("progress"));
6202
6303
  var import_child_process = require("child_process");
6203
6304
  var import_chalk8 = __toESM(require("chalk"));
6204
6305
  var import_node_crypto2 = require("crypto");
6306
+ var DEFAULT_PG_POOL_MAX = 10;
6307
+ var FAST_AI_PG_CONNECTION_BUDGET = 18;
6308
+ var MIN_FAST_AI_WORKER_PG_POOL_MAX = 3;
6309
+ var resolveFastAiWorkerPgPoolMax = (testSuite, workerCount, configuredPoolMax) => {
6310
+ if (configuredPoolMax?.trim() || workerCount <= 1 || testSuite.length === 0 || testSuite.some((test) => !test.fast || !test.ai)) {
6311
+ return void 0;
6312
+ }
6313
+ return String(
6314
+ Math.min(
6315
+ DEFAULT_PG_POOL_MAX,
6316
+ Math.max(
6317
+ MIN_FAST_AI_WORKER_PG_POOL_MAX,
6318
+ Math.floor(FAST_AI_PG_CONNECTION_BUDGET / workerCount)
6319
+ )
6320
+ )
6321
+ );
6322
+ };
6205
6323
  var executeBacktestWorkerPool = async ({
6206
6324
  testSuite,
6207
6325
  userName: userName2,
@@ -6220,6 +6338,12 @@ var executeBacktestWorkerPool = async ({
6220
6338
  totalTests
6221
6339
  }) => {
6222
6340
  const chunks = chunkTestSuite(testSuite);
6341
+ const fastAiWorkerPgPoolMax = resolveFastAiWorkerPgPoolMax(
6342
+ testSuite,
6343
+ chunks.length,
6344
+ process.env.PG_POOL_MAX
6345
+ );
6346
+ const workerEnv = fastAiWorkerPgPoolMax ? { ...process.env, PG_POOL_MAX: fastAiWorkerPgPoolMax } : void 0;
6223
6347
  const totalExpectedTests = Math.max(
6224
6348
  testSuite.length,
6225
6349
  totalTests ?? testSuite.length,
@@ -6277,7 +6401,7 @@ var executeBacktestWorkerPool = async ({
6277
6401
  console.log(line);
6278
6402
  }
6279
6403
  console.log("");
6280
- const bar = new import_progress3.default(
6404
+ const bar = new import_progress4.default(
6281
6405
  ":current/:total [:bar][:percent] avg :amount win :winRate trades :trades :eta(s)",
6282
6406
  {
6283
6407
  total: totalExpectedTests,
@@ -6305,6 +6429,7 @@ var executeBacktestWorkerPool = async ({
6305
6429
  const chunkId = runId ? `${runId}-${attemptId}` : attemptId;
6306
6430
  const chunkWithId = chunk.map((test) => ({ ...test, chunkId }));
6307
6431
  const tester = (0, import_child_process.fork)(testerWorkerPath, [], {
6432
+ ...workerEnv ? { env: workerEnv } : {},
6308
6433
  execArgv: testerNeedsTsRuntime ? [
6309
6434
  `--max-old-space-size=${workerHeapMb2}`,
6310
6435
  "-r",
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(hyperliquidWhaleBackfill_exports);
36
36
  var import_args = __toESM(require("args"));
37
37
 
38
38
  // src/lib/hyperliquidWhaleBackfill.ts
39
+ var import_progress = __toESM(require("progress"));
39
40
  var import_timescale = require("@tradejs/infra/timescale");
40
41
  var import_strategies = require("@tradejs/node/strategies");
41
42
 
@@ -410,11 +411,36 @@ var backfillHyperliquidWhaleContext = async (params) => {
410
411
  toMs,
411
412
  ...identity
412
413
  });
414
+ const totalCoverageBuckets = Math.ceil(
415
+ (toMs - fromMs) / HYPERLIQUID_WHALE_BUCKET_MS
416
+ );
417
+ const coverageBar = new import_progress.default(
418
+ "Hyperliquid coverage :current/:total [:bar][:percent] :eta(s) rows=:rows chunk=:chunk",
419
+ {
420
+ total: Math.max(1, totalCoverageBuckets),
421
+ width: 24
422
+ }
423
+ );
424
+ let displayedCoverageBuckets = 0;
413
425
  const coverageBuckets = await (0, import_timescale.rebuildHyperliquidWhaleCoverageRows)({
414
426
  fromMs,
415
427
  toMs,
416
428
  expectedWhales: whales.addresses.length,
417
- ...identity
429
+ ...identity,
430
+ onProgress: (progress) => {
431
+ const next = Math.max(
432
+ displayedCoverageBuckets,
433
+ Math.min(progress.completedBuckets, coverageBar.total)
434
+ );
435
+ const delta = next - displayedCoverageBuckets;
436
+ if (delta > 0) {
437
+ coverageBar.tick(delta, {
438
+ rows: progress.rows,
439
+ chunk: `${progress.chunkIndex}/${progress.totalChunks}`
440
+ });
441
+ displayedCoverageBuckets = next;
442
+ }
443
+ }
418
444
  });
419
445
  const result = {
420
446
  cached: false,
@@ -39,6 +39,7 @@ var import_node_crypto = require("crypto");
39
39
  var import_strategies2 = require("@tradejs/node/strategies");
40
40
 
41
41
  // src/lib/hyperliquidWhaleBackfill.ts
42
+ var import_progress = __toESM(require("progress"));
42
43
  var import_timescale = require("@tradejs/infra/timescale");
43
44
  var import_strategies = require("@tradejs/node/strategies");
44
45
 
@@ -2346,6 +2346,8 @@ var prepareRunEnvironment = async ({
2346
2346
 
2347
2347
  // src/lib/marketContextPrepare.ts
2348
2348
  var import_chalk7 = __toESM(require("chalk"));
2349
+ var import_timescale5 = require("@tradejs/infra/timescale");
2350
+ var import_registry = require("@tradejs/node/registry");
2349
2351
 
2350
2352
  // src/lib/binanceMarketContextBackfill.ts
2351
2353
  var import_progress = __toESM(require("progress"));
@@ -5181,6 +5183,7 @@ var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
5181
5183
  var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
5182
5184
 
5183
5185
  // src/lib/hyperliquidWhaleBackfill.ts
5186
+ var import_progress3 = __toESM(require("progress"));
5184
5187
  var import_timescale4 = require("@tradejs/infra/timescale");
5185
5188
  var import_strategies2 = require("@tradejs/node/strategies");
5186
5189
 
@@ -5555,11 +5558,36 @@ var backfillHyperliquidWhaleContext = async (params) => {
5555
5558
  toMs,
5556
5559
  ...identity
5557
5560
  });
5561
+ const totalCoverageBuckets = Math.ceil(
5562
+ (toMs - fromMs) / HYPERLIQUID_WHALE_BUCKET_MS
5563
+ );
5564
+ const coverageBar = new import_progress3.default(
5565
+ "Hyperliquid coverage :current/:total [:bar][:percent] :eta(s) rows=:rows chunk=:chunk",
5566
+ {
5567
+ total: Math.max(1, totalCoverageBuckets),
5568
+ width: 24
5569
+ }
5570
+ );
5571
+ let displayedCoverageBuckets = 0;
5558
5572
  const coverageBuckets = await (0, import_timescale4.rebuildHyperliquidWhaleCoverageRows)({
5559
5573
  fromMs,
5560
5574
  toMs,
5561
5575
  expectedWhales: whales.addresses.length,
5562
- ...identity
5576
+ ...identity,
5577
+ onProgress: (progress) => {
5578
+ const next = Math.max(
5579
+ displayedCoverageBuckets,
5580
+ Math.min(progress.completedBuckets, coverageBar.total)
5581
+ );
5582
+ const delta = next - displayedCoverageBuckets;
5583
+ if (delta > 0) {
5584
+ coverageBar.tick(delta, {
5585
+ rows: progress.rows,
5586
+ chunk: `${progress.chunkIndex}/${progress.totalChunks}`
5587
+ });
5588
+ displayedCoverageBuckets = next;
5589
+ }
5590
+ }
5563
5591
  });
5564
5592
  const result = {
5565
5593
  cached: false,
@@ -5587,6 +5615,9 @@ var backfillHyperliquidWhaleContext = async (params) => {
5587
5615
  };
5588
5616
 
5589
5617
  // src/lib/marketContextPrepare.ts
5618
+ var uniqueReasons = (reasons) => [
5619
+ ...new Set(reasons)
5620
+ ];
5590
5621
  var shouldPrepareDerivativesContextForRun = (params) => {
5591
5622
  if (params.universe === "tradfi") return false;
5592
5623
  if (params.mode === "signals") {
@@ -5594,6 +5625,9 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5594
5625
  cacheOnly: params.cacheOnly
5595
5626
  });
5596
5627
  }
5628
+ if (params.mode === "backtest" && !params.aiEnabled && !params.mlEnabled) {
5629
+ return false;
5630
+ }
5597
5631
  return shouldBackfillDerivativesContextForBacktest({
5598
5632
  aiEnabled: Boolean(params.aiEnabled),
5599
5633
  cacheOnly: params.cacheOnly,
@@ -5603,6 +5637,7 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5603
5637
  var shouldPrepareBinanceMarketContextForRun = (params) => {
5604
5638
  if (params.universe === "tradfi") return false;
5605
5639
  if (params.mode === "backtest") {
5640
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5606
5641
  return shouldBackfillBinanceMarketContextForBacktest({
5607
5642
  aiEnabled: Boolean(params.aiEnabled),
5608
5643
  cacheOnly: params.cacheOnly,
@@ -5621,6 +5656,7 @@ var shouldPrepareBinanceMarketContextForRun = (params) => {
5621
5656
  var shouldPrepareCoinMarketCapContextForRun = (params) => {
5622
5657
  if (params.universe === "tradfi") return false;
5623
5658
  if (params.mode === "backtest") {
5659
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5624
5660
  return shouldBackfillCoinMarketCapContextForBacktest({
5625
5661
  aiEnabled: Boolean(params.aiEnabled),
5626
5662
  cacheOnly: params.cacheOnly,
@@ -5636,10 +5672,8 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
5636
5672
  cacheOnly: params.cacheOnly
5637
5673
  });
5638
5674
  };
5639
- var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5640
- const strategyRequiresContext = params.strategyNames?.includes(
5641
- "HyperliquidConsensus"
5642
- );
5675
+ var shouldPrepareHyperliquidWhaleContextForRun = (params, coreContextSources = []) => {
5676
+ const strategyRequiresContext = coreContextSources.includes("hyperliquidWhales");
5643
5677
  if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
5644
5678
  return false;
5645
5679
  }
@@ -5650,6 +5684,64 @@ var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5650
5684
  if (strategyRequiresContext) return true;
5651
5685
  return ["1", "true", "yes", "on"].includes(configured);
5652
5686
  };
5687
+ var resolveCoreContextSources = async (params) => {
5688
+ const strategyNames = [...new Set(params.strategyNames ?? [])];
5689
+ if (!strategyNames.length) return [];
5690
+ await (0, import_registry.ensureStrategyPluginsLoaded)(params.projectRoot);
5691
+ return [
5692
+ ...new Set(
5693
+ strategyNames.flatMap(
5694
+ (strategyName) => (0, import_registry.getStrategyManifest)(strategyName, params.projectRoot)?.contextRequirements?.core ?? []
5695
+ )
5696
+ )
5697
+ ];
5698
+ };
5699
+ var resolveMarketContextRunRequirements = async (params) => {
5700
+ const coreContextSources = await resolveCoreContextSources(params);
5701
+ const paramsWithCoreDemand = (source) => coreContextSources.includes(source) ? { ...params, aiEnabled: true } : params;
5702
+ const isCrypto = params.universe !== "tradfi";
5703
+ const capturesPayload = Boolean(params.aiEnabled || params.mlEnabled);
5704
+ const runtimeReadsContext = params.mode !== "backtest";
5705
+ const reasonsFor = (source) => uniqueReasons([
5706
+ ...coreContextSources.includes(source) ? ["core"] : [],
5707
+ ...params.aiEnabled ? ["ai"] : [],
5708
+ ...params.mlEnabled ? ["ml"] : [],
5709
+ ...runtimeReadsContext ? ["runtime"] : []
5710
+ ]);
5711
+ const readsStandardContext = isCrypto && (capturesPayload || runtimeReadsContext);
5712
+ const readsHyperliquid = isCrypto && (readsStandardContext || coreContextSources.includes("hyperliquidWhales"));
5713
+ return {
5714
+ binance: {
5715
+ read: readsStandardContext || coreContextSources.includes("binance"),
5716
+ backfill: shouldPrepareBinanceMarketContextForRun(
5717
+ paramsWithCoreDemand("binance")
5718
+ ),
5719
+ requiredBy: reasonsFor("binance")
5720
+ },
5721
+ coinmarketcap: {
5722
+ read: readsStandardContext || coreContextSources.includes("coinmarketcap"),
5723
+ backfill: shouldPrepareCoinMarketCapContextForRun(
5724
+ paramsWithCoreDemand("coinmarketcap")
5725
+ ),
5726
+ requiredBy: reasonsFor("coinmarketcap")
5727
+ },
5728
+ derivatives: {
5729
+ read: readsStandardContext || coreContextSources.includes("derivatives"),
5730
+ backfill: shouldPrepareDerivativesContextForRun(
5731
+ paramsWithCoreDemand("derivatives")
5732
+ ),
5733
+ requiredBy: reasonsFor("derivatives")
5734
+ },
5735
+ hyperliquidWhales: {
5736
+ read: readsHyperliquid,
5737
+ backfill: shouldPrepareHyperliquidWhaleContextForRun(
5738
+ params,
5739
+ coreContextSources
5740
+ ),
5741
+ requiredBy: reasonsFor("hyperliquidWhales")
5742
+ }
5743
+ };
5744
+ };
5653
5745
  var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
5654
5746
  var buildCoinMarketCapBackfillParams = (params) => ({
5655
5747
  userName: params.userName,
@@ -5678,7 +5770,15 @@ var prepareMarketContextForRun = async (params) => {
5678
5770
  console.log(import_chalk7.default.gray(message));
5679
5771
  });
5680
5772
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
5681
- if (shouldPrepareDerivativesContextForRun(params)) {
5773
+ const requirements = await resolveMarketContextRunRequirements(params);
5774
+ const readableSources = Object.entries(requirements).filter(([, requirement]) => requirement.read).map(([source]) => source);
5775
+ if (readableSources.length) {
5776
+ await timeOperation3(
5777
+ "market context schema ensure",
5778
+ () => (0, import_timescale5.ensureMarketContextSchemas)(readableSources)
5779
+ );
5780
+ }
5781
+ if (requirements.derivatives.backfill) {
5682
5782
  await timeOperation3(
5683
5783
  "derivatives context backfill",
5684
5784
  () => (params.mode === "signals" ? backfillDerivativesContextForSignals : backfillDerivativesContextForBacktest)(
@@ -5686,7 +5786,7 @@ var prepareMarketContextForRun = async (params) => {
5686
5786
  )
5687
5787
  );
5688
5788
  }
5689
- if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
5789
+ if (requirements.hyperliquidWhales.backfill) {
5690
5790
  await timeOperation3(
5691
5791
  "hyperliquid whale context backfill",
5692
5792
  () => backfillHyperliquidWhaleContext({
@@ -5698,13 +5798,13 @@ var prepareMarketContextForRun = async (params) => {
5698
5798
  })
5699
5799
  );
5700
5800
  }
5701
- if (shouldPrepareBinanceMarketContextForRun(params)) {
5801
+ if (requirements.binance.backfill) {
5702
5802
  await timeOperation3("binance market context backfill", () => {
5703
5803
  const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
5704
5804
  return backfill(buildBinanceMarketBackfillParams(params));
5705
5805
  });
5706
5806
  }
5707
- if (shouldPrepareCoinMarketCapContextForRun(params)) {
5807
+ if (requirements.coinmarketcap.backfill) {
5708
5808
  await timeOperation3(
5709
5809
  "coinmarketcap historical context backfill",
5710
5810
  () => resolveCoinMarketCapBackfillForMode(params.mode)(
@@ -5712,6 +5812,7 @@ var prepareMarketContextForRun = async (params) => {
5712
5812
  )
5713
5813
  );
5714
5814
  }
5815
+ return requirements;
5715
5816
  };
5716
5817
 
5717
5818
  // src/lib/backtest/runState.ts
@@ -5984,7 +6085,7 @@ var compactReplaySignal = (signal) => {
5984
6085
 
5985
6086
  // src/lib/replay/historicalSignalsReplay.ts
5986
6087
  var import_chalk9 = __toESM(require("chalk"));
5987
- var import_progress3 = __toESM(require("progress"));
6088
+ var import_progress4 = __toESM(require("progress"));
5988
6089
  var import_backtest = require("@tradejs/core/backtest");
5989
6090
  var import_strategies4 = require("@tradejs/core/strategies");
5990
6091
  var import_time3 = require("@tradejs/core/time");
@@ -6868,7 +6969,7 @@ var runHistoricalSignalsReplay = async ({
6868
6969
  const sharedReplayKeyPrefixes = [];
6869
6970
  let preparedSymbols = 0;
6870
6971
  let skippedSymbols = 0;
6871
- const prepareBar = new import_progress3.default(
6972
+ const prepareBar = new import_progress4.default(
6872
6973
  "prepare :current/:total [:bar][:percent] skipped=:skipped :etas(s) :symbol",
6873
6974
  {
6874
6975
  total: preparedRun.tickers.length,
@@ -6990,7 +7091,7 @@ var runHistoricalSignalsReplay = async ({
6990
7091
  interval
6991
7092
  });
6992
7093
  let abortedCycles = 0;
6993
- const cycleBar = new import_progress3.default(
7094
+ const cycleBar = new import_progress4.default(
6994
7095
  "cycles :current/:total [:bar][:percent] sig=:signals abort=:aborted :etas(s) :ts",
6995
7096
  {
6996
7097
  total: orderedTimestamps.length,
@@ -7960,6 +8061,15 @@ var syncRuntimeTrades = async ({
7960
8061
  nextTrade,
7961
8062
  { expire: import_constants5.TTL_1M }
7962
8063
  ),
8064
+ (0, import_redis5.setHashJsonField)(
8065
+ import_redis5.redisKeys.runtimeClosedTradeBucket(
8066
+ userName,
8067
+ (0, import_time5.getRuntimeStorageDayKey)(nextTrade.exitTimestamp)
8068
+ ),
8069
+ trade.orderId,
8070
+ nextTrade,
8071
+ { expire: import_constants5.TTL_1M }
8072
+ ),
7963
8073
  ...isCurrentActiveTrade ? [(0, import_redis5.delKey)(activeTradeKey)] : []
7964
8074
  ]);
7965
8075
  syncedTrades.push(nextTrade);