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

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.
@@ -827,7 +827,9 @@ var BASE_CONTEXT_FEATURE_SECTIONS = [
827
827
  "relative",
828
828
  "derivatives",
829
829
  "mtf",
830
- "gateFeatures"
830
+ "gateFeatures",
831
+ "tickerProfile",
832
+ "portfolioContext"
831
833
  ];
832
834
  var asRecord = (value) => typeof value === "object" && value != null && !Array.isArray(value) ? value : null;
833
835
  var collectStrategyGateFeatures = (baseContext) => {
@@ -844,6 +846,7 @@ var pickBaseContextFeatureSections = (baseContext) => Object.fromEntries(
844
846
  );
845
847
  var buildAiTrainEvaluationFeatureSnapshot = ({
846
848
  additionalIndicators,
849
+ featureSnapshot,
847
850
  mode
848
851
  }) => {
849
852
  if (mode === "none") {
@@ -853,12 +856,14 @@ var buildAiTrainEvaluationFeatureSnapshot = ({
853
856
  const baseContext = asRecord(additional?.baseContext);
854
857
  if (!baseContext) {
855
858
  return {
856
- baseContextAvailable: false
859
+ baseContextAvailable: false,
860
+ ...featureSnapshot ? { featureSnapshot } : {}
857
861
  };
858
862
  }
859
863
  const strategyGateFeatures = collectStrategyGateFeatures(baseContext);
860
864
  const snapshot = {
861
865
  baseContextAvailable: true,
866
+ ...featureSnapshot ? { featureSnapshot } : {},
862
867
  gateFeatures: baseContext.gateFeatures ?? null,
863
868
  ...strategyGateFeatures ? { strategyGateFeatures } : {}
864
869
  };
@@ -1059,6 +1064,8 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
1059
1064
  "INTERVAL",
1060
1065
  "DERIVATIVES_CONTEXT_ENABLED",
1061
1066
  "DERIVATIVES_CONTEXT_TARGET_ENABLED",
1067
+ "TICKER_PROFILES_PROVIDER",
1068
+ "TICKER_PROFILES_UNIVERSE",
1062
1069
  "DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
1063
1070
  "DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
1064
1071
  "DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
@@ -1093,13 +1100,15 @@ var writeAiTrainResearchSnapshot = async ({
1093
1100
  }) => {
1094
1101
  const resolvedPath = import_node_path2.default.resolve(outputPath);
1095
1102
  await import_promises2.default.mkdir(import_node_path2.default.dirname(resolvedPath), { recursive: true });
1096
- await import_promises2.default.writeFile(
1097
- resolvedPath,
1098
- `${JSON.stringify(result, null, 2)}
1099
- `,
1100
- "utf8"
1101
- );
1102
- return resolvedPath;
1103
+ const bytes = `${JSON.stringify(result)}
1104
+ `;
1105
+ await import_promises2.default.writeFile(resolvedPath, bytes, "utf8");
1106
+ return {
1107
+ schema: "tradejs-research-report-receipt/v1",
1108
+ path: resolvedPath,
1109
+ bytes: Buffer.byteLength(bytes),
1110
+ sha256: (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex")
1111
+ };
1103
1112
  };
1104
1113
  var normalizeForStableJson = (value) => {
1105
1114
  if (Array.isArray(value)) {
@@ -1416,6 +1425,7 @@ var extractSignalFromAiDatasetRow = (row) => {
1416
1425
  const { payload } = row;
1417
1426
  return {
1418
1427
  ...payload.signal,
1428
+ aiFeatureReplay: { snapshot: payload.featureSnapshot ?? null },
1419
1429
  strategy: payload.signal.strategy,
1420
1430
  figures: payload.figures ?? {},
1421
1431
  indicators: payload.indicators ?? {},
@@ -1474,7 +1484,11 @@ import_args.default.option(
1474
1484
  "Save compact AI approval chart data for the strategies UI",
1475
1485
  false
1476
1486
  );
1477
- import_args.default.option(["j", "json"], "Print structured JSON summary", false);
1487
+ import_args.default.option(
1488
+ ["j", "json"],
1489
+ "Print full JSON without --output; otherwise print its receipt",
1490
+ false
1491
+ );
1478
1492
  import_args.default.option(
1479
1493
  ["O", "output"],
1480
1494
  "Write the structured JSON summary to a file and suppress table output",
@@ -1938,6 +1952,7 @@ var main = async () => {
1938
1952
  const isCorrect = aiApproved === profitableTrade;
1939
1953
  const features = buildAiTrainEvaluationFeatureSnapshot({
1940
1954
  additionalIndicators: payload.additionalIndicators,
1955
+ featureSnapshot: payload.featureSnapshot,
1941
1956
  mode: dumpFeatureMode
1942
1957
  });
1943
1958
  evaluations.push({
@@ -2182,14 +2197,11 @@ var main = async () => {
2182
2197
  ttlMs: import_constants.TTL_1M
2183
2198
  });
2184
2199
  }
2185
- if (jsonOutputPath) {
2186
- await writeAiTrainResearchSnapshot({
2187
- outputPath: jsonOutputPath,
2188
- result
2189
- });
2190
- }
2200
+ const reportReceipt = jsonOutputPath ? await writeAiTrainResearchSnapshot({ outputPath: jsonOutputPath, result }) : null;
2191
2201
  if (jsonOutput) {
2192
- if (printJsonOutput) {
2202
+ if (reportReceipt) {
2203
+ console.log(JSON.stringify(reportReceipt));
2204
+ } else if (printJsonOutput) {
2193
2205
  console.log(JSON.stringify(result));
2194
2206
  }
2195
2207
  process.exit(failed === selectedRows ? 1 : 0);
@@ -46,7 +46,7 @@ var import_node_path = __toESM(require("path"));
46
46
 
47
47
  // src/lib/coreResearch/types.ts
48
48
  var CORE_RESEARCH_SCHEMA = "tradejs-core-research/v1";
49
- var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/v1";
49
+ var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/v2";
50
50
  var CORE_RESEARCH_LEDGER_SCHEMA = "tradejs-core-research-ledger/v1";
51
51
 
52
52
  // src/lib/coreResearch/io.ts
@@ -731,6 +731,14 @@ var writeCoreResearchTrades = async (params) => {
731
731
  }
732
732
  await closeOutput(output);
733
733
  };
734
+ var summarizeCoreResearchComparison = (comparison) => {
735
+ const { matchedPairs, ...summary } = comparison;
736
+ return {
737
+ ...summary,
738
+ exitReasonChanges: matchedPairs.filter((pair) => pair.exitReasonChanged).length,
739
+ exitCodeChanges: matchedPairs.filter((pair) => pair.exitCodeChanged).length
740
+ };
741
+ };
734
742
 
735
743
  // src/lib/coreResearch/metrics.ts
736
744
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -1806,7 +1814,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
1806
1814
  )}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
1807
1815
  };
1808
1816
  var comparisonCards = (comparisons) => comparisons.map(
1809
- (comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
1817
+ (comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.exitReasonChanges}</dd><dt>Exit code changes</dt><dd>${comparison.exitCodeChanges}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
1810
1818
  ).join("");
1811
1819
  var regimeRows = (variants) => {
1812
1820
  const keys = [
@@ -2491,7 +2499,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2491
2499
  regimeCausality: "signal-time payload.additionalIndicators.baseContext only"
2492
2500
  },
2493
2501
  variants: analyses,
2494
- comparisons,
2502
+ comparisons: comparisons.map(summarizeCoreResearchComparison),
2495
2503
  multipleTesting: {
2496
2504
  family: spec.hypothesis.family,
2497
2505
  hypotheses: Math.max(comparisons.length, familyHypotheses),
@@ -2524,7 +2532,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2524
2532
  artifactHashes: sourceHashes
2525
2533
  };
2526
2534
  await writeJsonAtomic(paths.resultPath, result);
2527
- await writeJsonAtomic(paths.comparisonPath, comparisons);
2535
+ await writeJsonAtomic(paths.comparisonPath, result.comparisons);
2528
2536
  await writeCoreResearchMatches({ filePath: paths.matchesPath, comparisons });
2529
2537
  await writeCoreResearchTrades({
2530
2538
  filePath: paths.tradesPath,
@@ -5183,6 +5183,9 @@ var createPortfolioReplayConnector = (connector) => {
5183
5183
  let executionTimestamp = null;
5184
5184
  const positionsBySymbol = /* @__PURE__ */ new Map();
5185
5185
  const currentPriceBySymbol = /* @__PURE__ */ new Map();
5186
+ const markedAtBySymbol = /* @__PURE__ */ new Map();
5187
+ const entryFacts = /* @__PURE__ */ new Map();
5188
+ let portfolioObserved = false;
5186
5189
  const orderLog = [];
5187
5190
  const positionLog = [];
5188
5191
  const orderLogByStrategy = /* @__PURE__ */ new Map();
@@ -5420,14 +5423,51 @@ var createPortfolioReplayConnector = (connector) => {
5420
5423
  const { strategyName: _strategyName, ...nextPosition } = position;
5421
5424
  return nextPosition;
5422
5425
  }),
5426
+ getPortfolioObservation: async (asOf, reservations) => {
5427
+ portfolioObserved = true;
5428
+ const pendingIds = new Set(reservations.map((r) => r.id));
5429
+ for (const id of entryFacts.keys())
5430
+ if (!pendingIds.has(id)) entryFacts.delete(id);
5431
+ return {
5432
+ asOf,
5433
+ positions: Array.from(positionsBySymbol.values()).map(
5434
+ ({ position, stopLossPrice }) => ({
5435
+ id: position.orderId ?? `${position.strategyName}:${position.symbol}:${position.timestamp}`,
5436
+ strategy: position.strategyName === UNKNOWN_STRATEGY ? null : position.strategyName,
5437
+ symbol: position.symbol,
5438
+ direction: position.direction,
5439
+ qty: position.qty,
5440
+ entryPrice: position.price,
5441
+ markPrice: currentPriceBySymbol.get(position.symbol) ?? null,
5442
+ markedAt: markedAtBySymbol.get(position.symbol) ?? null,
5443
+ stopLossPrice
5444
+ })
5445
+ ),
5446
+ orders: reservations.map(
5447
+ (r) => entryFacts.get(r.id) ?? {
5448
+ id: r.id,
5449
+ status: "pending",
5450
+ filledQty: 0
5451
+ }
5452
+ )
5453
+ };
5454
+ },
5423
5455
  getOpenPositionPnl: async () => Array.from(positionsBySymbol.values()).map(
5424
- ({ position }) => getPositionPnlSnapshot({
5425
- position,
5426
- currentPrice: currentPriceBySymbol.get(position.symbol) ?? position.price
5456
+ ({ position, stopLossPrice }) => ({
5457
+ ...getPositionPnlSnapshot({
5458
+ position,
5459
+ currentPrice: currentPriceBySymbol.get(position.symbol) ?? position.price
5460
+ }),
5461
+ observedAt: markedAtBySymbol.get(position.symbol),
5462
+ stopLossPrice
5427
5463
  })
5428
5464
  ),
5429
5465
  placeOrder: async (order) => {
5430
- const { beforeSubmit, ...submittedOrder } = order;
5466
+ const {
5467
+ beforeSubmit,
5468
+ onRejected: _onRejected,
5469
+ ...submittedOrder
5470
+ } = order;
5431
5471
  if (positionsBySymbol.has(order.symbol)) {
5432
5472
  return false;
5433
5473
  }
@@ -5471,6 +5511,14 @@ var createPortfolioReplayConnector = (connector) => {
5471
5511
  stopLossPrice: null
5472
5512
  };
5473
5513
  positionsBySymbol.set(order.symbol, positionState);
5514
+ markedAtBySymbol.set(order.symbol, executionTimestamp ?? order.timestamp);
5515
+ if (portfolioObserved && order.orderId)
5516
+ entryFacts.set(order.orderId, {
5517
+ id: order.orderId,
5518
+ status: "filled",
5519
+ filledQty: order.qty,
5520
+ positionId: order.orderId
5521
+ });
5474
5522
  logOrder({
5475
5523
  positionState,
5476
5524
  data: {
@@ -5554,6 +5602,7 @@ var createPortfolioReplayConnector = (connector) => {
5554
5602
  getTickers: connector.getTickers,
5555
5603
  advanceMarket: async ({ symbol, candle }) => {
5556
5604
  executionTimestamp = candle.timestamp;
5605
+ markedAtBySymbol.set(symbol, candle.timestamp);
5557
5606
  currentPriceBySymbol.set(symbol, candle.close);
5558
5607
  await checkSl({ symbol, candle });
5559
5608
  await checkTp({ symbol, candle });
@@ -5188,6 +5188,9 @@ var createPortfolioReplayConnector = (connector) => {
5188
5188
  let executionTimestamp = null;
5189
5189
  const positionsBySymbol = /* @__PURE__ */ new Map();
5190
5190
  const currentPriceBySymbol = /* @__PURE__ */ new Map();
5191
+ const markedAtBySymbol = /* @__PURE__ */ new Map();
5192
+ const entryFacts = /* @__PURE__ */ new Map();
5193
+ let portfolioObserved = false;
5191
5194
  const orderLog = [];
5192
5195
  const positionLog = [];
5193
5196
  const orderLogByStrategy = /* @__PURE__ */ new Map();
@@ -5425,14 +5428,51 @@ var createPortfolioReplayConnector = (connector) => {
5425
5428
  const { strategyName: _strategyName, ...nextPosition } = position;
5426
5429
  return nextPosition;
5427
5430
  }),
5431
+ getPortfolioObservation: async (asOf, reservations) => {
5432
+ portfolioObserved = true;
5433
+ const pendingIds = new Set(reservations.map((r) => r.id));
5434
+ for (const id of entryFacts.keys())
5435
+ if (!pendingIds.has(id)) entryFacts.delete(id);
5436
+ return {
5437
+ asOf,
5438
+ positions: Array.from(positionsBySymbol.values()).map(
5439
+ ({ position, stopLossPrice }) => ({
5440
+ id: position.orderId ?? `${position.strategyName}:${position.symbol}:${position.timestamp}`,
5441
+ strategy: position.strategyName === UNKNOWN_STRATEGY ? null : position.strategyName,
5442
+ symbol: position.symbol,
5443
+ direction: position.direction,
5444
+ qty: position.qty,
5445
+ entryPrice: position.price,
5446
+ markPrice: currentPriceBySymbol.get(position.symbol) ?? null,
5447
+ markedAt: markedAtBySymbol.get(position.symbol) ?? null,
5448
+ stopLossPrice
5449
+ })
5450
+ ),
5451
+ orders: reservations.map(
5452
+ (r) => entryFacts.get(r.id) ?? {
5453
+ id: r.id,
5454
+ status: "pending",
5455
+ filledQty: 0
5456
+ }
5457
+ )
5458
+ };
5459
+ },
5428
5460
  getOpenPositionPnl: async () => Array.from(positionsBySymbol.values()).map(
5429
- ({ position }) => getPositionPnlSnapshot({
5430
- position,
5431
- currentPrice: currentPriceBySymbol.get(position.symbol) ?? position.price
5461
+ ({ position, stopLossPrice }) => ({
5462
+ ...getPositionPnlSnapshot({
5463
+ position,
5464
+ currentPrice: currentPriceBySymbol.get(position.symbol) ?? position.price
5465
+ }),
5466
+ observedAt: markedAtBySymbol.get(position.symbol),
5467
+ stopLossPrice
5432
5468
  })
5433
5469
  ),
5434
5470
  placeOrder: async (order) => {
5435
- const { beforeSubmit, ...submittedOrder } = order;
5471
+ const {
5472
+ beforeSubmit,
5473
+ onRejected: _onRejected,
5474
+ ...submittedOrder
5475
+ } = order;
5436
5476
  if (positionsBySymbol.has(order.symbol)) {
5437
5477
  return false;
5438
5478
  }
@@ -5476,6 +5516,14 @@ var createPortfolioReplayConnector = (connector) => {
5476
5516
  stopLossPrice: null
5477
5517
  };
5478
5518
  positionsBySymbol.set(order.symbol, positionState);
5519
+ markedAtBySymbol.set(order.symbol, executionTimestamp ?? order.timestamp);
5520
+ if (portfolioObserved && order.orderId)
5521
+ entryFacts.set(order.orderId, {
5522
+ id: order.orderId,
5523
+ status: "filled",
5524
+ filledQty: order.qty,
5525
+ positionId: order.orderId
5526
+ });
5479
5527
  logOrder({
5480
5528
  positionState,
5481
5529
  data: {
@@ -5559,6 +5607,7 @@ var createPortfolioReplayConnector = (connector) => {
5559
5607
  getTickers: connector.getTickers,
5560
5608
  advanceMarket: async ({ symbol, candle }) => {
5561
5609
  executionTimestamp = candle.timestamp;
5610
+ markedAtBySymbol.set(symbol, candle.timestamp);
5562
5611
  currentPriceBySymbol.set(symbol, candle.close);
5563
5612
  await checkSl({ symbol, candle });
5564
5613
  await checkTp({ symbol, candle });