@tradejs/node 3.1.29-beta.262 → 3.1.29-beta.264

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/mcp.mjs ADDED
@@ -0,0 +1,584 @@
1
+ import "./chunk-JQOLTLEY.mjs";
2
+ import "./chunk-ZUMUI37V.mjs";
3
+ import {
4
+ getConnectorCreatorByProvider
5
+ } from "./chunk-QVKUROR2.mjs";
6
+ import {
7
+ listRuntimeDeployments,
8
+ loadResolvedRuntimeStrategies
9
+ } from "./chunk-BO42QWAB.mjs";
10
+ import "./chunk-SKY2HNZU.mjs";
11
+ import {
12
+ ensureIndicatorPluginsLoaded
13
+ } from "./chunk-UL5Z46JL.mjs";
14
+ import "./chunk-LZDXRXIU.mjs";
15
+ import "./chunk-YR55Q355.mjs";
16
+ import "./chunk-GHSLOJLE.mjs";
17
+ import "./chunk-Y6FXYEAI.mjs";
18
+
19
+ // src/mcp.ts
20
+ import { createHash as createHash2, randomUUID } from "crypto";
21
+ import { createIndicators } from "@tradejs/core/indicators";
22
+ import { intervalToMs } from "@tradejs/core/data";
23
+ import {
24
+ getDataStrict as getDataStrict2,
25
+ getHashData,
26
+ getHashJsonField,
27
+ mcpStorage as mcpStorage2,
28
+ redisKeys as redisKeys2,
29
+ scanDataKeys,
30
+ scanHashJson
31
+ } from "@tradejs/infra/redis";
32
+ import { getRuntimeDeploymentHeartbeat } from "@tradejs/infra/runtimeHeartbeats";
33
+ import { getTradingAccount } from "@tradejs/infra/tradingAccounts";
34
+ import { getRuntimeStorageDayKey } from "@tradejs/core/time";
35
+
36
+ // src/mcpJobs.ts
37
+ import { createHash } from "crypto";
38
+ import { readFile } from "fs/promises";
39
+ import path from "path";
40
+ import { getDataStrict, mcpStorage, redisKeys } from "@tradejs/infra/redis";
41
+ var MCP_JOB_TTL = 7 * 86400;
42
+ var hash = (value) => createHash("sha256").update(value).digest("hex");
43
+ var mcpJobStorageId = (userName, id) => `jobs:${hash(userName)}:${id}`;
44
+ var mcpJobDirectory = (job) => path.join(
45
+ String(process.env.PROJECT_CWD || process.cwd()),
46
+ "data/mcp",
47
+ hash(job.userName),
48
+ job.id
49
+ );
50
+ var mcpPackageManifestHash = async () => hash(
51
+ await readFile(
52
+ path.join(
53
+ String(process.env.PROJECT_CWD || process.cwd()),
54
+ "runtime-package-manifest.json"
55
+ ),
56
+ "utf8"
57
+ )
58
+ );
59
+ var assertWindow = (startTime, endTime, maxDays) => {
60
+ if (!Number.isSafeInteger(startTime) || !Number.isSafeInteger(endTime) || startTime <= 0 || endTime <= startTime || endTime > Date.now() || endTime - startTime > maxDays * 864e5)
61
+ throw new Error(`Select a past window of at most ${maxDays} days`);
62
+ };
63
+ var enqueueMcpJob = async (principal, kind, request, idempotencyKey) => {
64
+ if (!/^[A-Za-z0-9_-]{8,100}$/.test(idempotencyKey))
65
+ throw new Error(
66
+ "Provide an idempotencyKey of 8\u2013100 letters, digits, underscores or hyphens"
67
+ );
68
+ const id = hash(
69
+ `${principal.userName}:${principal.clientId}:${idempotencyKey}`
70
+ );
71
+ const storageId = mcpJobStorageId(principal.userName, id);
72
+ const existing = await mcpStorage.get(storageId);
73
+ if (existing) {
74
+ if (existing.kind !== kind || existing.request.requestFingerprint !== hash(JSON.stringify(request)))
75
+ throw new Error("Idempotency key was used for another request");
76
+ return existing;
77
+ }
78
+ const worker = await mcpStorage.get(
79
+ "worker:heartbeat"
80
+ );
81
+ if (!worker) throw new Error("MCP worker is unavailable; no job was queued");
82
+ if (!await mcpStorage.rateLimit(`jobs:${hash(principal.userName)}`, 10, 3600))
83
+ throw new Error("Hourly job limit reached");
84
+ const job = {
85
+ id,
86
+ userName: principal.userName,
87
+ clientId: principal.clientId,
88
+ kind,
89
+ status: "queued",
90
+ request: { ...request, requestFingerprint: hash(JSON.stringify(request)) },
91
+ args: [],
92
+ createdAt: Date.now(),
93
+ updatedAt: Date.now(),
94
+ logs: [],
95
+ outputId: id,
96
+ packageManifestSha256: await mcpPackageManifestHash()
97
+ };
98
+ const outputDir = mcpJobDirectory(job);
99
+ if (kind === "backtest") {
100
+ assertWindow(Number(request.startTime), Number(request.endTime), 90);
101
+ const configId = String(request.configId || "");
102
+ if (!/^[A-Za-z][A-Za-z0-9]*:[A-Za-z0-9_-]+$/.test(configId))
103
+ throw new Error("Select a named Strategy:config");
104
+ const grid = await getDataStrict(
105
+ redisKeys.backtestConfig(principal.userName, configId)
106
+ );
107
+ if (!grid || typeof grid !== "object" || Array.isArray(grid))
108
+ throw new Error("Backtest config not found");
109
+ const cells = Object.values(grid);
110
+ if (!cells.length || !cells.every((cell) => Array.isArray(cell) && cell.length > 0) || cells.reduce((n, cell) => n * cell.length, 1) > 8)
111
+ throw new Error("MCP backtests allow at most eight config combinations");
112
+ const symbols = request.symbols;
113
+ if (!Array.isArray(symbols) || !symbols.length || symbols.length > 20 || !symbols.every(
114
+ (symbol) => typeof symbol === "string" && /^[A-Z0-9._-]{1,40}$/.test(symbol)
115
+ ))
116
+ throw new Error("Select 1\u201320 symbols");
117
+ const frozenConfigId = `${configId.split(":")[0]}:mcp_${id}`;
118
+ job.request = {
119
+ ...job.request,
120
+ resolvedConfig: grid,
121
+ configSha256: hash(JSON.stringify(grid)),
122
+ frozenConfigId
123
+ };
124
+ job.args = [
125
+ "backtest",
126
+ "--user",
127
+ principal.userName,
128
+ "--config",
129
+ frozenConfigId,
130
+ "--connector",
131
+ String(request.provider),
132
+ "--timeframe",
133
+ String(request.interval),
134
+ "--startTime",
135
+ String(request.startTime),
136
+ "--endTime",
137
+ String(request.endTime),
138
+ "--tickers",
139
+ symbols.join(","),
140
+ "--parallel",
141
+ "1",
142
+ "--cacheOnly",
143
+ "--fast",
144
+ "--persistResults"
145
+ ];
146
+ } else if (kind === "runtime-evidence") {
147
+ assertWindow(Number(request.startTime), Number(request.endTime), 7);
148
+ job.args = [
149
+ "runtime-evidence",
150
+ "--user",
151
+ principal.userName,
152
+ "--deployment",
153
+ String(request.deploymentId),
154
+ "--startTime",
155
+ String(request.startTime),
156
+ "--endTime",
157
+ String(request.endTime),
158
+ "--publishDir",
159
+ outputDir
160
+ ];
161
+ } else if (kind === "runtime-feedback-replay") {
162
+ if (!worker.replayAvailable)
163
+ throw new Error("MCP replay worker requires an isolated Redis host");
164
+ if (typeof request.verifiedEvidencePath !== "string")
165
+ throw new Error("Select verified runtime evidence");
166
+ job.args = [
167
+ "runtime-feedback-replay",
168
+ "--runtimeEvidence",
169
+ request.verifiedEvidencePath,
170
+ "--outDir",
171
+ outputDir,
172
+ "--runId",
173
+ id
174
+ ];
175
+ } else {
176
+ throw new Error(
177
+ "Unsupported diagnostic kind; use verified runtime feedback for deployment-bound parity"
178
+ );
179
+ }
180
+ if (!await mcpStorage.put(storageId, job, MCP_JOB_TTL, true)) {
181
+ const winner = await readMcpJob(principal.userName, id);
182
+ if (winner.kind !== kind || winner.request.requestFingerprint !== job.request.requestFingerprint)
183
+ throw new Error("Idempotency key was used for another request");
184
+ return winner;
185
+ }
186
+ return job;
187
+ };
188
+ var readMcpJob = async (userName, id) => {
189
+ if (!/^[a-f0-9]{64}$/.test(id)) throw new Error("Invalid job id");
190
+ const job = await mcpStorage.get(mcpJobStorageId(userName, id));
191
+ if (!job || job.userName !== userName) throw new Error("Job not found");
192
+ return job;
193
+ };
194
+ var cancelMcpJob = async (principal, id) => {
195
+ for (let attempt = 0; attempt < 4; attempt++) {
196
+ const job = await readMcpJob(principal.userName, id);
197
+ if (job.clientId !== principal.clientId)
198
+ throw new Error("Only the submitting client can cancel this job");
199
+ if (!["queued", "running"].includes(job.status)) return job;
200
+ const next = {
201
+ ...job,
202
+ cancelRequested: true,
203
+ updatedAt: Date.now(),
204
+ ...job.status === "queued" ? { status: "cancelled", finishedAt: Date.now() } : {}
205
+ };
206
+ if (await mcpStorage.compare(
207
+ mcpJobStorageId(job.userName, job.id),
208
+ job,
209
+ next,
210
+ MCP_JOB_TTL
211
+ ))
212
+ return next;
213
+ }
214
+ throw new Error("Job changed; retry cancellation");
215
+ };
216
+ var publicMcpJob = (job) => {
217
+ const { args: _args, logs: _logs, request: _request, ...view } = job;
218
+ return {
219
+ ...view,
220
+ request: Object.fromEntries(
221
+ Object.entries(job.request).filter(
222
+ ([key]) => !["verifiedEvidencePath", "resolvedConfig"].includes(key)
223
+ )
224
+ ),
225
+ logLines: job.logs.length
226
+ };
227
+ };
228
+
229
+ // src/mcp.ts
230
+ var mcpProjectRoot = () => String(process.env.PROJECT_CWD || process.cwd());
231
+ var isMcpRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
232
+ var mcpId = (value) => createHash2("sha256").update(value).digest("hex");
233
+ var redactMcpData = (value) => {
234
+ if (Array.isArray(value)) return value.map(redactMcpData);
235
+ if (!isMcpRecord(value)) return value;
236
+ return Object.fromEntries(
237
+ Object.entries(value).filter(
238
+ ([key]) => !/secret|password|authorization|api.?key|private.?key|access.?token|refresh.?token|bot.?token|cookie|credential/i.test(
239
+ key
240
+ )
241
+ ).map(([key, nested]) => [key, redactMcpData(nested)])
242
+ );
243
+ };
244
+ var connectorFor = async (userName, provider, universe) => {
245
+ const creator = await getConnectorCreatorByProvider(
246
+ provider,
247
+ mcpProjectRoot()
248
+ );
249
+ if (!creator) throw new Error("Unknown market provider");
250
+ return creator({ userName, universe });
251
+ };
252
+ var readMcpMarket = async (userName, provider, universe, offset, limit, symbol) => {
253
+ const connector = await connectorFor(userName, provider, universe);
254
+ const tickers = await connector.getTickers();
255
+ const rows = symbol ? tickers.filter((row) => row.symbol === symbol) : tickers;
256
+ return {
257
+ provider,
258
+ universe,
259
+ items: rows.slice(offset, offset + limit),
260
+ total: rows.length,
261
+ nextOffset: offset + limit < rows.length ? offset + limit : null,
262
+ observedAt: Date.now(),
263
+ source: "connector"
264
+ };
265
+ };
266
+ var readMcpChart = async (userName, request) => {
267
+ const connector = await connectorFor(
268
+ userName,
269
+ request.provider,
270
+ request.universe
271
+ );
272
+ const width = intervalToMs(request.interval);
273
+ const end = Math.floor(Math.min(request.endTime, Date.now()) / width) * width;
274
+ const visibleStart = end - request.bars * width;
275
+ const start = Math.max(0, visibleStart - 200 * width);
276
+ const fetched = await connector.kline({
277
+ symbol: request.symbol,
278
+ interval: request.interval,
279
+ start,
280
+ end,
281
+ cacheOnly: request.cacheOnly
282
+ });
283
+ const candles = fetched.filter(
284
+ (candle) => candle.timestamp >= start && candle.timestamp < end
285
+ );
286
+ const btc = request.universe === "tradfi" ? [] : request.symbol === "BTCUSDT" ? candles : await connector.kline({
287
+ symbol: "BTCUSDT",
288
+ interval: request.interval,
289
+ start,
290
+ end,
291
+ cacheOnly: request.cacheOnly
292
+ });
293
+ const visibleCandles = candles.filter((candle) => candle.timestamp >= visibleStart).slice(-request.bars);
294
+ await ensureIndicatorPluginsLoaded(mcpProjectRoot());
295
+ const allIndicators = createIndicators(
296
+ candles,
297
+ btc.filter((candle) => candle.timestamp < end),
298
+ {
299
+ includeMlPayload: false,
300
+ pluginRegistryScope: mcpProjectRoot()
301
+ }
302
+ ).result();
303
+ return {
304
+ ...request,
305
+ startTime: visibleStart,
306
+ historyStartTime: start,
307
+ warmupBars: 200,
308
+ endTime: end,
309
+ candles: visibleCandles,
310
+ indicators: Object.fromEntries(
311
+ (request.indicatorNames || ["maFast", "maSlow", "atr", "macd"]).map(
312
+ (key) => [
313
+ key,
314
+ Array.isArray(allIndicators[key]) ? allIndicators[key].slice(
315
+ -request.bars
316
+ ) : allIndicators[key] ?? null
317
+ ]
318
+ )
319
+ ),
320
+ availableIndicators: Object.keys(allIndicators),
321
+ observedAt: Date.now(),
322
+ source: request.cacheOnly ? "cached_history" : "connector",
323
+ closedCandlesOnly: true,
324
+ coverage: {
325
+ requestedBars: request.bars,
326
+ availableBars: candles.filter(
327
+ (candle) => candle.timestamp >= visibleStart
328
+ ).length,
329
+ complete: candles.filter((candle) => candle.timestamp >= visibleStart).length >= request.bars
330
+ }
331
+ };
332
+ };
333
+ var readMcpDeployments = async (userName) => {
334
+ const deployments = await listRuntimeDeployments({
335
+ userName,
336
+ projectRoot: mcpProjectRoot()
337
+ });
338
+ const allowed = [];
339
+ for (const deployment of deployments) {
340
+ if (!await getTradingAccount(userName, deployment.accountId)) continue;
341
+ allowed.push({
342
+ ...deployment,
343
+ heartbeat: await getRuntimeDeploymentHeartbeat(userName, deployment.id)
344
+ });
345
+ }
346
+ return {
347
+ deployments: allowed,
348
+ observedAt: Date.now(),
349
+ source: "project_declaration_and_runtime_storage"
350
+ };
351
+ };
352
+ var requireMcpDeployment = async (userName, deploymentId) => {
353
+ const { deployments } = await readMcpDeployments(userName);
354
+ const deployment = deployments.find((row) => row.id === deploymentId);
355
+ if (!deployment) throw new Error("Deployment unavailable for this user");
356
+ return deployment;
357
+ };
358
+ var readMcpStrategies = async (userName, deploymentId) => {
359
+ const deployment = await requireMcpDeployment(userName, deploymentId);
360
+ const rows = await loadResolvedRuntimeStrategies({
361
+ userName,
362
+ projectRoot: mcpProjectRoot(),
363
+ deploymentId
364
+ });
365
+ return {
366
+ deploymentId,
367
+ accountId: deployment.accountId,
368
+ strategies: rows.map(({ strategyCreator: _creator, ...row }) => row),
369
+ observedAt: Date.now()
370
+ };
371
+ };
372
+ var readMcpRuntimePage = async (userName, kind, request) => {
373
+ const deployment = await requireMcpDeployment(userName, request.deploymentId);
374
+ const prefix = kind === "signals" ? redisKeys2.runtimeSignalBuckets(userName) : kind === "evaluations" ? redisKeys2.runtimeSignalEvaluationBuckets(userName) : redisKeys2.runtimeTrades(userName);
375
+ const { cursor: _cursor, ...filters } = request;
376
+ const binding = mcpId(JSON.stringify({ userName, kind, ...filters }));
377
+ let state = {
378
+ binding,
379
+ scan: "0",
380
+ keys: [],
381
+ hash: "0",
382
+ values: [],
383
+ scanned: false
384
+ };
385
+ if (request.cursor !== "0") {
386
+ const saved = await mcpStorage2.get(
387
+ `cursor:${request.cursor}`
388
+ );
389
+ if (!saved || saved.binding !== binding)
390
+ throw new Error("Invalid storage cursor");
391
+ state = saved;
392
+ }
393
+ if (!state.keys.length && !state.values.length && (!state.scanned || state.scan !== "0")) {
394
+ const page = await scanDataKeys(prefix, state.scan);
395
+ state = { ...state, scan: page.cursor, keys: page.keys, scanned: true };
396
+ }
397
+ if (!state.values.length && state.keys.length) {
398
+ const key = state.keys[0];
399
+ if (kind === "orders") {
400
+ state.keys.shift();
401
+ if (!key.slice(prefix.length).startsWith("days:"))
402
+ state.values = [await getDataStrict2(key)];
403
+ } else {
404
+ const page = await scanHashJson(key, state.hash);
405
+ state.values = page.values;
406
+ state.hash = page.cursor;
407
+ if (page.cursor === "0") state.keys.shift();
408
+ }
409
+ }
410
+ const items = [];
411
+ for (let row of state.values.splice(0, 50)) {
412
+ if (kind === "signals" && isMcpRecord(row) && typeof row.symbol === "string" && typeof row.signalId === "string")
413
+ row = await getDataStrict2(
414
+ redisKeys2.storeSignal(row.symbol, row.signalId)
415
+ );
416
+ if (!isMcpRecord(row) || row.deploymentId !== request.deploymentId || row.accountId !== deployment.accountId || row.userName !== void 0 && row.userName !== userName || request.strategy && row.strategy !== request.strategy || request.symbol && row.symbol !== request.symbol)
417
+ continue;
418
+ const timestamp = Number(
419
+ row.timestamp ?? row.entryTimestamp ?? row.createdAt
420
+ );
421
+ if (timestamp < request.startTime || timestamp >= request.endTime || !Number.isFinite(timestamp))
422
+ continue;
423
+ const {
424
+ figures: _figures,
425
+ indicators: _indicators,
426
+ additionalIndicators: _additional,
427
+ ...summary
428
+ } = row;
429
+ items.push(summary);
430
+ }
431
+ let next = "0";
432
+ if (state.values.length || state.keys.length || state.scan !== "0") {
433
+ next = mcpId(randomUUID());
434
+ await mcpStorage2.put(`cursor:${next}`, state, 600);
435
+ }
436
+ return {
437
+ items,
438
+ cursor: next,
439
+ deploymentId: request.deploymentId,
440
+ observedAt: Date.now(),
441
+ source: "runtime_storage",
442
+ window: { startTime: request.startTime, endTime: request.endTime },
443
+ coverage: "stored_records_only",
444
+ note: "Missing records do not prove no evaluation occurred. Continue until cursor is 0; cursors expire after 10 minutes."
445
+ };
446
+ };
447
+ var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy) => {
448
+ const deployment = await requireMcpDeployment(userName, deploymentId);
449
+ const ref = await getHashJsonField(
450
+ redisKeys2.runtimeSignalBucket(
451
+ userName,
452
+ getRuntimeStorageDayKey(timestamp),
453
+ strategy
454
+ ),
455
+ signalId,
456
+ null
457
+ );
458
+ if (!isMcpRecord(ref) || typeof ref.symbol !== "string")
459
+ throw new Error("Signal not found");
460
+ const signal = await getDataStrict2(
461
+ redisKeys2.storeSignal(ref.symbol, signalId)
462
+ );
463
+ if (!isMcpRecord(signal) || signal.deploymentId !== deploymentId || signal.accountId !== deployment.accountId || signal.userName !== void 0 && signal.userName !== userName)
464
+ throw new Error("Signal not found");
465
+ return { signal, observedAt: Date.now(), source: "runtime_storage" };
466
+ };
467
+ var readMcpEvaluationStats = async (userName, deploymentId, cursor) => {
468
+ await requireMcpDeployment(userName, deploymentId);
469
+ const page = await scanDataKeys(
470
+ redisKeys2.runtimeSignalEvaluationStatsBuckets(userName),
471
+ cursor
472
+ );
473
+ const buckets = [];
474
+ for (const key of page.keys) {
475
+ const parts = key.slice(redisKeys2.runtimeSignalEvaluationStatsBuckets(userName).length).split(":");
476
+ if (parts.slice(1, -1).join(":") !== deploymentId) continue;
477
+ buckets.push({
478
+ bucket: key.slice(
479
+ redisKeys2.runtimeSignalEvaluationStatsBuckets(userName).length
480
+ ),
481
+ counters: await getHashData(key)
482
+ });
483
+ }
484
+ return {
485
+ buckets,
486
+ cursor: page.cursor,
487
+ observedAt: Date.now(),
488
+ source: "debug_telemetry",
489
+ immutableEvidence: false,
490
+ note: "Aggregate counters are not composition-bound evidence. Detailed skip records may not have been captured."
491
+ };
492
+ };
493
+ var readMcpBacktestPage = async (userName, kind, cursor) => {
494
+ const prefix = kind === "configs" ? `users:${userName}:backtests:configs:` : `users:${userName}:backtests:results:`;
495
+ const page = await scanDataKeys(prefix, cursor);
496
+ const items = [];
497
+ for (const key of page.keys) {
498
+ const value = await getDataStrict2(key);
499
+ items.push({
500
+ id: key.slice(prefix.length),
501
+ value: kind === "results" && isMcpRecord(value) ? {
502
+ config: value.config,
503
+ mode: value.mode,
504
+ startedAt: value.startedAt,
505
+ finishedAt: value.finishedAt,
506
+ successTests: value.successTests,
507
+ errorTests: value.errorTests,
508
+ resultCount: Array.isArray(value.results) ? value.results.length : 0
509
+ } : value
510
+ });
511
+ }
512
+ return {
513
+ items,
514
+ cursor: page.cursor,
515
+ observedAt: Date.now(),
516
+ source: "backtest_storage"
517
+ };
518
+ };
519
+ var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
520
+ if (id.includes("*") || id.includes("?") || id.includes("["))
521
+ throw new Error("Invalid result identifier");
522
+ const result = await getDataStrict2(
523
+ `users:${userName}:backtests:results:${id}`
524
+ );
525
+ if (!result) throw new Error("Result not found");
526
+ const record = isMcpRecord(result) ? result : { results: result };
527
+ const groups = ["results", "resultsByTickers", "resultsByStrategies"];
528
+ const pageGroup = (group) => Array.isArray(record[group]) ? record[group].slice(offset, offset + limit) : record[group];
529
+ const paged = {
530
+ results: pageGroup("results"),
531
+ resultsByTickers: pageGroup("resultsByTickers"),
532
+ resultsByStrategies: pageGroup("resultsByStrategies")
533
+ };
534
+ const total = Math.max(
535
+ 0,
536
+ ...groups.map(
537
+ (group) => Array.isArray(record[group]) ? record[group].length : 0
538
+ )
539
+ );
540
+ return {
541
+ id,
542
+ result: {
543
+ config: record.config,
544
+ mode: record.mode,
545
+ startedAt: record.startedAt,
546
+ finishedAt: record.finishedAt,
547
+ successTests: record.successTests,
548
+ errorTests: record.errorTests,
549
+ bestConfig: record.bestConfig,
550
+ mergedConfig: record.mergedConfig,
551
+ runtimeComparison: record.runtimeComparison,
552
+ ...paged
553
+ },
554
+ offset,
555
+ total,
556
+ nextOffset: offset + limit < total ? offset + limit : null,
557
+ observedAt: Date.now(),
558
+ source: "backtest_storage"
559
+ };
560
+ };
561
+ export {
562
+ MCP_JOB_TTL,
563
+ cancelMcpJob,
564
+ enqueueMcpJob,
565
+ isMcpRecord,
566
+ mcpId,
567
+ mcpJobDirectory,
568
+ mcpJobStorageId,
569
+ mcpPackageManifestHash,
570
+ mcpProjectRoot,
571
+ publicMcpJob,
572
+ readMcpBacktestPage,
573
+ readMcpBacktestResult,
574
+ readMcpChart,
575
+ readMcpDeployments,
576
+ readMcpEvaluationStats,
577
+ readMcpJob,
578
+ readMcpMarket,
579
+ readMcpRuntimePage,
580
+ readMcpSignal,
581
+ readMcpStrategies,
582
+ redactMcpData,
583
+ requireMcpDeployment
584
+ };
package/dist/registry.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import "./chunk-JQOLTLEY.mjs";
1
2
  import "./chunk-SKY2HNZU.mjs";
