☰
用WorkBuddy和MCP协议把量化回测压缩到一句话
2026/10/13 9:06:04 网站建设 项目流程

最近我把量化这条链路彻底换了个玩法:用 WorkBuddy 当 AI 操作台,把行情数据和回测引擎全部封装成 MCP 工具,之后你只需要用一句话说清楚“要哪只股票、什么周期、跑什么策略”,WorkBuddy 就会自己去拉数据、写策略、执行回测,最后把收益曲线和绩效指标摆在你面前。之前这一套流程我需要打开编辑器、写脚本、改参数、盯输出,至少半小时起步;现在熟练之后,从对话到拿到结果基本在一分钟内。这篇文章就围绕这条链路展开,不光讲 WorkBuddy 和 MCP 的配置方式,还把数据服务、回测引擎、常见坑位都拆开讲,适合已经会一点 Python、但不想再被繁琐流程绑架的量化爱好者。

1. 一句话跑通量化到底是怎么回事

1.1 WorkBuddy、MCP、量化脚本三个角色

初看这个标题,很多人会以为“一句话跑通量化”是玄学。其实拆开看,底层只有三个角色:WorkBuddy 是那个能听懂人话并执行任务的 AI 工作台,MCP 是连接工作台和外部工具的标准化协议,量化脚本则是真正干活的本地程序。我的做法是先把行情获取、策略回测这些能力写成一个个 MCP 工具,注册到同一个服务里,然后让 WorkBuddy 去调用它们,而不是让 AI 直接靠记忆编造数据或拼凑代码。

MCP 全称是 Model Context Protocol,本质上是给 AI 开了一组“远程函数接口”。AI 不直接执行 Python 代码,它只负责理解你的自然语言,把意图拆解成一次或多次工具调用。比如你说“取茅台最近一年的日线数据”,WorkBuddy 会看到我这里注册了一个 get_kline 工具,于是自动填好 600519、20240101、20241231 这些参数去调用,拿到 JSON 之后再继续下一步。这种模式和过去最大的区别在于:AI 不再只是写代码给你看,它自己就能调工具、跑程序、拿结果。

1.2 一次完整会话的链路演示

我在本地跑通的第一条完整链路是这样的:先有一个名为 quant-server 的 MCP 服务常驻运行,里面注册了 get_kline 和 run_backtest 两个核心工具,然后打开 WorkBuddy,在对话里输入一句话:“获取 600519 最近一年的日线数据,用 5 日和 20 日均线金叉死叉跑回测,初始资金 10 万,输出年化收益率和最大回撤。”

WorkBuddy 收到这句话后,会先通过 MCP 协议发现可用工具列表,然后调用 get_kline 拿到行情数据,再调用 run_backtest 执行回测。在我这边的实测里,整个过程大概十几秒,中间不需要我手动复制任何文件。如果换成以前的工作流,我得先写一个数据脚本,再写一个回测脚本,还要处理日期格式、复权方式、结果输出等等,人和机器的工作量完全不在一个量级。核心思路就是把重复劳动标准化成工具,AI 只做调度和解读。

2. 数据服务:自己写一个MCP量化服务器

2.1 技术选型:FastMCP + AkShare + 本地缓存

要接入 WorkBuddy,前提是本地有一个能跑起来的 MCP 服务器。我建议直接用 Python 生态里的 FastMCP 封装工具,它把 stdio 通信、工具注册、参数校验都处理好了,我们只需要写几个函数。数据源我选的是 AkShare,免费、无需 token、覆盖 A 股和港美股主要行情,用起来非常方便;回测部分先接 Backtrader,后续想换成向量化引擎也容易。

有一点要提前想清楚:MCP 服务本身只是个调度壳,真正的时间开销在数据网络请求和回测计算上。所以我会在服务端加一层本地缓存,第一次从 AkShare 拉到日线数据后存成 Parquet 文件,后续再请求相同股票和区间时直接读缓存。这样 AI 反复调错参数时不会每次都去请求远程接口,既快又不容易被限流。用大家熟悉的一句话类比:远程数据源是仓库,本地缓存是货架,AI 是店员,先把常用货搬到货架上,拿货自然快。

