@zltquant/zlt-mcp 0.2.0 → 0.3.0
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/config.json +2 -2
- package/package.json +1 -1
- package/zlt-mcp-proxy.bundle.cjs +226 -37
package/config.json
CHANGED
|
@@ -507,7 +507,7 @@
|
|
|
507
507
|
},
|
|
508
508
|
{
|
|
509
509
|
"name": "mcp-create-and-run-backtest",
|
|
510
|
-
"description": "创建并启动回测。strategyId 必须来自 mcp-create-strategy 返回的策略ID;runType 固定为 2;普通日线回测只传 strategyId,其余参数 MCP 补齐。未指定周期时 end 取自然日 T-1、start 从 end 往前推半年;ZLTAF 按实际交易日执行,非交易日不产生行情或策略调用。run/create 失败直接报错,不要去复用策略里自动生成的 debug runId(建策略时已回显为 debugRunId
|
|
510
|
+
"description": "创建并启动回测。strategyId 必须来自 mcp-create-strategy 返回的策略ID;runType 固定为 2;普通日线回测只传 strategyId,其余参数 MCP 补齐。未指定周期时 end 取自然日 T-1、start 从 end 往前推半年;ZLTAF 按实际交易日执行,非交易日不产生行情或策略调用。run/create 失败直接报错,不要去复用策略里自动生成的 debug runId(建策略时已回显为 debugRunId,跑它等于跑创建那一刻的模板快照)。run/create 生成快照后,MCP 会在真正启动前拒绝 get_current_data().get(...) 这种当前对象不支持的访问。\n\n**返回 runId 后立即调用 mcp-wait(kind=backtest,id=runId)阻塞等待回测结束,不要用 mcp-get-backtest-state 轮询。**\n\n日线类频率只可见到 context.previous_date:要用交易日 T 的收盘,end 必须设成 T 的下一个交易日;end 落在未来不报错但结果为空。展开见返回的 dailyVisibilityNote / endWarning。\n\n返回 Data 回显 resolvedStart/resolvedEnd/resolvedCapital——向用户汇报区间和初始资金时一律以这些字段为准,不要自行推算或臆测。evidence 给出 backtestId、实际 runDir、完整 effectiveRunParams 和代码快照 sourceSha256,后续结果必须与它对账。**基准不在此列**:回显的 requestedBenchmark 只是请求值,策略 initialize 里的 set_benchmark() 会覆盖它,收益产物里的「基准收益」按策略实际用的基准算。说基准是什么之前先读策略代码里的 set_benchmark,否则会把一只个股的涨跌报成指数的。\n\n**期货回测要同时改三处,少一处引擎就按股票跑**:runParams.marketIds 加上对应期货交易所(zj 中金所、zs 郑商所、ds 大商所、sq 上期所、gq 广期所),account_list 的 account_type 改成 2(实测用 6 直接废单),费率用 ratio_tpl_obj.future。合约代码形如 zjIM2605。基准保持默认 sz399300 即可,它对期货策略只是业绩比较基准。数据前置见 mcp-download-data:除行情外还必须有市场规则 mr。",
|
|
511
511
|
"inputSchema": {
|
|
512
512
|
"type": "object",
|
|
513
513
|
"properties": {
|
|
@@ -913,7 +913,7 @@
|
|
|
913
913
|
},
|
|
914
914
|
{
|
|
915
915
|
"name": "mcp-get-backtest-profit",
|
|
916
|
-
"description": "读取已完成回测的收益表现,只能在 mcp-get-backtest-state 返回 status=5 后调用。直接读回测副本目录下的 log/zlt_backtest_profit_detail.csv,不经过 ZLTAF。正常只传 mcp-create-and-run-backtest 返回的 runId;MCP 重启后上下文丢失时补传 codePath。\n\nperformance 是整轮汇总,取自产物最后一行。**名字带 Pct 的已经是百分数**:-15.62 就是 -15.62%,不要再乘 100。winRate 与 dailyWinRate 是 0-1 的比例,其余是无量纲比值。上游没有值时返回 null(原文是 nan 或空单元格),不要当成 0。\n\n另给 rows(逐日行数)、firstDate/lastDate、capital、maxDrawdownWindow
|
|
916
|
+
"description": "读取已完成回测的收益表现,只能在 mcp-get-backtest-state 返回 status=5 后调用。直接读回测副本目录下的 log/zlt_backtest_profit_detail.csv,不经过 ZLTAF。正常只传 mcp-create-and-run-backtest 返回的 runId;MCP 重启后上下文丢失时补传 codePath。\n\nperformance 是整轮汇总,取自产物最后一行。**名字带 Pct 的已经是百分数**:-15.62 就是 -15.62%,不要再乘 100。winRate 与 dailyWinRate 是 0-1 的比例,其余是无量纲比值。上游没有值时返回 null(原文是 nan 或空单元格),不要当成 0。\n\n另给 rows(逐日行数)、firstDate/lastDate、capital、maxDrawdownWindow(已按北京时间换算成日期)。evidence 对账 requestedBacktestId、artifactBacktestId、runDir、effectiveRunParams 和 sourceSha256;contextAvailable=false 表示 MCP 已重启,只能证明显式 codePath 和产物内 ID。逐日净值序列默认不返回,要画净值曲线才传 includeSeries=true。\n\n**notes 里的内容必须转述**:产物回测ID 与请求不一致,说明 codePath 指向了另一次运行,那批数字不属于本次回测;超额收益校验不通过,说明上游口径变了,此时三个收益数原样给出,不得自行重算,也不得据此宣称跑赢基准。",
|
|
917
917
|
"inputSchema": {
|
|
918
918
|
"type": "object",
|
|
919
919
|
"properties": {
|
package/package.json
CHANGED
package/zlt-mcp-proxy.bundle.cjs
CHANGED
|
@@ -10502,15 +10502,20 @@ var OFFICIAL_MIRROR_ROOTS = {
|
|
|
10502
10502
|
};
|
|
10503
10503
|
var OFFICIAL_INSTALLERS = {
|
|
10504
10504
|
public: {
|
|
10505
|
-
//
|
|
10506
|
-
//
|
|
10507
|
-
//
|
|
10508
|
-
//
|
|
10509
|
-
//
|
|
10505
|
+
// 2026-08-31 抬到 2026.08.31.3,接替 2026.06.18.11 —— 运维已把件传上公网 OSS。
|
|
10506
|
+
// 摘要不必重下:公网与内网是同一份字节,HEAD 实测 Content-Length 168,548,568
|
|
10507
|
+
// 与 ETag(OSS 侧即 MD5)C404C12F…33DA 两边完全一致,所以沿用内网那份已核过的
|
|
10508
|
+
// SHA256=5BE939F0…5B19。
|
|
10509
|
+
// 卡在 2026.06.18.11 的代价是实打实的:它低于我们自己发布的
|
|
10510
|
+
// zlt_quant_version_index.json 里的 Client.min = 2026.08.12.3,于是一台 full
|
|
10511
|
+
// 装机撞上闸门只会得到 blockedByPlugin,被告知「请先升级 ZLT Quant 插件」——
|
|
10512
|
+
// 而插件已经是最新的那一版,闸门永远消不掉。
|
|
10513
|
+
// 本条与 internal.full 这次同版本,那是因为公网刚补齐,不是两张表从此必然同步。
|
|
10514
|
+
// **本版没有装机验证**,与 internal.full 同一句话。
|
|
10510
10515
|
full: {
|
|
10511
10516
|
artifact: "zlt_client",
|
|
10512
|
-
version: "2026.
|
|
10513
|
-
sha256: "
|
|
10517
|
+
version: "2026.08.31.3",
|
|
10518
|
+
sha256: "5BE939F069E69C05F37017FBB06FF76DE4DBCBD4CBCAF1322479255A5CE55B19"
|
|
10514
10519
|
},
|
|
10515
10520
|
// 2026-08-15 抬到 2026.08.14.5:公网 OSS 与内网镜像是同一份字节(ETag 113b3cff…、
|
|
10516
10521
|
// 75,075,160 字节两边一致),本机下载实测 SHA256=9D40AE9A…D551,与 2026-08-14 覆盖
|
|
@@ -10519,38 +10524,54 @@ var OFFICIAL_INSTALLERS = {
|
|
|
10519
10524
|
// 拒绝,且该下限只在模块服务端。version 取安装包自身 FileVersion 的点分四段(不是
|
|
10520
10525
|
// 构建目录名的紧凑形 20260814.5):升级判定拿它和 InstallInfo.xml 的
|
|
10521
10526
|
// InstallAppReleaseVer 比,写成紧凑形会让「有可用更新」永远消不掉。
|
|
10527
|
+
// 2026-08-31 抬到 2026.08.31.4,接替 2026.08.14.5。判据与 full 同一条:HEAD 实测
|
|
10528
|
+
// Content-Length 79,605,832 与 ETag 7A53D1EE…1375 公网内网一致,摘要沿用内网已核过的
|
|
10529
|
+
// SHA256=115A3EED…83CE。
|
|
10530
|
+
// 这一槽位是市场版的首装路径 —— VSCode 只装 mini(runtime-profile.mjs 的
|
|
10531
|
+
// VSCODE_INSTALL_TYPE),full 槽位只在已装 full 的机器上参与升级判定。
|
|
10532
|
+
// **本版没有装机验证**:本机停在 2026.08.28.7,08-30 对 .7 做的那轮活体复核
|
|
10533
|
+
// (三进程起得来、ZLTHQHost 管道在、两轮 check:npm-live 全绿)不适用于本版。
|
|
10522
10534
|
mini: {
|
|
10523
10535
|
artifact: "zlt_plugin",
|
|
10524
|
-
version: "2026.08.
|
|
10525
|
-
sha256: "
|
|
10536
|
+
version: "2026.08.31.4",
|
|
10537
|
+
sha256: "115A3EEDC07211F6FBE56696573F668AE8315E351B017FECB0F6136AAAD983CE"
|
|
10526
10538
|
}
|
|
10527
10539
|
},
|
|
10528
10540
|
internal: {
|
|
10529
|
-
// 2026-08-
|
|
10530
|
-
// 168,
|
|
10531
|
-
// (版本取它,不取文件名)。它仍高于 Client.min = 2026.08.12.3
|
|
10532
|
-
//
|
|
10541
|
+
// 2026-08-31 抬到 2026.08.31.3(1.0.2 RC 用件),接替 2026.08.28.8。内网镜像下载
|
|
10542
|
+
// 核验:168,548,568 字节,SHA256=5BE939F0…5B19,安装包 FileVersion 自报
|
|
10543
|
+
// 2026.08.31.3(版本取它,不取文件名)。它仍高于 Client.min = 2026.08.12.3,
|
|
10544
|
+
// 所以内测通道上一台 full 装机不会以 blockedByPlugin 收场。
|
|
10545
|
+
// 公网 OSS 上这一版 404(2026-08-31 实测 HEAD),public.full 不动。
|
|
10533
10546
|
full: {
|
|
10534
10547
|
artifact: "zlt_client",
|
|
10535
|
-
version: "2026.08.
|
|
10536
|
-
sha256: "
|
|
10548
|
+
version: "2026.08.31.3",
|
|
10549
|
+
sha256: "5BE939F069E69C05F37017FBB06FF76DE4DBCBD4CBCAF1322479255A5CE55B19"
|
|
10537
10550
|
},
|
|
10538
|
-
// 2026-08-
|
|
10539
|
-
// SHA256=
|
|
10540
|
-
//
|
|
10541
|
-
//
|
|
10542
|
-
//
|
|
10543
|
-
//
|
|
10544
|
-
//
|
|
10545
|
-
//
|
|
10546
|
-
//
|
|
10551
|
+
// 2026-08-31 抬到 2026.08.31.4(1.0.2 RC 用件),接替 2026.08.28.7。下载核验:
|
|
10552
|
+
// 79,605,832 字节,SHA256=115A3EED…83CE,FileVersion 自报 2026.08.31.4。
|
|
10553
|
+
//
|
|
10554
|
+
// **本条两件都只核到字节与版本,没有装机验证。** 本机停在 2026.08.28.7
|
|
10555
|
+
// (InstallInfo.xml 的 InstallAppReleaseVer 实读如此),所以 08-30 对 .7 做的那轮
|
|
10556
|
+
// 活体复核(三进程起得来、ZLTHQHost 管道在、两轮 check:npm-live 全绿)**不适用于
|
|
10557
|
+
// 这两版**,别把它顺移过来当证据。RC 装机后要重新确认两件事:ZLTHome 起得来、
|
|
10558
|
+
// 且 InstallAppReleaseVer 回读等于本条点分四段(对不上会让「有可用更新」永远消不掉)。
|
|
10559
|
+
//
|
|
10560
|
+
// 这条 08-28 起的历史仍然有效,记着是因为它解释了为什么这批件在密集换版:
|
|
10561
|
+
// 2026.08.28.3 现场装过并 ZLTHome 启动即崩(TTaskFunc 持栈上 lambda 的悬垂引用,
|
|
10562
|
+
// 100% 复现,ZLTHQHost 管道从未存活);同版起把 zltaf 从投研模块的子模块集里去掉,
|
|
10563
|
+
// 修的是首装必炸的另一条权限死路 —— 安装器与模块各发一份 zltaf,模块解压要覆盖
|
|
10564
|
+
// 安装器以管理员身份写下的文件,而 ZLTBackService 跑在登录账户的 UAC 过滤令牌上,
|
|
10565
|
+
// 卷根 ACL 缺 Authenticated Users:(M) 时覆盖必被拒,2 毫秒失败在第一个文件上。
|
|
10547
10566
|
// 见 docs/reports/2026-08-28-zltaf-submodule-extract-access-denied.md。
|
|
10548
|
-
//
|
|
10567
|
+
// 这些版本都没进过本表,不要读成本槽位的版本序列(上一个 pin 是 2026.08.28.7)。
|
|
10568
|
+
//
|
|
10569
|
+
// 公网 OSS 上这一版同样 404,所以 public.mini 仍停在 2026.08.14.5 —— 两张表不同版本,
|
|
10549
10570
|
// 这正是按镜像分表的理由。
|
|
10550
10571
|
mini: {
|
|
10551
10572
|
artifact: "zlt_plugin",
|
|
10552
|
-
version: "2026.08.
|
|
10553
|
-
sha256: "
|
|
10573
|
+
version: "2026.08.31.4",
|
|
10574
|
+
sha256: "115A3EEDC07211F6FBE56696573F668AE8315E351B017FECB0F6136AAAD983CE"
|
|
10554
10575
|
}
|
|
10555
10576
|
}
|
|
10556
10577
|
};
|
|
@@ -21015,6 +21036,7 @@ var NPM_PACKAGE_CONFIG_TOOL_NAMES = Object.freeze([
|
|
|
21015
21036
|
// src/tool-pipeline.ts
|
|
21016
21037
|
var fs11 = __toESM(require("node:fs"), 1);
|
|
21017
21038
|
var path11 = __toESM(require("node:path"), 1);
|
|
21039
|
+
var import_node_crypto2 = require("node:crypto");
|
|
21018
21040
|
|
|
21019
21041
|
// src/feature-set.ts
|
|
21020
21042
|
function resolveFeatureSet() {
|
|
@@ -21096,7 +21118,7 @@ function shapeToolResult(tool, args, result, session) {
|
|
|
21096
21118
|
if (tool.name === "mcp-stop-backtest" && !result.isError && typeof args.runId === "string") {
|
|
21097
21119
|
session.activeBacktestRunIds.delete(args.runId);
|
|
21098
21120
|
}
|
|
21099
|
-
const echoed = echoResolvedBacktestConfig(tool, args, result);
|
|
21121
|
+
const echoed = echoResolvedBacktestConfig(tool, args, result, session.backtestRunContexts);
|
|
21100
21122
|
const guarded = applyBacktestResultGuards(
|
|
21101
21123
|
tool,
|
|
21102
21124
|
args,
|
|
@@ -22408,13 +22430,22 @@ function captureBacktestRunContext(tool, args, result, contexts, activeRunIds) {
|
|
|
22408
22430
|
const codePath = typeof parsed.Data.codePath === "string" && parsed.Data.codePath.trim() ? parsed.Data.codePath.trim() : prior?.codePath;
|
|
22409
22431
|
if (runId) {
|
|
22410
22432
|
const marketIds = Array.isArray(runParams.marketIds) ? runParams.marketIds.filter((id) => typeof id === "string") : prior?.marketIds;
|
|
22411
|
-
|
|
22433
|
+
const sourceSha256 = hashBacktestSource(codePath) ?? prior?.sourceSha256;
|
|
22434
|
+
contexts.set(runId, {
|
|
22435
|
+
strategyId,
|
|
22436
|
+
start,
|
|
22437
|
+
end,
|
|
22438
|
+
codePath,
|
|
22439
|
+
marketIds,
|
|
22440
|
+
runParams: { ...runParams },
|
|
22441
|
+
sourceSha256
|
|
22442
|
+
});
|
|
22412
22443
|
activeRunIds.add(runId);
|
|
22413
22444
|
}
|
|
22414
22445
|
} catch {
|
|
22415
22446
|
}
|
|
22416
22447
|
}
|
|
22417
|
-
function echoResolvedBacktestConfig(tool, args, result) {
|
|
22448
|
+
function echoResolvedBacktestConfig(tool, args, result, contexts) {
|
|
22418
22449
|
if (tool.name !== "mcp-create-and-run-backtest" && tool.name !== "mcp-re-backtest" || result.isError || result.content.length === 0) return result;
|
|
22419
22450
|
const runParams = isRecord2(args.runParams) ? args.runParams : {};
|
|
22420
22451
|
const resolvedStart = typeof runParams.start === "string" ? runParams.start : "";
|
|
@@ -22432,10 +22463,23 @@ function echoResolvedBacktestConfig(tool, args, result) {
|
|
|
22432
22463
|
if (isDailyBacktestFrequency(runParams.freq)) echo.dailyVisibilityNote = DAILY_VISIBILITY_NOTE;
|
|
22433
22464
|
const endWarning = describeFutureBacktestEnd(resolvedEnd);
|
|
22434
22465
|
if (endWarning) echo.endWarning = endWarning;
|
|
22435
|
-
if (Object.keys(echo).length === 0) return result;
|
|
22436
22466
|
try {
|
|
22437
22467
|
const parsed = JSON.parse(result.content[0].text);
|
|
22438
22468
|
if (!isRecord2(parsed) || !isRecord2(parsed.Data)) return result;
|
|
22469
|
+
const echoedRunId = typeof parsed.Data.runId === "string" ? parsed.Data.runId.trim() : "";
|
|
22470
|
+
const runId = echoedRunId || (tool.name === "mcp-re-backtest" && typeof args.runId === "string" ? args.runId.trim() : "");
|
|
22471
|
+
const context = runId ? contexts.get(runId) : void 0;
|
|
22472
|
+
if (runId) {
|
|
22473
|
+
echo.evidence = {
|
|
22474
|
+
contextAvailable: Boolean(context),
|
|
22475
|
+
backtestId: runId,
|
|
22476
|
+
...context?.codePath ? { runDir: context.codePath } : {},
|
|
22477
|
+
...context?.strategyId ? { strategyId: context.strategyId } : {},
|
|
22478
|
+
...context?.runParams ? { effectiveRunParams: { ...context.runParams } } : {},
|
|
22479
|
+
...context?.sourceSha256 ? { sourceSha256: context.sourceSha256 } : {}
|
|
22480
|
+
};
|
|
22481
|
+
}
|
|
22482
|
+
if (Object.keys(echo).length === 0) return result;
|
|
22439
22483
|
return {
|
|
22440
22484
|
...result,
|
|
22441
22485
|
content: [{
|
|
@@ -22450,6 +22494,16 @@ function echoResolvedBacktestConfig(tool, args, result) {
|
|
|
22450
22494
|
return result;
|
|
22451
22495
|
}
|
|
22452
22496
|
}
|
|
22497
|
+
function hashBacktestSource(codePath) {
|
|
22498
|
+
if (!codePath) return void 0;
|
|
22499
|
+
try {
|
|
22500
|
+
const sourcePath = path11.join(codePath, "main.py");
|
|
22501
|
+
if (!fs11.statSync(sourcePath).isFile()) return void 0;
|
|
22502
|
+
return (0, import_node_crypto2.createHash)("sha256").update(fs11.readFileSync(sourcePath)).digest("hex");
|
|
22503
|
+
} catch {
|
|
22504
|
+
return void 0;
|
|
22505
|
+
}
|
|
22506
|
+
}
|
|
22453
22507
|
var BENCHMARK_OVERRIDE_NOTE = "\u8FD9\u662F\u8BF7\u6C42\u7684\u57FA\u51C6\uFF0C\u4E0D\u4E00\u5B9A\u662F\u5B9E\u9645\u751F\u6548\u7684\uFF1A\u7B56\u7565 initialize \u91CC\u7684 set_benchmark() \u4F1A\u8986\u76D6\u5B83\uFF0C\u6536\u76CA\u4EA7\u7269\u4E2D\u7684\u300C\u57FA\u51C6\u6536\u76CA\u300D\u6309\u7B56\u7565\u5B9E\u9645\u7528\u7684\u57FA\u51C6\u8BA1\u7B97\u3002\u62A5\u544A\u57FA\u51C6\u662F\u4EC0\u4E48\u4E4B\u524D\uFF0C\u5148\u8BFB\u7B56\u7565\u4EE3\u7801\u91CC\u7684 set_benchmark\u3002";
|
|
22454
22508
|
var DAILY_VISIBILITY_NOTE = "\u65E5\u7EBF\u7C7B\u9891\u7387\u7684\u7B80\u5355\u56DE\u6D4B\u53EA\u80FD\u770B\u5230 context.previous_date \u53CA\u4EE5\u524D\u7684\u5B8C\u6574\u65E5 K\uFF1B\u5F53\u65E5\u6536\u76D8\u5728\u56DE\u6D4B\u4E2D\u4E0D\u53EF\u89C1\uFF0Cafter_trading_end \u540C\u6837\u770B\u4E0D\u5230\u3002\u6240\u4EE5\u8981\u7528\u67D0\u4E2A\u4EA4\u6613\u65E5 T \u7684\u6536\u76D8\u505A\u7B5B\u9009\u6216\u8BA1\u7B97\uFF0CrunParams.end \u5FC5\u987B\u8BBE\u6210 T \u7684\u4E0B\u4E00\u4E2A\u4EA4\u6613\u65E5\uFF1B\u53EA\u8DD1 [T, T] \u5355\u65E5\uFF0C\u53D6\u5230\u7684\u662F T-1 \u7684\u6536\u76D8\u3002\u5206\u949F\u3001Tick\u3001\u7814\u7A76\u3001\u4EFF\u771F\u3001\u5B9E\u76D8\u4E0D\u53D7\u6B64\u9650\u3002";
|
|
22455
22509
|
function isDailyBacktestFrequency(value) {
|
|
@@ -22873,7 +22927,7 @@ function extractStrategyTreeIds(result) {
|
|
|
22873
22927
|
}
|
|
22874
22928
|
return ids;
|
|
22875
22929
|
}
|
|
22876
|
-
async function executeSteps(steps, client, initialBody, timeout, repairHint = identityRepairHint) {
|
|
22930
|
+
async function executeSteps(steps, client, initialBody, timeout, repairHint = identityRepairHint, toolName) {
|
|
22877
22931
|
let body = { ...initialBody };
|
|
22878
22932
|
for (let i = 0; i < steps.length; i++) {
|
|
22879
22933
|
const step = steps[i];
|
|
@@ -22910,6 +22964,10 @@ async function executeSteps(steps, client, initialBody, timeout, repairHint = id
|
|
|
22910
22964
|
}
|
|
22911
22965
|
}
|
|
22912
22966
|
}
|
|
22967
|
+
if (i < steps.length - 1) {
|
|
22968
|
+
const blocked = preflightCreatedBacktestSource(toolName, body);
|
|
22969
|
+
if (blocked) return blocked;
|
|
22970
|
+
}
|
|
22913
22971
|
if (i === steps.length - 1) {
|
|
22914
22972
|
const finalData = repairHint(mergeStepFieldsToFinalData(parsed.data, body, step.mergeToFinalData));
|
|
22915
22973
|
return {
|
|
@@ -22920,6 +22978,126 @@ async function executeSteps(steps, client, initialBody, timeout, repairHint = id
|
|
|
22920
22978
|
}
|
|
22921
22979
|
return { content: [{ type: "text", text: "{}" }] };
|
|
22922
22980
|
}
|
|
22981
|
+
function pythonCodeLines(source) {
|
|
22982
|
+
let state = null;
|
|
22983
|
+
return source.split(/\r?\n/).map((line) => {
|
|
22984
|
+
let code = "";
|
|
22985
|
+
for (let i = 0; i < line.length; ) {
|
|
22986
|
+
if (state === "'''" || state === '"""') {
|
|
22987
|
+
if (line.startsWith(state, i)) {
|
|
22988
|
+
i += 3;
|
|
22989
|
+
state = null;
|
|
22990
|
+
} else {
|
|
22991
|
+
code += " ";
|
|
22992
|
+
i += 1;
|
|
22993
|
+
}
|
|
22994
|
+
continue;
|
|
22995
|
+
}
|
|
22996
|
+
if (state === "'" || state === '"') {
|
|
22997
|
+
if (line[i] === "\\") {
|
|
22998
|
+
code += " ";
|
|
22999
|
+
i += 2;
|
|
23000
|
+
} else if (line[i] === state) {
|
|
23001
|
+
code += " ";
|
|
23002
|
+
i += 1;
|
|
23003
|
+
state = null;
|
|
23004
|
+
} else {
|
|
23005
|
+
code += " ";
|
|
23006
|
+
i += 1;
|
|
23007
|
+
}
|
|
23008
|
+
continue;
|
|
23009
|
+
}
|
|
23010
|
+
if (line[i] === "#") break;
|
|
23011
|
+
if (line.startsWith("'''", i) || line.startsWith('"""', i)) {
|
|
23012
|
+
state = line.slice(i, i + 3);
|
|
23013
|
+
code += " ";
|
|
23014
|
+
i += 3;
|
|
23015
|
+
continue;
|
|
23016
|
+
}
|
|
23017
|
+
if (line[i] === "'" || line[i] === '"') {
|
|
23018
|
+
state = line[i];
|
|
23019
|
+
code += " ";
|
|
23020
|
+
i += 1;
|
|
23021
|
+
continue;
|
|
23022
|
+
}
|
|
23023
|
+
code += line[i];
|
|
23024
|
+
i += 1;
|
|
23025
|
+
}
|
|
23026
|
+
if (state === "'" || state === '"') state = null;
|
|
23027
|
+
return code;
|
|
23028
|
+
});
|
|
23029
|
+
}
|
|
23030
|
+
function findUnsupportedCurrentDataGet(source) {
|
|
23031
|
+
const lines = pythonCodeLines(source);
|
|
23032
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
23033
|
+
const assignment = /\b([A-Za-z_]\w*)\s*(?::[^=]+)?=\s*get_current_data\s*\(\s*\)/g;
|
|
23034
|
+
for (const line of lines) {
|
|
23035
|
+
assignment.lastIndex = 0;
|
|
23036
|
+
let match;
|
|
23037
|
+
while ((match = assignment.exec(line)) !== null) aliases.add(match[1]);
|
|
23038
|
+
}
|
|
23039
|
+
let changed = true;
|
|
23040
|
+
while (changed) {
|
|
23041
|
+
changed = false;
|
|
23042
|
+
for (const line of lines) {
|
|
23043
|
+
for (const alias of aliases) {
|
|
23044
|
+
const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23045
|
+
const copied = new RegExp(`\\b([A-Za-z_]\\w*)\\s*(?::[^=]+)?=\\s*${escaped}\\b`).exec(line);
|
|
23046
|
+
if (copied && !aliases.has(copied[1])) {
|
|
23047
|
+
aliases.add(copied[1]);
|
|
23048
|
+
changed = true;
|
|
23049
|
+
}
|
|
23050
|
+
}
|
|
23051
|
+
}
|
|
23052
|
+
}
|
|
23053
|
+
const violations = [];
|
|
23054
|
+
lines.forEach((line, index) => {
|
|
23055
|
+
if (/get_current_data\s*\(\s*\)\s*\.\s*get\s*\(/.test(line)) {
|
|
23056
|
+
violations.push({ line: index + 1, expression: "get_current_data().get(...)" });
|
|
23057
|
+
}
|
|
23058
|
+
for (const alias of aliases) {
|
|
23059
|
+
const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23060
|
+
if (new RegExp(`\\b${escaped}\\s*\\.\\s*get\\s*\\(`).test(line)) {
|
|
23061
|
+
violations.push({ line: index + 1, expression: `${alias}.get(...)` });
|
|
23062
|
+
}
|
|
23063
|
+
}
|
|
23064
|
+
});
|
|
23065
|
+
return violations.filter((item, index, all) => all.findIndex((candidate) => candidate.line === item.line && candidate.expression === item.expression) === index);
|
|
23066
|
+
}
|
|
23067
|
+
function preflightCreatedBacktestSource(toolName, body) {
|
|
23068
|
+
if (toolName !== "mcp-create-and-run-backtest") return null;
|
|
23069
|
+
const runId = typeof body.runId === "string" ? body.runId.trim() : "";
|
|
23070
|
+
const codePath = typeof body.codePath === "string" ? body.codePath.trim() : "";
|
|
23071
|
+
if (!codePath || !fs11.existsSync(codePath)) return null;
|
|
23072
|
+
const sourcePath = path11.join(codePath, "main.py");
|
|
23073
|
+
if (!fs11.existsSync(sourcePath)) return null;
|
|
23074
|
+
let source;
|
|
23075
|
+
try {
|
|
23076
|
+
source = fs11.readFileSync(sourcePath, "utf8");
|
|
23077
|
+
} catch (error2) {
|
|
23078
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
23079
|
+
return jsonToolResult(10061, "\u56DE\u6D4B\u4EE3\u7801\u5FEB\u7167\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u672A\u542F\u52A8\u7B56\u7565\u5F15\u64CE\u3002", {
|
|
23080
|
+
retryableByAgent: false,
|
|
23081
|
+
runId,
|
|
23082
|
+
codePath,
|
|
23083
|
+
sourcePath,
|
|
23084
|
+
sourcePreflight: "blocked",
|
|
23085
|
+
reason: message
|
|
23086
|
+
}, true);
|
|
23087
|
+
}
|
|
23088
|
+
const violations = findUnsupportedCurrentDataGet(source);
|
|
23089
|
+
if (violations.length === 0) return null;
|
|
23090
|
+
return jsonToolResult(10061, "\u56DE\u6D4B\u4EE3\u7801\u4F7F\u7528\u4E86 get_current_data().get(...)\uFF0C\u5F53\u524D\u5BF9\u8C61\u4E0D\u652F\u6301 .get()\uFF1B\u5DF2\u521B\u5EFA\u5FEB\u7167\u4F46\u672A\u542F\u52A8\u7B56\u7565\u5F15\u64CE\u3002", {
|
|
23091
|
+
retryableByAgent: false,
|
|
23092
|
+
runId,
|
|
23093
|
+
codePath,
|
|
23094
|
+
sourcePath,
|
|
23095
|
+
sourcePreflight: "blocked",
|
|
23096
|
+
sourceSha256: (0, import_node_crypto2.createHash)("sha256").update(source).digest("hex"),
|
|
23097
|
+
violations,
|
|
23098
|
+
userAction: "\u628A current_data.get(security) \u6539\u4E3A current_data[security]\uFF0C\u5E76\u7528 KeyError \u5904\u7406\u7F3A\u5931\u8BC1\u5238\uFF1B\u7136\u540E\u91CD\u65B0\u521B\u5EFA\u5E76\u8FD0\u884C\u56DE\u6D4B\u3002"
|
|
23099
|
+
}, true);
|
|
23100
|
+
}
|
|
22923
23101
|
function buildStepBody(step, body) {
|
|
22924
23102
|
if (!step.bodyMapping) return body;
|
|
22925
23103
|
const mappedBody = {};
|
|
@@ -23968,13 +24146,24 @@ function handleBacktestProfitTool(args, contexts) {
|
|
|
23968
24146
|
Data: null
|
|
23969
24147
|
}, true);
|
|
23970
24148
|
}
|
|
24149
|
+
const context = contexts.get(backtestId);
|
|
24150
|
+
const summary = summarizeBacktestProfit(runDir, {
|
|
24151
|
+
expectedBacktestId: backtestId,
|
|
24152
|
+
includeSeries: args.includeSeries === true
|
|
24153
|
+
});
|
|
24154
|
+
summary.evidence = {
|
|
24155
|
+
contextAvailable: Boolean(context),
|
|
24156
|
+
requestedBacktestId: backtestId,
|
|
24157
|
+
...summary.backtestId ? { artifactBacktestId: summary.backtestId } : {},
|
|
24158
|
+
runDir,
|
|
24159
|
+
...context?.strategyId ? { strategyId: context.strategyId } : {},
|
|
24160
|
+
...context?.runParams ? { effectiveRunParams: { ...context.runParams } } : {},
|
|
24161
|
+
...context?.sourceSha256 ? { sourceSha256: context.sourceSha256 } : {}
|
|
24162
|
+
};
|
|
23971
24163
|
return toolText({
|
|
23972
24164
|
ErrNo: 0,
|
|
23973
24165
|
ErrMsg: "",
|
|
23974
|
-
Data:
|
|
23975
|
-
expectedBacktestId: backtestId,
|
|
23976
|
-
includeSeries: args.includeSeries === true
|
|
23977
|
-
})
|
|
24166
|
+
Data: summary
|
|
23978
24167
|
});
|
|
23979
24168
|
}
|
|
23980
24169
|
|
|
@@ -24231,7 +24420,7 @@ async function startMcpServer() {
|
|
|
24231
24420
|
);
|
|
24232
24421
|
if (special) return special;
|
|
24233
24422
|
if (tool.steps && tool.steps.length > 0) {
|
|
24234
|
-
const result = await executeSteps(tool.steps, ipcClient, stripControlParams(toolArgs), timeout, withRuntimeRepairHint);
|
|
24423
|
+
const result = await executeSteps(tool.steps, ipcClient, stripControlParams(toolArgs), timeout, withRuntimeRepairHint, tool.name);
|
|
24235
24424
|
return shapeToolResult(tool, toolArgs, result, session);
|
|
24236
24425
|
}
|
|
24237
24426
|
try {
|