@tradejs/cli 3.1.29-beta.291 → 3.1.29-beta.296

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.
@@ -170,7 +170,7 @@ function normalizeTrade(raw, fallbackStrategy, candlesBySymbol = {}) {
170
170
  pockets,
171
171
  features,
172
172
  groups,
173
- candles: normalizeCandles(r.candles ?? candlesBySymbol[symbol]),
173
+ candles: r.candles === void 0 ? candlesBySymbol[symbol] ?? [] : normalizeCandles(r.candles),
174
174
  figures: normalizeFigures(r.figures ?? payload.figures)
175
175
  };
176
176
  }
@@ -192,11 +192,16 @@ function normalizeDataset(input, options = {}) {
192
192
  );
193
193
  if (runs.size > 1)
194
194
  throw new Error("Mixed backtest runs; provide one immutable lineage");
195
+ const candlesBySymbol = Object.fromEntries(
196
+ Object.entries(options.candlesBySymbol ?? object(root.candlesBySymbol)).map(
197
+ ([symbol, candles]) => [symbol, normalizeCandles(candles)]
198
+ )
199
+ );
195
200
  const trades = rows.map(
196
201
  (r) => normalizeTrade(
197
202
  r,
198
203
  options.strategy ?? String(root.strategy ?? ""),
199
- options.candlesBySymbol ?? object(root.candlesBySymbol)
204
+ candlesBySymbol
200
205
  )
201
206
  );
202
207
  const ids = /* @__PURE__ */ new Set();
@@ -213,6 +218,21 @@ function normalizeDataset(input, options = {}) {
213
218
  trades
214
219
  };
215
220
  }
221
+ function tradeCandleWindow(trade, intervalMs) {
222
+ const lowerBound = (timestamp) => {
223
+ let lo = 0, hi = trade.candles.length;
224
+ while (lo < hi) {
225
+ const mid = Math.floor((lo + hi) / 2);
226
+ if (trade.candles[mid].timestamp < timestamp) lo = mid + 1;
227
+ else hi = mid;
228
+ }
229
+ return lo;
230
+ };
231
+ return trade.candles.slice(
232
+ lowerBound(trade.signalTime - 140 * intervalMs),
233
+ lowerBound(trade.exitTime + 20 * intervalMs + 1)
234
+ );
235
+ }
216
236
 
217
237
  // src/lib/researchVisuals/analysis.ts
218
238
  var import_node_crypto = require("crypto");
@@ -534,7 +554,10 @@ function pricePanel(t, c, step, x, y, w, h) {
534
554
  );
535
555
  }