2.2 先写一个能拉K线的工具

先用最小代码实现 get_kline 工具。创建 quant_server.py,内容大致如下:

from mcp.server.fastmcp import FastMCP import akshare as ak import pandas as pd import json mcp = FastMCP("quant-server") @mcp.tool() def get_kline(symbol: str, start_date: str = "20240101", end_date: str = "20241231", adjust: str = "qfq") -> str: """获取A股日线行情,返回JSON字符串。 symbol: 6位股票代码 adjust: qfq前复权 / hfq后复权 / ""不复权 """ df = ak.stock_zh_a_hist( symbol=symbol, period="daily", start_date=start_date, end_date=end_date, adjust=adjust, ) if df is None or df.empty: return json.dumps({"error": "no data"}, ensure_ascii=False) df = df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume", }) df = df[["date", "open", "close", "high", "low", "volume"]] return df.tail(500).to_json(orient="records", force_ascii=False) if __name__ == "__main__": mcp.run()

这段代码看着简单,但有两个细节需要注意。第一,函数定义里的 docstring 不要省略,WorkBuddy 这类客户端判定工具用途时,主要依赖工具名、参数名和函数说明,你写得越清晰,AI 自动填参就越准。第二,返回结果我用 tail(500) 做了截断,既保留最近一年大部分日线数据,又避免单次返回内容撑爆上下文窗口。实际如果是一年 A 股日线大概 240 个 bar,500 足够覆盖;如果你要更长周期,建议改成服务端分页参数。

2.3 工具列表的权限与裁剪

很多人在 MCP 服务里会忍不住把所有函数都注册成工具,我劝你不要这么做,至少不要随便把“执行任意命令”“写入任意文件”这类能力暴露给 AI。原因很简单:MCP 协议本身只负责传递调用请求,不负责做权限判断,AI 一旦被污染或上下文注入,就可能调用到一个有破坏性的工具。我的原则是给量化场景暴露刚需的三个工具:get_kline 拿行情、get_stock_list 拉股票列表、run_backtest 跑策略,其他操作都通过独立脚本完成。

要把工具划分清晰,最好的办法是像设计内部 API 一样对待每个函数。先想清楚输入是什么、输出是什么、边界条件是什么,再注册到 MCP 里。比如 get_stock_list 可以返回全部 A 股代码和简称,但不要把财务指标、实时 tick 都塞进去,因为回测真正需要的只是干净的日线序列。少一个工具,AI 就少一分“胡思乱想”的空间,调用链路也更稳定。

2.4 启动服务器并自检

MCP 服务默认通过标准输入输出和客户端通信,所以启动方式很简单:

pip install mcp akshare pandas pyarrow python quant_server.py

启动时有个非常容易踩的坑:不要在工具函数之外打印任何无关内容,包括调试日志、进度条、print 字典之类的。因为 stdio 通道被 MCP 协议占用,任何多余的输出都会污染协议流,轻则工具调用失败,重则整个连接直接挂掉。想看日志就把日志写到文件或者用 logging 库输出到 stderr,而不要去动 stdout。

启动之后可以用官方提供的 mcp 调试工具自检一下,或者直接在 WorkBuddy 里查看工具列表。如果能看到上面注册的两个函数,就说明服务端正常;如果看不到,优先检查 Python 环境是否是启动 WorkBuddy 时用的同一个环境。这个自检步骤虽然简单,但能帮你省下后面排查的不少时间。

3. 在WorkBuddy里接入MCP并“调教”调用方式

3.1 配置文件路径与写法

WorkBuddy 这类支持 MCP 的客户端,通常都支持通过一个 JSON 文件声明要加载哪些 MCP 服务。我这边用的是全局配置方式,右键打开配置文件后,添加一段内容:

{ "mcpServers": { "quant": { "command": "python", "args": ["D:/workspace/quant_server.py"], "env": {} } } }

配置里的关键字段就三个:command 指定启动命令,args 指定服务器脚本路径,env 可以用来注入环境变量。这里我特别想提醒一句:脚本路径尽量写绝对路径。我第一次配置时图省事写了个相对路径,结果 WorkBuddy 工作目录一变,MCP 服务就直接起不来了。改完配置之后需要重启 WorkBuddy 或重新加载 MCP,工具列表刷出来才算连接成功。

如果你把项目从一台电脑搬到另一台电脑,配置文件里最容易出问题的是路径和 Python 环境。建议在项目根目录放一个 requirements.txt,新电脑上先创建虚拟环境、安装依赖,再启动 MCP 服务。很多所谓的“MCP 连不上”问题,换个角度看其实就是“环境和路径没对上”。

3.2 自然语言调用与参数补全

连接成功之后,WorkBuddy 会拿到工具的函数签名、参数说明、docstring。这时与其说是“让 AI 工作”,不如说是“让 AI 做接口编排”。我实际测试下来,它的参数补全能力比我想象中强,比如我只说“茅台”它可能不确定具体代码,所以我一般会在第一次对话里说“茅台 600519”,之后 WorkBuddy 会记住这个映射,后面再说“茅台”也能自动关联到 600519。

为了减少歧义,建议在工具描述里把参数边界写清楚,比如日期格式统一用 YYYYMMDD,复权参数只接受 qfq、hfq、空字符串三种取值。AI 对这些约束的理解能力取决于描述是否精确。我之前的 get_kline 里日期参数没写格式,结果 WorkBuddy 有一次自动传入了 2024-01-01 这种带横杠的格式,AkShare 直接报错。后来我在 docstring 里明确标注“日期格式为YYYYMMDD”,问题就很少再出现。

3.3 一句话实操:从拉数据到看结果

现在做一次完整实操。我在 WorkBuddy 对话框输入:“用 quant 服务获取 600519 最近一年日线数据,跑双均线回测,快线 5 日,慢线 20 日,初始资金 100000,输出收益曲线要点和最大回撤。” 实际执行时,WorkBuddy 会先列出我的 MCP 工具,然后调用 get_kline 拿到数据,再调用 run_backtest 跑回测。因为服务端已经内置了双均线策略,它只需要把参数传进去即可。

第一次跑的时候,我发现它返回的是一段包含年化收益率、总收益率、最大回撤、交易次数等字段的 JSON。说实话这个输出已经挺完整,但我还是让它额外画了一张简易净值曲线图。WorkBuddy 没有直接绘图能力,但它知道可以用 matplotlib 把结果写到本地 PNG 文件,然后再展示出来。整个过程仍然是一句话的事,它自己会完成“数据获取、策略执行、结果落盘、文件展示”这一整串动作。

3.4 调用失败时如何观察MCP日志

再稳定的工具也有翻车的时候。第一次实测时,WorkBuddy 提示“工具调用失败:not a valid MCP tool name”,我查了半天才发现是配置文件里的服务名和实际脚本里 FastMCP 的名称不一致。WorkBuddy 的日志里会把实际加载到的工具名列出来,对比一下就能发现差异。如果日志里连工具列表都看不到,那大概率是服务没启动成功,回到命令行直接运行python quant_server.py,看有没有报错。

还有一次 AkShare 临时接口挂了,get_kline 返回的空 DataFrame 被我包装成了 {"error": "no data"}。这时候 WorkBuddy 不会说“数据源坏了”,它只会告诉你“没有拿到数据”。所以工具函数里一定要做好异常捕获和错误信息返回,把“数据为空”“接口超时”“参数非法”都明确写进返回体。这样 AI 拿到错误后还能尝试修正或告诉你原因,而不是把失败了当成一次普通空结果继续往下跑。

4. 回测引擎:让MCP直接托管策略运行

4.1 为什么不在MCP里写一大套回测框架

