@tradejs/cli 3.1.29-beta.280 → 3.1.29-beta.282

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.
package/dist/cli.js CHANGED
@@ -12818,7 +12818,7 @@ var init_marketContextPrepare = __esm({
12818
12818
  });
12819
12819
 
12820
12820
  // src/lib/runtimeRedis.ts
12821
- var import_redis4, import_time, import_runtimeStrategyConfigs, isRuntimeStrategyEnabled, isRuntimeTradeRecord, loadRuntimeTrades, loadRuntimeClosedTrades, loadRuntimeActiveTradeOrderIds, loadRuntimeActiveTrades, loadStrategyResultSymbols;
12821
+ var import_redis4, import_time, import_runtimeStrategyConfigs, isRuntimeStrategyEnabled, isRuntimeTradeRecord, loadRuntimeTrades, loadRuntimeClosedTrades, loadRuntimeEvidenceTrades, loadRuntimeActiveTradeOrderIds, loadRuntimeActiveTrades, loadStrategyResultSymbols;
12822
12822
  var init_runtimeRedis = __esm({
12823
12823
  "src/lib/runtimeRedis.ts"() {
12824
12824
  "use strict";
@@ -12892,6 +12892,49 @@ var init_runtimeRedis = __esm({
12892
12892
  (left, right) => left.exitTimestamp - right.exitTimestamp
12893
12893
  );
12894
12894
  };
12895
+ loadRuntimeEvidenceTrades = async (userName3, { startTime, endTime }) => {
12896
+ const prefix = import_redis4.redisKeys.runtimeTradeBuckets(userName3);
12897
+ const lastDay = (0, import_time.getRuntimeStorageDayKey)(endTime - 1);
12898
+ const keys = (await (0, import_redis4.getKeys)(prefix)).filter((key) => {
12899
+ const day = key.slice(prefix.length);
12900
+ return key.startsWith(prefix) && /^\d{4}-\d{2}-\d{2}$/.test(day) && day <= lastDay;
12901
+ });
12902
+ const retained = /* @__PURE__ */ new Map();
12903
+ for (let offset = 0; offset < keys.length; offset += 16) {
12904
+ const buckets = await Promise.all(
12905
+ keys.slice(offset, offset + 16).map((key) => (0, import_redis4.getHashJsonValues)(key))
12906
+ );
12907
+ for (const row of buckets.flat())
12908
+ if (isRuntimeTradeRecord(row)) retained.set(row.orderId, row);
12909
+ }
12910
+ for (const row of await loadRuntimeClosedTrades(userName3, {
12911
+ startTime,
12912
+ endTime
12913
+ }))
12914
+ retained.set(row.orderId, row);
12915
+ return [...retained.values()].filter(
12916
+ (row) => row.entryTimestamp < endTime && (row.status === "active" || Number.isFinite(row.exitTimestamp) && row.exitTimestamp >= startTime)
12917
+ ).map((row) => {
12918
+ const closesAfterCutoff = row.status === "closed" && row.exitTimestamp >= endTime;
12919
+ if (!closesAfterCutoff && !(row.status === "active" && row.lastSyncedAt >= endTime))
12920
+ return row;
12921
+ return {
12922
+ ...row,
12923
+ status: "active",
12924
+ currentPrice: null,
12925
+ currentPnl: null,
12926
+ closedPnl: null,
12927
+ exitPrice: null,
12928
+ actualExitPrice: null,
12929
+ exitTimestamp: null,
12930
+ exitType: null,
12931
+ closeFee: null,
12932
+ fundingFee: null,
12933
+ totalFee: null,
12934
+ lastSyncedAt: void 0
12935
+ };
12936
+ }).sort((a, b) => a.entryTimestamp - b.entryTimestamp);
12937
+ };
12895
12938
  loadRuntimeActiveTradeOrderIds = async (userName3) => {
12896
12939
  const keys = await (0, import_redis4.getKeys)(import_redis4.redisKeys.runtimeActiveTrades(userName3));
12897
12940
  const refs = await Promise.all(keys.map((key) => (0, import_redis4.getData)(key, null)));
@@ -24601,8 +24644,21 @@ const timer = setInterval(() => {
24601
24644
  try {
24602
24645
  const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
24603
24646
  console.log(result.message);
24647
+ if (result.exitCode !== 0) {
24648
+ let fd;
24649
+ try {
24650
+ fd = fs.openSync(file + '.log', fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
24651
+ const size = fs.fstatSync(fd).size;
24652
+ const tail = Buffer.alloc(Math.min(size, 65536));
24653
+ fs.readSync(fd, tail, 0, tail.length, Math.max(0, size - tail.length));
24654
+ // The worker redacts each line before persisting or returning it through MCP.
24655
+ for (const line of tail.toString('utf8').split(/[\\r\\n]+/).filter(Boolean).slice(-150)) console.error('Host replay: ' + line);
24656
+ } catch (error) {
24657
+ console.error('Host replay log unavailable: ' + error.code);
24658
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
24659
+ }
24604
24660
  clearInterval(timer);
24605
- process.exit(result.exitCode === 0 ? 0 : 1);
24661
+ process.exitCode = result.exitCode === 0 ? 0 : 1;
24606
24662
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
24607
24663
  }, 1000);
24608
24664
  `;
@@ -24850,7 +24906,7 @@ var init_mcpWorker = __esm({
24850
24906
  status: "failed",
24851
24907
  finishedAt: Date.now(),
24852
24908
  updatedAt: Date.now(),
24853
- error: error instanceof Error ? error.message : "Replay preparation failed"
24909
+ error: error instanceof Error ? (0, import_mcp.redactMcpJobLog)(error.message) : "Replay preparation failed"
24854
24910
  },
24855
24911
  import_mcp.MCP_JOB_TTL
24856
24912
  );
@@ -24871,14 +24927,7 @@ var init_mcpWorker = __esm({
24871
24927
  const logs = [];
24872
24928
  const log = (chunk) => {
24873
24929
  const lines = chunk.toString().replace(/\u001b\[[0-9;]*m/g, "").split(/[\r\n]+/).filter(Boolean);
24874
- logs.push(
24875
- ...lines.map(
24876
- (line) => line.slice(0, 1e3).replace(
24877
- /((?:secret|password|api.?key|token|authorization)\s*[:=]\s*)\S+/gi,
24878
- "$1[redacted]"
24879
- )
24880
- )
24881
- );
24930
+ logs.push(...lines.map(import_mcp.redactMcpJobLog));
24882
24931
  if (logs.length > 150) logs.splice(0, logs.length - 150);
24883
24932
  };
24884
24933
  currentChild.stdout?.on("data", log);
@@ -24908,7 +24957,7 @@ var init_mcpWorker = __esm({
24908
24957
  "error",
24909
24958
  () => resolve({ code: null, error: "Unable to start research process" })
24910
24959
  );
24911
- currentChild.once("exit", (code) => resolve({ code }));
24960
+ currentChild.once("close", (code) => resolve({ code }));
24912
24961
  });
24913
24962
  clearTimeout(timeout);
24914
24963
  clearInterval(monitor);
@@ -24925,7 +24974,11 @@ var init_mcpWorker = __esm({
24925
24974
  exitCode: outcome.code,
24926
24975
  updatedAt: Date.now(),
24927
24976
  finishedAt: Date.now(),
24928
- ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 ? { error: outcome.error || "Research process failed" } : {}
24977
+ ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 ? {
24978
+ error: outcome.error || (started.kind === "runtime-feedback-replay" ? logs.find(
24979
+ (line) => line.startsWith("Host replay: ")
24980
+ ) || logs.at(-1) : void 0) || "Research process failed"
24981
+ } : {}
24929
24982
  };
24930
24983
  if (await import_redis15.mcpStorage.compare(jobKey, latest, next, import_mcp.MCP_JOB_TTL))
24931
24984
  break;
@@ -31364,30 +31417,13 @@ var init_runtimeDebugEvidence = __esm({
31364
31417
  }) => {
31365
31418
  const dayKeys = (0, import_time5.getRuntimeStorageDayKeys)(startTime, endTime);
31366
31419
  const dayKeySet = new Set(dayKeys);
31367
- const [
31368
- entryTrades,
31369
- closedTrades,
31370
- signals2,
31371
- evaluations,
31372
- evaluationStatsBuckets,
31373
- lineageScopes
31374
- ] = await Promise.all([
31375
- loadRuntimeTrades(userName3, { startTime, endTime }),
31376
- loadRuntimeClosedTrades(userName3, { startTime, endTime }),
31420
+ const [trades, signals2, evaluations, evaluationStatsBuckets, lineageScopes] = await Promise.all([
31421
+ loadRuntimeEvidenceTrades(userName3, { startTime, endTime }),
31377
31422
  loadRuntimeSignals(userName3, { startTime, endTime }),
31378
31423
  loadRuntimeSignalEvaluations(userName3, { startTime, endTime }),
31379
31424
  loadRuntimeSignalEvaluationStatsBuckets(userName3),
31380
31425
  loadRuntimeLineageScopes(userName3, { startTime, endTime })
31381
31426
  ]);
31382
- const tradesByOrderId = new Map(
31383
- entryTrades.map((trade) => [trade.orderId, trade])
31384
- );
31385
- for (const trade of closedTrades) {
31386
- tradesByOrderId.set(trade.orderId, trade);
31387
- }
31388
- const trades = [...tradesByOrderId.values()].sort(
31389
- (left, right) => left.entryTimestamp - right.entryTimestamp
31390
- );
31391
31427
  const filteredStats = evaluationStatsBuckets.filter(
31392
31428
  (entry) => dayKeySet.has(entry.dayKey) && (!deploymentId || entry.deploymentId === deploymentId) && (!strategies?.length || strategies.includes(entry.strategy))
31393
31429
  );
@@ -33078,12 +33114,26 @@ var init_runtimeEvidenceCompositions = __esm({
33078
33114
  const register = (snapshot) => {
33079
33115
  const compositionId = snapshot.deployment.deploymentCompositionId;
33080
33116
  const known = snapshots.get(compositionId);
33081
- if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
33117
+ if (known && stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment)) {
33082
33118
  throw new Error(
33083
33119
  `Conflicting runtime evidence snapshot for composition ${compositionId}`
33084
33120
  );
33085
33121
  }
33086
- snapshots.set(compositionId, snapshot);
33122
+ const producers = /* @__PURE__ */ new Map();
33123
+ for (const source of [
33124
+ ...known?.producerVariants ?? [],
33125
+ known?.producer,
33126
+ snapshot.producer
33127
+ ]) {
33128
+ if (source) producers.set(producerIdentity(source), source);
33129
+ }
33130
+ const producerVariants = [...producers.values()];
33131
+ snapshots.set(compositionId, {
33132
+ ...known,
33133
+ ...snapshot,
33134
+ producer: producerVariants.length === 1 ? producerVariants[0] : null,
33135
+ ...producerVariants.length > 1 ? { producerVariants } : {}
33136
+ });
33087
33137
  };
33088
33138
  const compositionSnapshots = await discoverRuntimeEvidenceCompositionSnapshots(publishRoot);
33089
33139
  for (const snapshot of compositionSnapshots) {
@@ -33118,7 +33168,7 @@ var init_runtimeEvidenceCompositions = __esm({
33118
33168
  `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
33119
33169
  );
33120
33170
  }
33121
- snapshots.set(currentDeployment.deploymentCompositionId, {
33171
+ register({
33122
33172
  deployment: { ...currentDeployment, tickers: void 0 },
33123
33173
  producer: currentProducer
33124
33174
  });
@@ -33176,6 +33226,14 @@ var init_runtimeEvidenceCompositions = __esm({
33176
33226
  `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
33177
33227
  );
33178
33228
  }
33229
+ for (const compositionId of observed) {
33230
+ const variants = snapshots.get(compositionId)?.producerVariants;
33231
+ if (variants && variants.length > 1) {
33232
+ throw new Error(
33233
+ `Ambiguous runtime evidence producer for composition ${compositionId}: ${variants.map((source) => `${source.projectSha}@${source.imageDigest}`).sort().join(", ")}; runtime lineage has no producer image identity`
33234
+ );
33235
+ }
33236
+ }
33179
33237
  return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer, fallbackTickers }) => {
33180
33238
  const trades = evidence.trades.filter(
33181
33239
  (row) => belongsToSnapshot(row, deployment)
@@ -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
  `;
@@ -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
  );
@@ -143,12 +143,26 @@ var loadRuntimeEvidenceCompositionSnapshots = async ({
143
143
  const register = (snapshot) => {
144
144
  const compositionId = snapshot.deployment.deploymentCompositionId;
145
145
  const known = snapshots.get(compositionId);
146
- if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
146
+ if (known && stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment)) {
147
147
  throw new Error(
148
148
  `Conflicting runtime evidence snapshot for composition ${compositionId}`
149
149
  );
150
150
  }
151
- snapshots.set(compositionId, snapshot);
151
+ const producers = /* @__PURE__ */ new Map();
152
+ for (const source of [
153
+ ...known?.producerVariants ?? [],
154
+ known?.producer,
155
+ snapshot.producer
156
+ ]) {
157
+ if (source) producers.set(producerIdentity(source), source);
158
+ }
159
+ const producerVariants = [...producers.values()];
160
+ snapshots.set(compositionId, {
161
+ ...known,
162
+ ...snapshot,
163
+ producer: producerVariants.length === 1 ? producerVariants[0] : null,
164
+ ...producerVariants.length > 1 ? { producerVariants } : {}
165
+ });
152
166
  };
153
167
  const compositionSnapshots = await discoverRuntimeEvidenceCompositionSnapshots(publishRoot);
154
168
  for (const snapshot of compositionSnapshots) {
@@ -183,7 +197,7 @@ var loadRuntimeEvidenceCompositionSnapshots = async ({
183
197
  `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
184
198
  );
185
199
  }
186
- snapshots.set(currentDeployment.deploymentCompositionId, {
200
+ register({
187
201
  deployment: { ...currentDeployment, tickers: void 0 },
188
202
  producer: currentProducer
189
203
  });
@@ -241,6 +255,14 @@ var splitRuntimeEvidenceByComposition = ({
241
255
  `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
242
256
  );
243
257
  }
258
+ for (const compositionId of observed) {
259
+ const variants = snapshots.get(compositionId)?.producerVariants;
260
+ if (variants && variants.length > 1) {
261
+ throw new Error(
262
+ `Ambiguous runtime evidence producer for composition ${compositionId}: ${variants.map((source) => `${source.projectSha}@${source.imageDigest}`).sort().join(", ")}; runtime lineage has no producer image identity`
263
+ );
264
+ }
265
+ }
244
266
  return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer, fallbackTickers }) => {
245
267
  const trades = evidence.trades.filter(
246
268
  (row) => belongsToSnapshot(row, deployment)
@@ -26,6 +26,7 @@ __export(runtimeRedis_exports, {
26
26
  loadRuntimeActiveTradeOrderIds: () => loadRuntimeActiveTradeOrderIds,
27
27
  loadRuntimeActiveTrades: () => loadRuntimeActiveTrades,
28
28
  loadRuntimeClosedTrades: () => loadRuntimeClosedTrades,
29
+ loadRuntimeEvidenceTrades: () => loadRuntimeEvidenceTrades,
29
30
  loadRuntimeStrategyConfigs: () => import_runtimeStrategyConfigs.loadRuntimeStrategyConfigs,
30
31
  loadRuntimeStrategyNames: () => import_runtimeStrategyConfigs.loadRuntimeStrategyNames,
31
32
  loadRuntimeTrades: () => loadRuntimeTrades,
@@ -104,6 +105,49 @@ var loadRuntimeClosedTrades = async (userName, {
104
105
  (left, right) => left.exitTimestamp - right.exitTimestamp
105
106
  );
106
107
  };
108
+ var loadRuntimeEvidenceTrades = async (userName, { startTime, endTime }) => {
109
+ const prefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
110
+ const lastDay = (0, import_time.getRuntimeStorageDayKey)(endTime - 1);
111
+ const keys = (await (0, import_redis.getKeys)(prefix)).filter((key) => {
112
+ const day = key.slice(prefix.length);
113
+ return key.startsWith(prefix) && /^\d{4}-\d{2}-\d{2}$/.test(day) && day <= lastDay;
114
+ });
115
+ const retained = /* @__PURE__ */ new Map();
116
+ for (let offset = 0; offset < keys.length; offset += 16) {
117
+ const buckets = await Promise.all(
118
+ keys.slice(offset, offset + 16).map((key) => (0, import_redis.getHashJsonValues)(key))
119
+ );
120
+ for (const row of buckets.flat())
121
+ if (isRuntimeTradeRecord(row)) retained.set(row.orderId, row);
122
+ }
123
+ for (const row of await loadRuntimeClosedTrades(userName, {
124
+ startTime,
125
+ endTime
126
+ }))
127
+ retained.set(row.orderId, row);
128
+ return [...retained.values()].filter(
129
+ (row) => row.entryTimestamp < endTime && (row.status === "active" || Number.isFinite(row.exitTimestamp) && row.exitTimestamp >= startTime)
130
+ ).map((row) => {
131
+ const closesAfterCutoff = row.status === "closed" && row.exitTimestamp >= endTime;
132
+ if (!closesAfterCutoff && !(row.status === "active" && row.lastSyncedAt >= endTime))
133
+ return row;
134
+ return {
135
+ ...row,
136
+ status: "active",
137
+ currentPrice: null,
138
+ currentPnl: null,
139
+ closedPnl: null,
140
+ exitPrice: null,
141
+ actualExitPrice: null,
142
+ exitTimestamp: null,
143
+ exitType: null,
144
+ closeFee: null,
145
+ fundingFee: null,
146
+ totalFee: null,
147
+ lastSyncedAt: void 0
148
+ };
149
+ }).sort((a, b) => a.entryTimestamp - b.entryTimestamp);
150
+ };
107
151
  var loadRuntimeActiveTradeOrderIds = async (userName) => {
108
152
  const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeActiveTrades(userName));
109
153
  const refs = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
@@ -144,6 +188,7 @@ var loadStrategyResultSymbols = async ({
144
188
  loadRuntimeActiveTradeOrderIds,
145
189
  loadRuntimeActiveTrades,
146
190
  loadRuntimeClosedTrades,
191
+ loadRuntimeEvidenceTrades,
147
192
  loadRuntimeStrategyConfigs,
148
193
  loadRuntimeStrategyNames,
149
194
  loadRuntimeTrades,
@@ -66,8 +66,21 @@ const timer = setInterval(() => {
66
66
  try {
67
67
  const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
68
68
  console.log(result.message);
69
+ if (result.exitCode !== 0) {
70
+ let fd;
71
+ try {
72
+ fd = fs.openSync(file + '.log', fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
73
+ const size = fs.fstatSync(fd).size;
74
+ const tail = Buffer.alloc(Math.min(size, 65536));
75
+ fs.readSync(fd, tail, 0, tail.length, Math.max(0, size - tail.length));
76
+ // The worker redacts each line before persisting or returning it through MCP.
77
+ for (const line of tail.toString('utf8').split(/[\\r\\n]+/).filter(Boolean).slice(-150)) console.error('Host replay: ' + line);
78
+ } catch (error) {
79
+ console.error('Host replay log unavailable: ' + error.code);
80
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
81
+ }
69
82
  clearInterval(timer);
70
- process.exit(result.exitCode === 0 ? 0 : 1);
83
+ process.exitCode = result.exitCode === 0 ? 0 : 1;
71
84
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
72
85
  }, 1000);
73
86
  `;
@@ -299,7 +312,7 @@ var main = async (options = {}) => {
299
312
  status: "failed",
300
313
  finishedAt: Date.now(),
301
314
  updatedAt: Date.now(),
302
- error: error instanceof Error ? error.message : "Replay preparation failed"
315
+ error: error instanceof Error ? (0, import_mcp.redactMcpJobLog)(error.message) : "Replay preparation failed"
303
316
  },
304
317
  import_mcp.MCP_JOB_TTL
305
318
  );
@@ -320,14 +333,7 @@ var main = async (options = {}) => {
320
333
  const logs = [];
321
334
  const log = (chunk) => {
322
335
  const lines = chunk.toString().replace(/\u001b\[[0-9;]*m/g, "").split(/[\r\n]+/).filter(Boolean);
323
- logs.push(
324
- ...lines.map(
325
- (line) => line.slice(0, 1e3).replace(
326
- /((?:secret|password|api.?key|token|authorization)\s*[:=]\s*)\S+/gi,
327
- "$1[redacted]"
328
- )
329
- )
330
- );
336
+ logs.push(...lines.map(import_mcp.redactMcpJobLog));
331
337
  if (logs.length > 150) logs.splice(0, logs.length - 150);
332
338
  };
333
339
  currentChild.stdout?.on("data", log);
@@ -357,7 +363,7 @@ var main = async (options = {}) => {
357
363
  "error",
358
364
  () => resolve({ code: null, error: "Unable to start research process" })
359
365
  );
360
- currentChild.once("exit", (code) => resolve({ code }));
366
+ currentChild.once("close", (code) => resolve({ code }));
361
367
  });
362
368
  clearTimeout(timeout);
363
369
  clearInterval(monitor);
@@ -374,7 +380,11 @@ var main = async (options = {}) => {
374
380
  exitCode: outcome.code,
375
381
  updatedAt: Date.now(),
376
382
  finishedAt: Date.now(),
377
- ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 ? { error: outcome.error || "Research process failed" } : {}
383
+ ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 ? {
384
+ error: outcome.error || (started.kind === "runtime-feedback-replay" ? logs.find(
385
+ (line) => line.startsWith("Host replay: ")
386
+ ) || logs.at(-1) : void 0) || "Research process failed"
387
+ } : {}
378
388
  };
379
389
  if (await import_redis.mcpStorage.compare(jobKey, latest, next, import_mcp.MCP_JOB_TTL))
380
390
  break;
@@ -55,39 +55,6 @@ var isRuntimeTradeRecord = (value) => {
55
55
  const record = value;
56
56
  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");
57
57
  };
58
- var loadRuntimeTrades = async (userName, {
59
- startTime,
60
- endTime
61
- } = {}) => {
62
- const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
63
- const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
64
- if (hasWindow) {
65
- const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
66
- const bucketTrades = (await Promise.all(
67
- dayKeys.map(
68
- (dayKey) => (0, import_redis.getHashJsonValues)(
69
- import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
70
- )
71
- )
72
- )).flat();
73
- const dedupedBucketTrades = /* @__PURE__ */ new Map();
74
- for (const trade of bucketTrades) {
75
- if (!isRuntimeTradeRecord(trade)) {
76
- continue;
77
- }
78
- dedupedBucketTrades.set(trade.orderId, trade);
79
- }
80
- const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
81
- return windowedTrades;
82
- }
83
- const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
84
- const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
85
- const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
86
- (key) => !key.startsWith(bucketPrefix)
87
- );
88
- const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
89
- return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
90
- };
91
58
  var loadRuntimeClosedTrades = async (userName, {
92
59
  startTime,
93
60
  endTime
@@ -111,6 +78,49 @@ var loadRuntimeClosedTrades = async (userName, {
111
78
  (left, right) => left.exitTimestamp - right.exitTimestamp
112
79
  );
113
80
  };
81
+ var loadRuntimeEvidenceTrades = async (userName, { startTime, endTime }) => {
82
+ const prefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
83
+ const lastDay = (0, import_time.getRuntimeStorageDayKey)(endTime - 1);
84
+ const keys = (await (0, import_redis.getKeys)(prefix)).filter((key) => {
85
+ const day = key.slice(prefix.length);
86
+ return key.startsWith(prefix) && /^\d{4}-\d{2}-\d{2}$/.test(day) && day <= lastDay;
87
+ });
88
+ const retained = /* @__PURE__ */ new Map();
89
+ for (let offset = 0; offset < keys.length; offset += 16) {
90
+ const buckets = await Promise.all(
91
+ keys.slice(offset, offset + 16).map((key) => (0, import_redis.getHashJsonValues)(key))
92
+ );
93
+ for (const row of buckets.flat())
94
+ if (isRuntimeTradeRecord(row)) retained.set(row.orderId, row);
95
+ }
96
+ for (const row of await loadRuntimeClosedTrades(userName, {
97
+ startTime,
98
+ endTime
99
+ }))
100
+ retained.set(row.orderId, row);
101
+ return [...retained.values()].filter(
102
+ (row) => row.entryTimestamp < endTime && (row.status === "active" || Number.isFinite(row.exitTimestamp) && row.exitTimestamp >= startTime)
103
+ ).map((row) => {
104
+ const closesAfterCutoff = row.status === "closed" && row.exitTimestamp >= endTime;
105
+ if (!closesAfterCutoff && !(row.status === "active" && row.lastSyncedAt >= endTime))
106
+ return row;
107
+ return {
108
+ ...row,
109
+ status: "active",
110
+ currentPrice: null,
111
+ currentPnl: null,
112
+ closedPnl: null,
113
+ exitPrice: null,
114
+ actualExitPrice: null,
115
+ exitTimestamp: null,
116
+ exitType: null,
117
+ closeFee: null,
118
+ fundingFee: null,
119
+ totalFee: null,
120
+ lastSyncedAt: void 0
121
+ };
122
+ }).sort((a, b) => a.entryTimestamp - b.entryTimestamp);
123
+ };
114
124
 
115
125
  // src/lib/runtimeSignalsLoader.ts
116
126
  var import_redis2 = require("@tradejs/infra/redis");
@@ -313,30 +323,13 @@ var collectRuntimeDebugEvidence = async ({
313
323
  }) => {
314
324
  const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
315
325
  const dayKeySet = new Set(dayKeys);
316
- const [
317
- entryTrades,
318
- closedTrades,
319
- signals,
320
- evaluations,
321
- evaluationStatsBuckets,
322
- lineageScopes
323
- ] = await Promise.all([
324
- loadRuntimeTrades(userName, { startTime, endTime }),
325
- loadRuntimeClosedTrades(userName, { startTime, endTime }),
326
+ const [trades, signals, evaluations, evaluationStatsBuckets, lineageScopes] = await Promise.all([
327
+ loadRuntimeEvidenceTrades(userName, { startTime, endTime }),
326
328
  loadRuntimeSignals(userName, { startTime, endTime }),
327
329
  loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
328
330
  loadRuntimeSignalEvaluationStatsBuckets(userName),
329
331
  loadRuntimeLineageScopes(userName, { startTime, endTime })
330
332
  ]);
331
- const tradesByOrderId = new Map(
332
- entryTrades.map((trade) => [trade.orderId, trade])
333
- );
334
- for (const trade of closedTrades) {
335
- tradesByOrderId.set(trade.orderId, trade);
336
- }
337
- const trades = [...tradesByOrderId.values()].sort(
338
- (left, right) => left.entryTimestamp - right.entryTimestamp
339
- );
340
333
  const filteredStats = evaluationStatsBuckets.filter(
341
334
  (entry) => dayKeySet.has(entry.dayKey) && (!deploymentId || entry.deploymentId === deploymentId) && (!strategies?.length || strategies.includes(entry.strategy))
342
335
  );
@@ -54,39 +54,6 @@ var isRuntimeTradeRecord = (value) => {
54
54
  const record = value;
55
55
  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");
56
56
  };
57
- var loadRuntimeTrades = async (userName, {
58
- startTime,
59
- endTime
60
- } = {}) => {
61
- const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
62
- const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
63
- if (hasWindow) {
64
- const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
65
- const bucketTrades = (await Promise.all(
66
- dayKeys.map(
67
- (dayKey) => (0, import_redis.getHashJsonValues)(
68
- import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
69
- )
70
- )
71
- )).flat();
72
- const dedupedBucketTrades = /* @__PURE__ */ new Map();
73
- for (const trade of bucketTrades) {
74
- if (!isRuntimeTradeRecord(trade)) {
75
- continue;
76
- }
77
- dedupedBucketTrades.set(trade.orderId, trade);
78
- }
79
- const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
80
- return windowedTrades;
81
- }
82
- const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
83
- const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
84
- const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
85
- (key) => !key.startsWith(bucketPrefix)
86
- );
87
- const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
88
- return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
89
- };
90
57
  var loadRuntimeClosedTrades = async (userName, {
91
58
  startTime,
92
59
  endTime
@@ -110,6 +77,49 @@ var loadRuntimeClosedTrades = async (userName, {
110
77
  (left, right) => left.exitTimestamp - right.exitTimestamp
111
78
  );
112
79
  };
80
+ var loadRuntimeEvidenceTrades = async (userName, { startTime, endTime }) => {
81
+ const prefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
82
+ const lastDay = (0, import_time.getRuntimeStorageDayKey)(endTime - 1);
83
+ const keys = (await (0, import_redis.getKeys)(prefix)).filter((key) => {
84
+ const day = key.slice(prefix.length);
85
+ return key.startsWith(prefix) && /^\d{4}-\d{2}-\d{2}$/.test(day) && day <= lastDay;
86
+ });
87
+ const retained = /* @__PURE__ */ new Map();
88
+ for (let offset = 0; offset < keys.length; offset += 16) {
89
+ const buckets = await Promise.all(
90
+ keys.slice(offset, offset + 16).map((key) => (0, import_redis.getHashJsonValues)(key))
91
+ );
92
+ for (const row of buckets.flat())
93
+ if (isRuntimeTradeRecord(row)) retained.set(row.orderId, row);
94
+ }
95
+ for (const row of await loadRuntimeClosedTrades(userName, {
96
+ startTime,
97
+ endTime
98
+ }))
99
+ retained.set(row.orderId, row);
100
+ return [...retained.values()].filter(
101
+ (row) => row.entryTimestamp < endTime && (row.status === "active" || Number.isFinite(row.exitTimestamp) && row.exitTimestamp >= startTime)
102
+ ).map((row) => {
103
+ const closesAfterCutoff = row.status === "closed" && row.exitTimestamp >= endTime;
104
+ if (!closesAfterCutoff && !(row.status === "active" && row.lastSyncedAt >= endTime))
105
+ return row;
106
+ return {
107
+ ...row,
108
+ status: "active",
109
+ currentPrice: null,
110
+ currentPnl: null,
111
+ closedPnl: null,
112
+ exitPrice: null,
113
+ actualExitPrice: null,
114
+ exitTimestamp: null,
115
+ exitType: null,
116
+ closeFee: null,
117
+ fundingFee: null,
118
+ totalFee: null,
119
+ lastSyncedAt: void 0
120
+ };
121
+ }).sort((a, b) => a.entryTimestamp - b.entryTimestamp);
122
+ };
113
123
 
114
124
  // src/lib/runtimeSignalsLoader.ts
115
125
  var import_redis2 = require("@tradejs/infra/redis");
@@ -312,30 +322,13 @@ var collectRuntimeDebugEvidence = async ({
312
322
  }) => {
313
323
  const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
314
324
  const dayKeySet = new Set(dayKeys);
315
- const [
316
- entryTrades,
317
- closedTrades,
318
- signals,
319
- evaluations,
320
- evaluationStatsBuckets,
321
- lineageScopes
322
- ] = await Promise.all([
323
- loadRuntimeTrades(userName, { startTime, endTime }),
324
- loadRuntimeClosedTrades(userName, { startTime, endTime }),
325
+ const [trades, signals, evaluations, evaluationStatsBuckets, lineageScopes] = await Promise.all([
326
+ loadRuntimeEvidenceTrades(userName, { startTime, endTime }),
325
327
  loadRuntimeSignals(userName, { startTime, endTime }),
326
328
  loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
327
329
  loadRuntimeSignalEvaluationStatsBuckets(userName),
328
330
  loadRuntimeLineageScopes(userName, { startTime, endTime })
329
331
  ]);
330
- const tradesByOrderId = new Map(
331
- entryTrades.map((trade) => [trade.orderId, trade])
332
- );
333
- for (const trade of closedTrades) {
334
- tradesByOrderId.set(trade.orderId, trade);
335
- }
336
- const trades = [...tradesByOrderId.values()].sort(
337
- (left, right) => left.entryTimestamp - right.entryTimestamp
338
- );
339
332
  const filteredStats = evaluationStatsBuckets.filter(
340
333
  (entry) => dayKeySet.has(entry.dayKey) && (!deploymentId || entry.deploymentId === deploymentId) && (!strategies?.length || strategies.includes(entry.strategy))
341
334
  );
@@ -491,12 +484,26 @@ var loadRuntimeEvidenceCompositionSnapshots = async ({
491
484
  const register = (snapshot) => {
492
485
  const compositionId = snapshot.deployment.deploymentCompositionId;
493
486
  const known = snapshots.get(compositionId);
494
- if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
487
+ if (known && stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment)) {
495
488
  throw new Error(
496
489
  `Conflicting runtime evidence snapshot for composition ${compositionId}`
497
490
  );
498
491
  }
499
- snapshots.set(compositionId, snapshot);
492
+ const producers = /* @__PURE__ */ new Map();
493
+ for (const source of [
494
+ ...known?.producerVariants ?? [],
495
+ known?.producer,
496
+ snapshot.producer
497
+ ]) {
498
+ if (source) producers.set(producerIdentity(source), source);
499
+ }
500
+ const producerVariants = [...producers.values()];
501
+ snapshots.set(compositionId, {
502
+ ...known,
503
+ ...snapshot,
504
+ producer: producerVariants.length === 1 ? producerVariants[0] : null,
505
+ ...producerVariants.length > 1 ? { producerVariants } : {}
506
+ });
500
507
  };
501
508
  const compositionSnapshots = await discoverRuntimeEvidenceCompositionSnapshots(publishRoot);
502
509
  for (const snapshot of compositionSnapshots) {
@@ -531,7 +538,7 @@ var loadRuntimeEvidenceCompositionSnapshots = async ({
531
538
  `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
532
539
  );
533
540
  }
534
- snapshots.set(currentDeployment.deploymentCompositionId, {
541
+ register({
535
542
  deployment: { ...currentDeployment, tickers: void 0 },
536
543
  producer: currentProducer
537
544
  });
@@ -589,6 +596,14 @@ var splitRuntimeEvidenceByComposition = ({
589
596
  `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
590
597
  );
591
598
  }
599
+ for (const compositionId of observed) {
600
+ const variants = snapshots.get(compositionId)?.producerVariants;
601
+ if (variants && variants.length > 1) {
602
+ throw new Error(
603
+ `Ambiguous runtime evidence producer for composition ${compositionId}: ${variants.map((source) => `${source.projectSha}@${source.imageDigest}`).sort().join(", ")}; runtime lineage has no producer image identity`
604
+ );
605
+ }
606
+ }
592
607
  return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer, fallbackTickers }) => {
593
608
  const trades = evidence.trades.filter(
594
609
  (row) => belongsToSnapshot(row, deployment)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "3.1.29-beta.280",
3
+ "version": "3.1.29-beta.282",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -37,13 +37,13 @@
37
37
  "tradejs": "dist/cli.js"
38
38
  },
39
39
  "dependencies": {
40
- "@tradejs/connectors": "^3.1.29-beta.280",
41
- "@tradejs/core": "^3.1.29-beta.280",
42
- "@tradejs/indicators": "^3.1.29-beta.280",
43
- "@tradejs/infra": "^3.1.29-beta.280",
44
- "@tradejs/node": "^3.1.29-beta.280",
40
+ "@tradejs/connectors": "^3.1.29-beta.282",
41
+ "@tradejs/core": "^3.1.29-beta.282",
42
+ "@tradejs/indicators": "^3.1.29-beta.282",
43
+ "@tradejs/infra": "^3.1.29-beta.282",
44
+ "@tradejs/node": "^3.1.29-beta.282",
45
45
  "@tradejs/strategy-kit": "3.0.6",
46
- "@tradejs/types": "^3.1.29-beta.280",
46
+ "@tradejs/types": "^3.1.29-beta.282",
47
47
  "args": "^5.0.3",
48
48
  "bcryptjs": "^2.4.3",
49
49
  "chalk": "4.1.2",