2
3
  import {
3
4
  ensureIndicatorPluginsLoaded,
@@ -9201,7 +9201,7 @@ var groupRuntimeTradesByIdentity = (trades, identities) => {
9201
9201
  const grouped = /* @__PURE__ */ new Map();
9202
9202
  for (const trade of trades) {
9203
9203
  const matches = [...identities.entries()].filter(([, identity]) => {
9204
- if (trade.strategy !== identity.strategyName || String(trade.interval) !== String(identity.interval)) {
9204
+ if (trade.strategy !== identity.strategyName || trade.interval != null && String(trade.interval) !== String(identity.interval)) {
9205
9205
  return false;
9206
9206
  }
9207
9207
  if (trade.deploymentId && trade.deploymentId !== identity.deploymentId) {
@@ -1,7 +1,3 @@
1
- import {
2
- listRuntimeDeployments,
3
- loadResolvedRuntimeStrategies
4
- } from "./chunk-BO42QWAB.mjs";
5
1
  import {
6
2
  buildExchangeFallbackRuntimeTrades,
7
3
  takeClosedPnlMatch
@@ -9,6 +5,10 @@ import {
9
5
  import {
10
6
  getConnectorCreatorByProvider
11
7
  } from "./chunk-QVKUROR2.mjs";
8
+ import {
9
+ listRuntimeDeployments,
10
+ loadResolvedRuntimeStrategies
11
+ } from "./chunk-BO42QWAB.mjs";
12
12
  import "./chunk-SKY2HNZU.mjs";
13
13
  import "./chunk-UL5Z46JL.mjs";
14
14
  import "./chunk-LZDXRXIU.mjs";
@@ -266,7 +266,7 @@ var groupRuntimeTradesByIdentity = (trades, identities) => {
266
266
  const grouped = /* @__PURE__ */ new Map();
267
267
  for (const trade of trades) {
268
268
  const matches = [...identities.entries()].filter(([, identity]) => {
269
- if (trade.strategy !== identity.strategyName || String(trade.interval) !== String(identity.interval)) {
269
+ if (trade.strategy !== identity.strategyName || trade.interval != null && String(trade.interval) !== String(identity.interval)) {
270
270
  return false;
271
271
  }
272
272
  if (trade.deploymentId && trade.deploymentId !== identity.deploymentId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/node",
3
- "version": "3.1.29-beta.262",
3
+ "version": "3.1.29-beta.264",
4
4
  "description": "Node-only runtime for the TradeJS TypeScript framework: strategies, backtests, Pine strategy loading, and plugin registries.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -90,14 +90,24 @@
90
90
  "types": "./dist/jev.d.ts",
91
91
  "import": "./dist/jev.mjs",
92
92
  "require": "./dist/jev.js"
93
+ },
94
+ "./mcp": {
95
+ "types": "./dist/mcp.d.ts",
96
+ "import": "./dist/mcp.mjs",
97
+ "require": "./dist/mcp.js"
98
+ },
99
+ "./diagnostics": {
100
+ "types": "./dist/diagnostics.d.ts",
101
+ "import": "./dist/diagnostics.mjs",
102
+ "require": "./dist/diagnostics.js"
93
103
  }
94
104
  },
95
105
  "dependencies": {
96
106
  "@langchain/core": "^1.2.3",
97
107
  "@langchain/openai": "^1.5.5",
98
- "@tradejs/core": "^3.1.29-beta.262",
99
- "@tradejs/infra": "^3.1.29-beta.262",
100
- "@tradejs/types": "^3.1.29-beta.262",
108
+ "@tradejs/core": "^3.1.29-beta.264",
109
+ "@tradejs/infra": "^3.1.29-beta.264",
110
+ "@tradejs/types": "^3.1.29-beta.264",
101
111
  "@types/node": "^24",
102
112
  "chalk": "4.1.2",
103
113
  "ioredis": "5.11.1",
@@ -110,7 +120,7 @@
110
120
  "typescript": "^5.9"
111
121
  },
112
122
  "devDependencies": {
113
- "@tradejs/indicators": "^3.1.29-beta.262",
123
+ "@tradejs/indicators": "^3.1.29-beta.264",
114
124
  "@tradejs/strategy-adaptive-momentum-ribbon": "3.0.2",
115
125
  "@tradejs/strategy-hyperliquid-consensus": "3.0.2",
116
126
  "@tradejs/strategy-kit": "3.0.6",