@tradejs/cli 3.1.29-beta.281 → 3.1.29-beta.283

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.
@@ -59,8 +59,21 @@ const timer = setInterval(() => {
59
59
  try {
60
60
  const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
61
61
  console.log(result.message);
62
+ if (result.exitCode !== 0) {
63
+ let fd;
64
+ try {
65
+ fd = fs.openSync(file + '.log', fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
66
+ const size = fs.fstatSync(fd).size;
67
+ const tail = Buffer.alloc(Math.min(size, 65536));
68
+ fs.readSync(fd, tail, 0, tail.length, Math.max(0, size - tail.length));
69
+ // The worker redacts each line before persisting or returning it through MCP.
70
+ for (const line of tail.toString('utf8').split(/[\\r\\n]+/).filter(Boolean).slice(-150)) console.error('Host replay: ' + line);
71
+ } catch (error) {
72
+ console.error('Host replay log unavailable: ' + error.code);
73
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
74
+ }
62
75
  clearInterval(timer);
63
- process.exit(result.exitCode === 0 ? 0 : 1);
76
+ process.exitCode = result.exitCode === 0 ? 0 : 1;
64
77
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
65
78
  }, 1000);
66
79
  `;
@@ -83,7 +96,7 @@ var buildMcpReplayProcess = async ({
83
96
  const heartbeat = JSON.parse(
84
97
  await import_promises.default.readFile(import_node_path.default.join(root, "heartbeat.json"), "utf8")
85
98
  );
86
- if (!Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
99
+ if (heartbeat.protocolVersion !== 2 || !Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
87
100
  throw new Error("Host replay runner heartbeat is stale");
88
101
  const bytes = await import_promises.default.readFile(evidencePath);
89
102
  const request = JSON.stringify({
@@ -42,39 +42,6 @@ var isRuntimeTradeRecord = (value) => {
42
42
  const record = value;
43
43
  return typeof record.orderId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.entryTimestamp === "number" && typeof record.entryPrice === "number" && typeof record.qty === "number" && (record.direction === "LONG" || record.direction === "SHORT");
44
44
  };
45
- var loadRuntimeTrades = async (userName, {
46
- startTime,
47
- endTime
48
- } = {}) => {
49
- const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
50
- const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
51
- if (hasWindow) {
52
- const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
53
- const bucketTrades = (await Promise.all(
54
- dayKeys.map(
55
- (dayKey) => (0, import_redis.getHashJsonValues)(
56
- import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
57
- )
58
- )
59
- )).flat();
60
- const dedupedBucketTrades = /* @__PURE__ */ new Map();
61
- for (const trade of bucketTrades) {
62
- if (!isRuntimeTradeRecord(trade)) {
63
- continue;
64
- }
65
- dedupedBucketTrades.set(trade.orderId, trade);
66
- }
67
- const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
68
- return windowedTrades;
69
- }
70
- const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
71
- const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
72
- const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
73
- (key) => !key.startsWith(bucketPrefix)
74
- );
75
- const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
76
- return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
77
- };
78
45
  var loadRuntimeClosedTrades = async (userName, {
79
46
  startTime,
80
47
  endTime
@@ -98,6 +65,49 @@ var loadRuntimeClosedTrades = async (userName, {
98
65
  (left, right) => left.exitTimestamp - right.exitTimestamp
99
66
  );
100
67
  };
68
+ var loadRuntimeEvidenceTrades = async (userName, { startTime, endTime }) => {
69
+ const prefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
70
+ const lastDay = (0, import_time.getRuntimeStorageDayKey)(endTime - 1);
71
+ const keys = (await (0, import_redis.getKeys)(prefix)).filter((key) => {
72
+ const day = key.slice(prefix.length);
73
+ return key.startsWith(prefix) && /^\d{4}-\d{2}-\d{2}$/.test(day) && day <= lastDay;
74
+ });
75
+ const retained = /* @__PURE__ */ new Map();
76
+ for (let offset = 0; offset < keys.length; offset += 16) {
77
+ const buckets = await Promise.all(
78
+ keys.slice(offset, offset + 16).map((key) => (0, import_redis.getHashJsonValues)(key))
79
+ );
80
+ for (const row of buckets.flat())
81
+ if (isRuntimeTradeRecord(row)) retained.set(row.orderId, row);
82
+ }
83
+ for (const row of await loadRuntimeClosedTrades(userName, {
84
+ startTime,
85
+ endTime
86
+ }))
87
+ retained.set(row.orderId, row);
88
+ return [...retained.values()].filter(
89
+ (row) => row.entryTimestamp < endTime && (row.status === "active" || Number.isFinite(row.exitTimestamp) && row.exitTimestamp >= startTime)
90
+ ).map((row) => {
91
+ const closesAfterCutoff = row.status === "closed" && row.exitTimestamp >= endTime;
92
+ if (!closesAfterCutoff && !(row.status === "active" && row.lastSyncedAt >= endTime))
93
+ return row;
94
+ return {
95
+ ...row,
96
+ status: "active",
97
+ currentPrice: null,
98
+ currentPnl: null,
99
+ closedPnl: null,
100
+ exitPrice: null,
101
+ actualExitPrice: null,
102
+ exitTimestamp: null,
103
+ exitType: null,
104
+ closeFee: null,
105
+ fundingFee: null,
106
+ totalFee: null,
107
+ lastSyncedAt: void 0
108
+ };
109
+ }).sort((a, b) => a.entryTimestamp - b.entryTimestamp);
110
+ };
101
111
 
102
112
  // src/lib/runtimeSignalsLoader.ts
103
113
  var import_redis2 = require("@tradejs/infra/redis");
@@ -318,30 +328,13 @@ var collectRuntimeDebugEvidence = async ({
318
328
  }) => {
319
329
  const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
320
330
  const dayKeySet = new Set(dayKeys);
321
- const [
322
- entryTrades,
323
- closedTrades,
324
- signals,
325
- evaluations,
326
- evaluationStatsBuckets,
327
- lineageScopes
328
- ] = await Promise.all([
329
- loadRuntimeTrades(userName, { startTime, endTime }),
330
- loadRuntimeClosedTrades(userName, { startTime, endTime }),
331
+ const [trades, signals, evaluations, evaluationStatsBuckets, lineageScopes] = await Promise.all([
332
+ loadRuntimeEvidenceTrades(userName, { startTime, endTime }),
331
333
  loadRuntimeSignals(userName, { startTime, endTime }),
332
334
  loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
333
335
  loadRuntimeSignalEvaluationStatsBuckets(userName),
334
336
  loadRuntimeLineageScopes(userName, { startTime, endTime })
335
337
  ]);
336
- const tradesByOrderId = new Map(
337
- entryTrades.map((trade) => [trade.orderId, trade])
338
- );
339
- for (const trade of closedTrades) {
340
- tradesByOrderId.set(trade.orderId, trade);
341
- }
342
- const trades = [...tradesByOrderId.values()].sort(
343
- (left, right) => left.entryTimestamp - right.entryTimestamp
344
- );
345
338
  const filteredStats = evaluationStatsBuckets.filter(
346
339
  (entry) => dayKeySet.has(entry.dayKey) && (!deploymentId || entry.deploymentId === deploymentId) && (!strategies?.length || strategies.includes(entry.strategy))
347
340
  );