回测引擎的接入方式有两种:一种是把 Backtrader 写成一个完整工具丢给 MCP,另一种是只在 MCP 里放一个策略执行入口。我的建议是选后者。原因是 MCP 会话本身不擅长承载长任务状态,尤其是几千行策略日志和中间变量,如果全部塞进工具返回信息里,很容易把上下文窗口撑爆。更合理的设计是:MCP 服务只接收策略参数,内部调用本地回测脚本,最后把压缩后的绩效摘要返回。

拿双均线举例,MCP 工具 run_backtest 的职责不是复刻一个回测平台,而是把“拿数据、建引擎、加策略、跑结果”这几步串起来。这样既能让 WorkBuddy 通过一句话触发回测,又不会让 AI 被大量中间计算干扰。如果你想换策略,修改的是本地脚本,而不是每次都要让 AI 现场写一套新回测代码,维护成本会低很多。

4.2 用Backtrader实现双均线策略

在 quant_server.py 里增加 run_backtest 工具,核心代码框架如下:

import backtrader as bt class MaCross(bt.Strategy): params = (("fast", 5), ("slow", 20)) def __init__(self): self.fast_ma = bt.ind.SMA(period=self.p.fast) self.slow_ma = bt.ind.SMA(period=self.p.slow) def next(self): if not self.position and self.fast_ma[0] > self.slow_ma[0]: self.buy(size=100) elif self.position and self.fast_ma[0] < self.slow_ma[0]: self.close() @mcp.tool() def run_backtest(symbol: str, start_date: str = "20240101", end_date: str = "20241231", fast: int = 5, slow: int = 20, cash: float = 100000.0) -> str: """对指定股票运行双均线策略回测,返回绩效摘要。""" df = load_kline_from_cache_or_api(symbol, start_date, end_date) if isinstance(df, str): return df cerebro = bt.Cerebro() data = bt.feeds.PandasData(dataname=df) cerebro.adddata(data) cerebro.addstrategy(MaCross, fast=fast, slow=slow) cerebro.broker.setcash(cash) cerebro.broker.setcommission(commission=0.0003) cerebro.addanalyzer(bt.analyzers.DrawDown, _name="dd") cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name="ar") results = cerebro.run() strat = results[0] stats = { "final_value": round(cerebro.broker.getvalue(), 2), "max_drawdown": round(strat.analyzers.dd.get_analysis()["max"]["drawdown"], 2), "annual_return": round(strat.analyzers.ar.get_analysis().get(len(df), 0), 4), "trade_count": len(strat.analyzers), # 这里仅示意,实际用TradeAnalyzer更准确 } return json.dumps(stats, ensure_ascii=False)

代码里有个细节要提醒:我没有在回测过程中让 AI 逐步看每个 bar 的持仓,只返回绩效摘要。这样处理既能快速得出结论,又不会让 MCP 返回体变成几千行难读的日志。如果你真的需要逐日净值,可以在脚本里把净值数据存成 CSV,把文件路径返回给 WorkBuddy,让它去读取展示,而不是直接把大对象扔回对话。

4.3 回测结果的返回格式设计

MCP 工具的返回值本质上就是一个字符串,但怎么组织这个字符串决定了 AI 后续能做什么。我常用的做法是统一返回 JSON 对象,里面包含“状态、摘要、附加信息”三层结构。状态字段标记 success 或 error;摘要字段放年化收益、最大回撤、交易次数等核心指标;附加信息字段放 CSV 路径、净值曲线图片路径、参数说明等。

这样做的好处是 WorkBuddy 拿到 JSON 后,可以非常自然地把摘要在对话里展示给用户,还可以根据附加路径继续绘图或读取详细数据。如果返回的是随口乱写的自然语言文案,AI 再想进一步操作就得重新解析,容易出错。我在实践中还加了一个约定:日期区间超过半年的,在摘要里继续返回 tail 部分数据;区间较短时返回全量净值曲线。这套规则能让对话体验和细节保留做到平衡。

4.4 从单股回测扩展到多股与参数扫描

单一股票跑通之后,很自然的下一步就是多股回测。比如你想同时测试沪深 300 里的多只股票,可以使用 Backtrader 的多个 data feeds,或者循环调用 run_backtest 然后汇总。我建议在 MCP 工具里新增一个 run_multi_backtest 工具,接收股票代码列表,循环执行单股回测,最后返回一个汇总表格的结构化数据。

参数扫描也是一样。双均线策略的快线和慢线参数范围完全可以交给 WorkBuddy 去组合,比如它自己枚举 fast 在 3、5、10、15,slow 在 20、30、60 之间跑网格,然后把每组参数对应的年化和回撤列出来。实测下来这种模式跑一个几十组的小网格很快,但要注意别在对话里一次性铺开太多表格,只展示最优的前五个,其余写进本地结果文件。这样既不浪费上下文,又能保留全部计算数据供后续分析。

5. 常见问题与避坑实录

5.1 MCP工具列表为空或连接超时

这是接入时遇到最多的问题,没有之一。工具列表为空,大概率不是 WorkBuddy 的问题,而是 MCP 服务没有启动成功。按下述顺序排查最快:先确认路径是否正确,再确认 workbuddy 启动时是否使用了同一个 Python 环境,最后看命令行单独运行脚本有没有报错。我遇到过因为 AkShare 版本过旧导致接口调用失败,服务启动后 Flare 但工具注册不完整,升级依赖后就恢复了。

连接超时则多发生在数据请求环节。MCP 协议里对单个工具调用本身没有特别短的硬超时,但 AI 客户端一般会设置一个请求等待上限。如果你的首次请求就要从 AkShare 拉三年全市场数据,大概率会等到超时。解决方案是在服务端做两层处理:先拉缓存,缓存没有就先返回“数据准备中”的中间状态,用异步方式把数据缓存好再让 WorkBuddy 重试。我在实际项目里很少真的用异步,因为加了本地缓存后大多数请求都在 200 毫秒内返回,根本触发不了超时。

5.2 回测结果“虚高”:未来函数与复权

我在刚跑双均线策略时,发现年化收益高到离谱,后来定位到两个问题:一个是未来函数,一个是复权方式选择错误。所谓未来函数,就是策略在计算当天信号时,不小心用到了当天晚上才知道的数据,比如把当天收盘价同时拿来计算信号和成交价,这会让回测看起来“未卜先知”。Backtrader 的 next 方法天然会在当前 bar 结束时触发,但如果你在指标计算中没有做 shift,或者使用了未来一段窗口的平均值,就会偷看未来。

复权问题更加隐蔽。很多人拉数据时用不复权,而股票如果有分红送股,历史价格会出现跳空,导致均线信号失真。我一般用前复权数据进行回测,但要注意前复权的历史价格会随最新价格变动而变化,换一个数据截止日,同一历史信号可能不完全一样。这种细节不是 MCP 能替你解决的,必须在数据层做好标记。WorkBuddy 确实让调用变简单了,但策略逻辑的口子还得自己把关,我后来在工具描述里强制指定 adjust 默认值为 qfq,并让 AI 明白复权参数的选择会直接影响结论。

5.3 数据量一大就卡:上下文与表格渲染

如果 get_kline 返回全量分钟数据,单次返回可能上千行,这会让 WorkBuddy 的对话窗口不堪重负,甚至出现明显的卡顿。很多人开发时喜欢一次把数据全丢给 AI 看,这其实是个误区。AI 并不需要真的读完整张表,它只需要拿到足够判断的数据结构和几条最新样本,真正的计算都放在本地脚本里进行。所以服务端最好默认只返回摘要和最近的若干行,完整数据落到本地文件,再把文件路径给 WorkBuddy。

这个思路和前端里“表格大数据卡顿优化”很像。与其把百万行数据直接绑定到表格控件里,不如分页、懒加载、聚合展示。MCP 工具返回数据也是一样,能少传就少传,能传文件路径就别传全文。如果你后续用 WorkBuddy 展示回测结果,也建议只展示绩效指标和净值曲线图,而不是把所有交易明细都塞进对话里。

5.4 从入门到更换电脑的配置迁移

WorkBuddy、MCP Server、量化项目放一起,最麻烦的是换电脑。我的项目结构一般是:

D:/workspace/quant-project/ ├── quant_server.py ├── requirements.txt ├── config/ │ └── mcp.json ├── data_cache/ └── reports/

迁移时只要把整个目录拷到新电脑,重新创建虚拟环境并安装依赖,再把 mcp.json 里的绝对路径改一下即可。之前我图省事直接把 WorkBuddy 的全局配置一起备份恢复,结果因为路径前缀不同,MCP 服务全挂。后来我把配置里可变的路径都抽成相对项目根目录的写法,只在需要的地方拼接绝对路径,迁移成本降了很多。这也算是一个从入门到进阶的必踩坑位。

5.5 其他值得注意的小坑

量化经常涉及“主力大资金指标”“资金全景指标源码”这类玄学策略,我建议不要指望 MCP 帮你作弊式选取未来信号,MCP 只是工具层,策略逻辑必须靠谱。另外,如果要做交易比赛或实盘模拟,还要特别注意数据时区问题。AkShare 返回的日期是北京时间,而本机如果是 UTC,直接转时间戳会偏移 8 小时。虽然后回测只关注日期粒度,但一旦到分钟级别或者涉及隔夜持仓,时区问题就会被放大。

6. 让这套流程更稳的一点个人经验

6.1 数据缓存永远比重复请求划算

我在 quant-server 里统一加了一个缓存函数:先查本地 Parquet 文件,文件存在且日期覆盖完整就直接读取,否则再请求 AkShare 并回写文件。这个改动看起来不起眼,但实际运行后把数据环节从平均 2 秒压到了 50 毫秒以内。更重要的是,它让 WorkBuddy 反复调参时不那么依赖外网,回测的一致性也好很多。对于每天数据量很大的用户,甚至可以写个定时脚本提前把当日行情下载到缓存,WorkBuddy 白天调用时就完全不碰远程接口。

6.2 把“思考”留给AI,把“计算”留给本地

一开始我也尝试让 WorkBuddy 在每个工具调用里反复读取数据、计算指标、做判断,结果发现上下文消耗非常快,而且 AI 面对大量数值时很容易“一本正经地胡说”。后来我把计算密集型的部分全部下沉到本地脚本,MCP 工具只做黑盒调用:你传参数,它返回结论。AI 的角色变成理解意图、编排调用、解读结果,这正好是它最擅长的事情。整套系统也因此稳定很多。

6.3 给自己留一条手动复核的通道

WorkBuddy 的自动化让我产生过一种错觉:回测跑出高收益就说明策略可行。直到有一次它跑出一个年化 200% 的策略,我很兴奋,结果手动检查数据时发现收盘价复用出问题。所以我的项目里始终保留一个 generate_report.py 脚本,每次 MCP 回测完,WorkBuddy 会顺便调用它生成一份完整报告,包含数据区间、复权方式、参数网格、交易明细和净值曲线。自动化负责效率,手动复核负责底线,两者缺一不可。

就我个人的实际体验来说,用 WorkBuddy 调 MCP 做量化最大价值不是“省去了写代码”,而是把量化研究里那些琐碎又容易出错的粘合工作全部自动化了。数据获取、缓存、回测执行、结果汇总,原本需要来回切换的五六件事,现在都收敛成一个对话入口。对已经有一定 Python 基础的量化爱好者来说,这套流程非常值得搭一套;即使你现在只做手工交易,也不妨先用它跑一跑历史数据,看看策略在不同市场环境下到底会怎样表现。最后再分享一个低成本小技巧:刚开始不要追求工具数量多,先把“一个数据工具加一个回测工具”跑通,比一次性上一堆花哨功能有用得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询