536
556
  function renderTradeSvg(t, intervalMs) {
537
- const candles = chartCandles(t);
557
+ const candles = chartCandles({
558
+ ...t,
559
+ candles: tradeCandleWindow(t, intervalMs)
560
+ });
538
561
  const start = Math.min(
539
562
  t.signalTime - 90 * intervalMs,
540
563
  t.entryTime - 90 * intervalMs
@@ -809,9 +832,7 @@ async function generateResearchVisuals(options) {
809
832
  population: "selected",
810
833
  trades: selected.map((trade) => ({
811
834
  ...trade,
812
- candles: trade.candles.filter(
813
- (bar) => bar.timestamp >= trade.signalTime - 140 * dataset.intervalMs && bar.timestamp <= trade.exitTime + 20 * dataset.intervalMs
814
- )
835
+ candles: tradeCandleWindow(trade, dataset.intervalMs)
815
836
  }))
816
837
  };
817
838
  await write("chart-inputs.json.gz", (0, import_node_zlib.gzipSync)(JSON.stringify(retained)));
@@ -1028,13 +1028,12 @@ var getReferenceSymbols = () => {
1028
1028
  };
1029
1029
  var intervalToMarketFeatureInterval = (interval2) => {
1030
1030
  const normalized = String(interval2).trim().toLowerCase();
1031
- if (normalized === "1" || normalized === "1m") return "1m";
1031
+ if (normalized === "1" || normalized === "1m") return null;
1032
1032
  if (normalized === "5" || normalized === "5m") return "5m";
1033
1033
  if (normalized === "60" || normalized === "1h") return "1h";
1034
1034
  return "15m";
1035
1035
  };
1036
1036
  var marketIntervalToConnectorInterval = (interval2) => {
1037
- if (interval2 === "1m") return "1";
1038
1037
  if (interval2 === "5m") return "5";
1039
1038
  if (interval2 === "1h") return "60";
1040
1039
  return "15";
@@ -1199,6 +1198,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1199
1198
  return skippedBackfillResult();
1200
1199
  }
1201
1200
  const interval2 = intervalToMarketFeatureInterval(params.interval);
1201
+ if (interval2 === null) return skippedBackfillResult();
1202
1202
  const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval2];
1203
1203
  const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
1204
1204
  if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
@@ -2779,7 +2779,7 @@ var coinMarketCapFetch = async (params) => {
2779
2779
  };
2780
2780
  var fetchCoinMarketCapIndexRows = async (params) => {
2781
2781
  const rows = [];
2782
- const maxChunkDays = 9;
2782
+ const maxChunkDays = 4;
2783
2783
  const totalChunks = Math.max(
2784
2784
  1,
2785
2785
  Math.ceil(
@@ -488,13 +488,12 @@ var getReferenceSymbols = () => {
488
488
  };
489
489
  var intervalToMarketFeatureInterval = (interval) => {
490
490
  const normalized = String(interval).trim().toLowerCase();
491
- if (normalized === "1" || normalized === "1m") return "1m";
491
+ if (normalized === "1" || normalized === "1m") return null;
492
492
  if (normalized === "5" || normalized === "5m") return "5m";
493
493
  if (normalized === "60" || normalized === "1h") return "1h";
494
494
  return "15m";
495
495
  };
496
496
  var marketIntervalToConnectorInterval = (interval) => {
497
- if (interval === "1m") return "1";
498
497
  if (interval === "5m") return "5";
499
498
  if (interval === "1h") return "60";
500
499
  return "15";
@@ -659,6 +658,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
659
658
  return skippedBackfillResult();
660
659
  }
661
660
  const interval = intervalToMarketFeatureInterval(params.interval);
661
+ if (interval === null) return skippedBackfillResult();
662
662
  const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
663
663
  const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
664
664
  if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
@@ -2239,7 +2239,7 @@ var coinMarketCapFetch = async (params) => {
2239
2239
  };
2240
2240
  var fetchCoinMarketCapIndexRows = async (params) => {
2241
2241
  const rows = [];
2242
- const maxChunkDays = 9;
2242
+ const maxChunkDays = 4;
2243
2243
  const totalChunks = Math.max(
2244
2244
  1,
2245
2245
  Math.ceil(
@@ -486,13 +486,12 @@ var getReferenceSymbols = () => {
486
486
  };
487
487
  var intervalToMarketFeatureInterval = (interval) => {
488
488
  const normalized = String(interval).trim().toLowerCase();
489
- if (normalized === "1" || normalized === "1m") return "1m";
489
+ if (normalized === "1" || normalized === "1m") return null;
490
490
  if (normalized === "5" || normalized === "5m") return "5m";
491
491
  if (normalized === "60" || normalized === "1h") return "1h";
492
492
  return "15m";
493
493
  };
494
494
  var marketIntervalToConnectorInterval = (interval) => {
495
- if (interval === "1m") return "1";
496
495
  if (interval === "5m") return "5";
497
496
  if (interval === "1h") return "60";
498
497
  return "15";
@@ -657,6 +656,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
657
656
  return skippedBackfillResult();
658
657
  }
659
658
  const interval = intervalToMarketFeatureInterval(params.interval);
659
+ if (interval === null) return skippedBackfillResult();
660
660
  const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
661
661
  const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
662
662
  if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
@@ -2237,7 +2237,7 @@ var coinMarketCapFetch = async (params) => {
2237
2237
  };
2238
2238
  var fetchCoinMarketCapIndexRows = async (params) => {
2239
2239
  const rows = [];
2240
- const maxChunkDays = 9;
2240
+ const maxChunkDays = 4;
2241
2241
  const totalChunks = Math.max(
2242
2242
  1,
2243
2243
  Math.ceil(
@@ -42,40 +42,9 @@ var import_chalk = __toESM(require("chalk"));
42
42
 
43
43
  // src/lib/strategyRelease/researchRetention.ts
44
44
  var import_promises = __toESM(require("fs/promises"));
45
-
46
- // src/lib/researchMetricSnapshot.ts
47
- var import_node_crypto = require("crypto");
48
- var import_node_zlib = require("zlib");
49
- var MAX_BYTES = 256 * 1024 * 1024;
50
- var hash = (bytes) => (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
51
- var validatePermanentResearchMetrics = (value) => {
52
- if (!value || typeof value !== "object") return;
53
- for (const [key, child] of Object.entries(value)) {
54
- if (["matchedPairs", "tradeRows", "allProjectedLeaves"].includes(key) && Array.isArray(child) && child.length)
55
- throw new Error(`Temporary payload ${key} is not a permanent metric`);
56
- if (key === "trades" && Array.isArray(child) && child.some(
57
- (row) => row && typeof row === "object" && ("signalId" in row || "entryTimestamp" in row)
58
- ))
59
- throw new Error("Temporary trade records are not permanent metrics");
60
- validatePermanentResearchMetrics(child);
61
- }
62
- };
63
- var decodeResearchMetricSnapshot = (snapshot) => {
64
- if (!snapshot || typeof snapshot !== "object" || !("schema" in snapshot) || snapshot.schema !== "tradejs-metric-snapshot/v1" || !("encoding" in snapshot) || snapshot.encoding !== "gzip+base64" || !("decodedBytes" in snapshot) || typeof snapshot.decodedBytes !== "number" || !Number.isSafeInteger(snapshot.decodedBytes) || snapshot.decodedBytes <= 0 || snapshot.decodedBytes > MAX_BYTES || !("sha256" in snapshot) || typeof snapshot.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(snapshot.sha256) || !("payload" in snapshot) || typeof snapshot.payload !== "string")
65
- throw new Error("Invalid metric snapshot envelope");
66
- const bytes = (0, import_node_zlib.gunzipSync)(Buffer.from(snapshot.payload, "base64"), {
67
- maxOutputLength: snapshot.decodedBytes
68
- });
69
- if (bytes.length !== snapshot.decodedBytes || hash(bytes) !== snapshot.sha256)
70
- throw new Error("Metric snapshot checksum or size mismatch");
71
- const metrics = JSON.parse(bytes.toString("utf8"));
72
- validatePermanentResearchMetrics(metrics);
73
- return metrics;
74
- };
75
-
76
- // src/lib/strategyRelease/researchRetention.ts
45
+ var import_researchMetricSnapshot = require("@tradejs/infra/researchMetricSnapshot");
77
46
  var import_node_path2 = __toESM(require("path"));
78
- var import_node_crypto2 = require("crypto");
47
+ var import_node_crypto = require("crypto");
79
48
  var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
80
49
 
81
50
  // src/lib/strategyRelease/researchFileConsumers.ts
@@ -267,7 +236,7 @@ var mapFiles = async (entries, visit) => {
267
236
  if (worker.status === "rejected") throw worker.reason;
268
237
  return results;
269
238
  };
270
- var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto2.createHash)("sha256").update(await import_promises.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
239
+ var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto.createHash)("sha256").update(await import_promises.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
271
240
  var inside = (root, file) => {
272
241
  const relative = import_node_path2.default.relative(root, file);
273
242
  return relative !== "" && !relative.startsWith(`..${import_node_path2.default.sep}`) && relative !== ".." && !import_node_path2.default.isAbsolute(relative);
@@ -330,7 +299,7 @@ var applyResearchRetention = async (options) => {
330
299
  throw new Error(
331
300
  "Complete v2 note needs one verified metric snapshot"
332
301
  );
333
- decodeResearchMetricSnapshot(JSON.parse(blocks[0][1]));
302
+ (0, import_researchMetricSnapshot.decodeResearchMetricSnapshot)(JSON.parse(blocks[0][1]));
334
303
  }
335
304
  } finally {
336
305
  await handle.close();
@@ -415,7 +384,7 @@ var applyResearchRetention = async (options) => {
415
384
  };
416
385
 
417
386
  // src/lib/strategyRelease.ts
418
- var import_node_crypto3 = require("crypto");
387
+ var import_node_crypto2 = require("crypto");
419
388
  var import_promises2 = __toESM(require("fs/promises"));
420
389
  var import_node_path3 = __toESM(require("path"));
421
390
  var import_types2 = require("@tradejs/types");
@@ -1585,7 +1554,7 @@ var verifyCompletedCoreResearchBundle = async ({
1585
1554
  const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
1586
1555
  const parsedSpec = JSON.parse(specBytes.toString("utf8"));
1587
1556
  assert2(
1588
- manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto3.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
1557
+ manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto2.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
1589
1558
  "core result is not bound by its completed core research manifest"
1590
1559
  );
1591
1560
  for (const [relativePath, expectedSha256] of Object.entries(