资讯动态

怎么给大模型(Claude / GPT)加上「操作 MT5」的能力?MCP 交易工具开发实战

发布时间:2026/10/9 21:36:32 来源:尧图企业网站定制
1. 为什么大模型不能直接连 MT5非要绕一层 MCP先说结论Claude、GPT 这类大模型本身没有任何办法直接碰你的 MT5 终端。它们能做的只是「生成文本」而「读持仓」「下单」「改 EA 参数」这些动作必须由一段跑在你本机的程序去执行。中间这层把动作暴露成模型可调用函数的协议就是 MCPModel Context Protocol。你可以把 MCP 理解成「给大模型配的一套标准插座」。你写一个 MCP Server在里面声明一组工具工具名、入参 schema、具体实现。任何支持 MCP 的 AgentClaude Code、Claude Desktop、Cline、Fay 等接上就能用不用为每家模型单独写一套 function calling 适配。这就是为什么现在做交易类 Agent大家更愿意走 MCP 而不是绑死某家的工具调用格式。那具体到 MT5 场景链路是这样的Agent 收到你的自然语言指令 → 模型推理该调哪个工具 → 通过 MCP 协议把调用请求发给本机的 MCP Server → Server 用 Python 的 MetaTrader5 库操作 MT5 终端 → 结果以 JSON 返回给模型 → 模型继续推理或给你结论。整个过程你不需要写 if-else 去编排模型自己用 ReAct推理-行动循环决定下一步。我试过把这套跑通之后最直观的变化是以前你问「我黄金那单亏多少」模型只能瞎猜或者让你自己去看现在它会真的去调list_all_mt5_status读你的真实持仓再告诉你浮亏数字。这个差别就是「聊交易」和「能操作交易」的分水岭。不过这里有个前提必须先讲清楚动钱的工具默认不能开。原因很简单模型再聪明也会有幻觉一旦它能无条件下单风险不可控。所以整套设计的第一原则是读写分层、权限分离后面会详细展开。本篇聚焦两条最小链路行情读取和下单/撤单闭环再补齐鉴权、错误重试与日志。全程用模拟账户验证不碰真金白银。TaoToken 在这里的角色是统一 Key/API 通道——你不需要为 Claude、GPT 分别管理一堆 Key一个通道搞定模型侧的调用官网见 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 。适合谁看有 Python 基础、想给自己的交易 Agent 加「手脚」的开发者或者你已经在用 Claude Code / Cline 写代码想让它顺手把 MT5 也管起来。不需要你懂 MQL5 底层但得能看懂 Python 和 JSON。2. 前置准备TaoToken 统一 Key 与 MT5 环境搭建在写 MCP Server 之前先把两边的环境弄干净模型侧和交易侧。模型侧我建议用 TaoToken 做统一入口。原因是你在开发调试阶段会频繁切换模型——有时候用 Claude 测工具调用有时候用 GPT 对比效果如果每家都单独申请 Key、单独配 Base URL配置会乱成一团。TaoToken 提供一个统一的 API 通道Base URL 是https://taotoken.net/api你拿一个 Key 就能调不同模型。API Key 在控制台生成https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。交易侧你需要安装 MT5 终端Windows 原生Linux 用 WinemacOS 也能跑但坑多。在 MT5 里登录一个模拟账户别用实盘调试。安装 Python 的 MetaTrader5 库pip install MetaTrader5。确认 MT5 终端里「工具 → 选项 → 智能交易系统」勾选了「允许算法交易」否则order_send会直接返回失败。这里有个容易忽略的点MetaTrader5 这个 Python 库是跟本机 MT5 终端进程通信的不是走网络 API。所以你的 MCP Server 必须和 MT5 跑在同一台机器上。如果你想让远程的 Agent 调用得自己在中间加一层转发但那是另一个话题本篇不展开。环境变量这块我建议一开始就规划好权限闸环境变量作用默认值EASYDEAL_TRADING_WRITE开启平仓/改单类工具关闭EASYDEAL_TRADING_WRITE_OPEN开启开仓类工具关闭MT5_LOGIN模拟账户账号无MT5_PASSWORD账户密码无MT5_SERVER券商服务器名无没开权限时动钱工具根本不出现在工具列表里——模型连「乱下单」的入口都看不到。这比「工具存在但内部拦截」安全得多因为模型不会去尝试调用一个它看不见的工具。模型侧的配置如果你用 Claude Code可以在 settings 里指定 Base URL 和 Key如果用 Cline在 MCP 配置里填。下面给一份可复制的配置片段路径按你自己的实际安装位置改。{ mcpServers: { easydeal-mt5: { command: python, args: [/path/to/easydeal_mcp_server.py], env: { MT5_LOGIN: 你的模拟账号, MT5_PASSWORD: 你的密码, MT5_SERVER: 你的券商服务器, EASYDEAL_TRADING_WRITE: 0, EASYDEAL_TRADING_WRITE_OPEN: 0 } } } }注意这里两个写权限都设成0先只跑只读链路。等只读验证通过了再单独开写权限测下单。这个顺序别颠倒否则你会在一个「工具能下单但行情还没读对」的混乱状态里排障。模型 ID 这块Claude 系可以用claude-sonnet-4-5之类GPT 系用gpt-4o之类具体以 TaoToken 文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。工具调用能力强的模型ReAct 循环会更稳别用太小的模型测交易类工具容易在参数格式上翻车。3. 可复制配置MCP Server 工具函数签名与权限闸这一节是核心直接给可复制的代码骨架。你要做的是把 MT5 的每个操作封装成一个工具工具分两层只读层和动钱层。先看只读工具。这类工具永远可用负责读状态、读行情、读参数import os import json import MetaTrader5 as mt5 def list_all_mt5_status() - dict: 读取账户、持仓、挂单的汇总状态 if not mt5.initialize(): return {ok: False, error: mt5_initialize_failed} account mt5.account_info() positions mt5.positions_get() orders mt5.orders_get() return { ok: True, account: { login: account.login, balance: account.balance, equity: account.equity, margin_free: account.margin_free, }, positions: [p._asdict() for p in (positions or [])], orders: [o._asdict() for o in (orders or [])], } def get_symbol_tick(symbol: str) - dict: 读取某品种的最新买卖价 info mt5.symbol_info(symbol) if info is None: picked _pick_chartable_symbol([symbol]) if picked: symbol picked else: return {ok: False, error: fsymbol_not_found:{symbol}} tick mt5.symbol_info_tick(symbol) return { ok: True, symbol: symbol, bid: tick.bid, ask: tick.ask, time: tick.time, }注意get_symbol_tick里的模糊匹配逻辑。不同券商的黄金叫法不一样XAUUSD、XAUUSDm、XAUUSD.c都可能是它。工具内部别写死品种名拿通用名去券商全品种表里找变体def _pick_chartable_symbol(candidates: list) - tuple: 在全品种表里找含候选名的、且行情可见的品种 all_symbols mt5.symbols_get() if not all_symbols: return None, None for cand in candidates: for s in all_symbols: if cand.upper() in s.name.upper() and s.visible: return s.name, s return None, None这个模糊匹配是真实踩过的坑。我一开始写死XAUUSD结果换了个券商账户工具一直报symbol_not_found排查半天才发现人家叫XAUUSDm。再看动钱工具。这类工具默认不暴露靠环境变量闸控制def _write_enabled() - bool: return os.getenv(EASYDEAL_TRADING_WRITE, 0) 1 def _open_enabled() - bool: return os.getenv(EASYDEAL_TRADING_WRITE_OPEN, 0) 1 def open_position(symbol: str, volume: float, order_type: str, sl: float 0.0, tp: float 0.0) - dict: 开仓。需要 EASYDEAL_TRADING_WRITE_OPEN1 if not _open_enabled(): return {ok: False, error: open_disabled} info mt5.symbol_info(symbol) if info is None: picked, _ _pick_chartable_symbol([symbol]) if not picked: return {ok: False, error: fsymbol_not_found:{symbol}} symbol picked info mt5.symbol_info(symbol) # 手数量化按券商 volume_step / min / max 取整 step info.volume_step volume max(info.volume_min, min(info.volume_max, round(volume / step) * step)) price mt5.symbol_info_tick(symbol).ask if order_type buy \ else mt5.symbol_info_tick(symbol).bid req { action: mt5.TRADE_ACTION_DEAL, symbol: symbol, volume: volume, type: mt5.ORDER_TYPE_BUY if order_type buy else mt5.ORDER_TYPE_SELL, price: price, sl: sl, tp: tp, magic: _pick_magic(), comment: mcp_agent, type_time: mt5.ORDER_TIME_GTC, type_filling: mt5.ORDER_FILLING_IOC, } result mt5.order_send(req) ok result is not None and result.retcode mt5.TRADE_RETCODE_DONE return { ok: ok, ticket: result.order if ok else None, retcode: getattr(result, retcode, None), message: None if ok else forder_send retcode{result.retcode}, }这里有几个关键设计点逐个说。魔术数隔离。_pick_magic()要避开当前在跑策略的持仓魔术数否则 AI 新开的单会被那只 EA 误当成自己的单去平/改/统计。实现上可以先读一遍现有持仓的 magic 集合然后取一个不在集合里的值。手数量化。不同券商对最小手数、步长、最大手数要求不同。直接传0.1可能因为步长是0.01而报Invalid volume。上面这段按volume_step取整能挡掉大部分退单。返回值统一带ok字段。这是给 Agent 判断成败用的。如果工具返回一个没有ok字段的错误对象上层很容易误判成功。这个坑我在早期版本踩过order_send返回None时代码没检查结果 Agent 以为下单成功了实际什么都没发生。平仓和改单工具同理只是权限闸用EASYDEAL_TRADING_WRITEdef close_position(ticket: int) - dict: 平仓。需要 EASYDEAL_TRADING_WRITE1 if not _write_enabled(): return {ok: False, error: write_disabled} pos mt5.positions_get(ticketticket) if not pos: return {ok: False, error: fposition_not_found:{ticket}} p pos[0] req { action: mt5.TRADE_ACTION_DEAL, symbol: p.symbol, volume: p.volume, type: mt5.ORDER_TYPE_SELL if p.type mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY, position: ticket, price: mt5.symbol_info_tick(p.symbol).bid if p.type mt5.POSITION_TYPE_BUY else mt5.symbol_info_tick(p.symbol).ask, magic: p.magic, comment: mcp_agent_close, type_time: mt5.ORDER_TIME_GTC, type_filling: mt5.ORDER_FILLING_IOC, } result mt5.order_send(req) ok result is not None and result.retcode mt5.TRADE_RETCODE_DONE return { ok: ok, ticket: ticket, retcode: getattr(result, retcode, None), message: None if ok else fclose retcode{result.retcode}, }工具声明完之后MCP Server 启动时根据环境变量决定暴露哪些工具。只读工具无条件注册动钱工具按开关注册。这样模型看到的工具列表就是「当前允许它做的事」的精确映射。模型侧配置如果你用 Claude Code可以在~/.claude/settings.json里配 MCP Server如果用 Cline在 MCP 配置面板里填。三件套Base URL Key Model ID缺一不可# 示例Cline MCP 配置片段 [mcp_servers.easydeal-mt5] command python args [/path/to/easydeal_mcp_server.py] [mcp_servers.easydeal-mt5.env] MT5_LOGIN 你的模拟账号 MT5_PASSWORD 你的密码 MT5_SERVER 你的券商服务器 EASYDEAL_TRADING_WRITE 0 EASYDEAL_TRADING_WRITE_OPEN 0模型 ID 和 Key 走 TaoToken 的话Base URL 填https://taotoken.net/apiKey 在控制台拿。这样你切模型只改 Model ID不用动其他配置。4. 验证请求模拟账户跑通下单与撤单闭环配置写完先别急着开写权限。第一步是验证只读链路。启动 MCP Server然后在 Agent 里问一句「帮我看看当前账户状态和持仓」。如果一切正常模型会调用list_all_mt5_status返回你的模拟账户余额、净值、持仓列表。这一步能过说明 MCP 协议通了、MT5 连接通了、工具注册对了。如果这一步就失败先查三个地方MT5 终端是否在运行、是否登录了模拟账户、Python 的 MetaTrader5 库版本是否和终端匹配。mt5.initialize()返回False时用mt5.last_error()看具体错误码。只读通了之后开写权限测下单。把环境变量改成export EASYDEAL_TRADING_WRITE1 export EASYDEAL_TRADING_WRITE_OPEN1重启 MCP Server然后在 Agent 里下指令「用模拟账户买入 0.01 手 XAUUSD带 50 点止损」。模型会调open_position返回里应该有ok: true和一个 ticket 号。拿到 ticket 后验证撤单闭环。这里分两种情况如果单子已经成交变成持仓用close_position平掉如果还是挂单用mt5.order_send发TRADE_ACTION_REMOVE撤掉。模拟账户上跑一遍确认 ticket 从持仓列表里消失。一个完整的验证流程大概是这样# 验证脚本跑一遍下单-查询-平仓 import MetaTrader5 as mt5 mt5.initialize() # 1. 下单 r open_position(XAUUSD, 0.01, buy, sl0.0, tp0.0) print(open:, r) assert r[ok], r ticket r[ticket] # 2. 查持仓 positions mt5.positions_get(ticketticket) print(positions:, positions) assert positions and len(positions) 1 # 3. 平仓 c close_position(ticket) print(close:, c) assert c[ok], c # 4. 确认已平 positions mt5.positions_get(ticketticket) print(after close:, positions) assert not positions print(闭环验证通过)跑通这个脚本说明你的 MCP 交易工具最小可用版本成了。接下来才是接模型、让模型自主调用。接模型验证时用 TaoToken 的统一通道模型对话入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。你可以先在对话里测「读持仓」这类只读指令确认模型能正确解析工具返回的 JSON。再测「买入 0.01 手」这类写指令观察模型是否正确填充了 symbol、volume、order_type 三个参数。这里有个细节模型填参数时可能会把order_type填成BUY而不是buy或者把 volume 填成字符串0.01。你的工具函数入口要做一次规范化别假设模型一定填对。这是 ReAct 循环里最常见的翻车点。日志这块建议在 MCP Server 里加一层请求日志每次工具调用记录工具名、入参、返回、耗时。出问题时翻日志比猜快得多。日志写到文件别只打 stdout因为 MCP 的 stdout 是协议通道混入日志会污染通信。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节列几个真实会撞上的报错以及排查方向。401 Unauthorized。模型侧报 401基本是 Key 或 Base URL 配错了。检查三件套Base URL 是不是https://taotoken.net/api注意别多加路径、Key 有没有复制全、Model ID 是不是当前通道支持的。如果 Key 是从控制台新生成的确认没有多余空格。401 和 403 要分清401 是没认证403 是认证了但没权限后者可能是模型 ID 不在你的套餐里。local proxy failed / connection refused。这个通常出现在 MCP Server 启动阶段。原因可能是Python 路径不对、脚本路径不对、依赖没装全。先在命令行手动跑一遍python /path/to/easydeal_mcp_server.py看能不能起来。如果报ModuleNotFoundError: MetaTrader5就是库没装。如果报mt5.initialize() failed就是 MT5 终端没开或没登录。reading choices of undefined。这是模型返回结构解析失败常见于流式响应处理。如果你用的是某个 SDK检查它是否兼容当前 API 的返回格式。有时候是 Model ID 填错了通道返回了一个非预期的结构。换成文档里明确支持的模型 ID 再试。OAuth / token expired。如果你用的是 Claude Code 或某些需要 OAuth 的客户端token 过期会报这个。重新走一遍授权流程或者换成 API Key 方式接入。用 TaoToken 的统一 Key 通道可以绕开这类客户端 OAuth 的坑因为认证走的是标准 API Key。order_send retcode 非 0。这是交易侧最常见的。retcode对照表里10004是 requote重新报价10006是 rejected被拒10013是 invalid request请求无效10014是 invalid volume手数无效10016是 invalid stops止损止盈无效10018是 market closed市场关闭。10014就回去查手数量化逻辑10016就查止损止盈跟当前价的距离是否满足券商最小 stop level。工具列表里看不到动钱工具。先确认环境变量设了没、MCP Server 重启了没。环境变量是在 Server 启动时读的改了不重启不生效。另外确认变量名拼写EASYDEAL_TRADING_WRITE_OPEN别写成EASYDEAL_TRADE_WRITE_OPEN。模型不调用工具只在那聊天。这通常是模型能力问题换工具调用能力更强的模型。另外检查工具描述description写得够不够清楚模型靠描述判断什么时候该调哪个工具。描述里把「什么时候用」写明白比只写「这个工具做什么」有效。品种名匹配到错误的品种。模糊匹配太宽会误伤比如XAU可能匹配到XAUUSD也可能匹配到XAUCNH。匹配逻辑里加一层「行情可见」过滤再按名称长度排序取最接近的。实在拿不准让工具返回候选列表让模型或用户确认。排障时有个通用原则先隔离层。模型侧的问题和交易侧的问题分开测。只读工具能通说明 MCP 和 MT5 连接没问题问题在写权限或模型参数填充只读工具都不通说明底层连接有问题先别碰写逻辑。6. 把交易能力接进你的 Agent 工作流跑通最小闭环之后下一步是把它接进日常开发流。如果你长期用 Claude Code 或 Cline 写代码顺手把 MT5 工具挂上就能在写策略的同时让 Agent 读实时持仓、查行情、甚至按你的指令调仓。这种「编码 交易」一体的工作流用 Coding Plan 会更顺https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。几个实战建议。第一写操作永远保留人工确认。哪怕权限开了也建议在 Agent 的提示词里加一句「执行任何开仓/平仓前先向我复述一遍参数并等待确认」。模型会照做这一步能挡掉大部分误操作。第二改 EA 源码要备份。如果你的工具里有「改 EA 参数」甚至「改 MQL5 源码」的能力改前自动备份、可回滚。而且 MQL5 改完通常要人工在 MetaEditor 里编译才生效别假设 AI 改完就自动生效。第三日志留全。每次工具调用的入参、返回、时间戳都记下来。交易类操作出问题事后复盘全靠日志。第四模拟账户先跑够。别急着上实盘。模拟账户上把各种边界情况跑一遍手数超限、止损太近、市场关闭、网络抖动重试。这些在模拟盘上暴露出来比在实盘上暴露便宜得多。第五错误重试要有上限。order_send遇到 requote 可以重试但别无限重试。设个 3 次上限超了就返回失败让模型决定下一步。无限重试在交易场景里可能造成重复下单。如果你不想从零写这套工具可以参考开源的 EasyDeal 实现它把读状态、改参数、授权下单这一整套都做好了GPL-3.0 协议。你可以在它的基础上改或者对照它的设计检查自己的实现。最后说个我自己的体会给大模型加交易能力难点从来不是「怎么让模型调工具」而是「怎么设计一组安全的工具」。读写分层、权限闸、品种模糊匹配、返回带 ok、写操作隔离与确认——这五条做到了剩下的就是工程细节。模型侧用 TaoToken 统一通道省掉多模型 Key 管理的麻烦把精力放在工具设计上这才是正事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