开发具有 RestAPI 集成的 MQL5 强化学习代理(第 2 部分):用于与井字游戏 RestAPI 进行 HTTP 交互的 MQL5 函数·进阶篇
用井字棋做 MQL5 与 Python 的桥接沙盒
想把 MQL5 的信号丢给 Python 处理,最省事的练手环境不是直接啃行情接口,而是先写一个井字棋类,让 MT5 后续通过 HTTP 去调用它。TicTacToe 类用 3x3 二维列表存棋盘,player_turn 用布尔值标记先后手,初始化时全格为空、首手为 True。 check_winner 遍历 3 行、3 列及 2 条对角线,任一方向三格相同且非空就返回该符号,否则回 None;make_move 先判目标格是否为空,空则落子并翻转 player_turn,否则报无效。这段逻辑本身跑不起来交易,但它是纯内存状态机,方便验证双向通信是否丢包。 真正的粘合层是 FastAPI:用 Pydantic 定义 GameBoard(board 列表 + player_turn 字符串)和 PlayerMove(row、col),开 /start-game/ 建实例回 game_id,开 /play/{game_id}/ 走子并返回新棋盘;Uvicorn 起 ASGI 服务后,MQL5 用 WebRequest 就能按 id 轮询。 FastAPI 在 Python 3.6+ 下基于类型提示,Swagger UI 与 ReDoc 文档随代码自动更新,省掉手维护接口说明的精力。其底层用 Starlette 路由、Uvicorn 执行,属于 Python 里吞吐靠前的框架,对需要低延迟回应的本地策略桥接够用。 别把正态当圣经 井字棋只是验证通道的探针,它不是策略本身。接实盘前先在 127.0.0.1 把 game_id 生命周期和 404 分支跑通,外汇与贵金属杠杆高、滑点随机,任何跨进程调用多一跳都可能放大风险。
class="kw">import random <span class="keyword">class</span> TicTacToe: def __init__(self): self.board = [[<span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span> <span class="keyword">for</span> _ <span class="keyword">in</span> range(<span class="number">class="num">3</span>)] <span class="keyword">for</span> _ <span class="keyword">in</span> range(<span class="number">class="num">3</span>)] self.player_turn = True def print_board(self): <span class="keyword">for</span> row <span class="keyword">in</span> self.board: print(<span class="class="type">class="kw">string">"|"</span>.<span class="keyword">join</span>(row)) print(<span class="class="type">class="kw">string">"-"</span> * <span class="number">class="num">5</span>) def check_winner(self): <span class="keyword">for</span> i in range(<span class="number">class="num">3</span>): <span class="keyword">if</span> self.board[i][<span class="number">class="num">0</span>] == self.board[i][<span class="number">class="num">1</span>] == self.board[i][<span class="number">class="num">2</span>] != <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>: <span class="keyword">class="kw">return</span> self.board[i][<span class="number">class="num">0</span>] <span class="keyword">if</span> self.board[<span class="number">class="num">0</span>][i] == self.board[<span class="number">class="num">1</span>][i] == self.board[<span class="number">class="num">2</span>][i] != <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>: <span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][i] <span class="keyword">if</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">0</span>] == self.board[<span class="number">class="num">1</span>][<span class="number">class="num">1</span>] == self.board[<span class="number">class="num">2</span>][<span class="number">class="num">2</span>] != <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>: <span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">0</span>] <span class="keyword">if</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">2</span>] == self.board[<span class="number">class="num">1</span>][<span class="number">class="num">1</span>] == self.board[<span class="number">class="num">2</span>][<span class="number">class="num">0</span>] != <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>: <span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">2</span>] <span class="keyword">class="kw">return</span> None def make_move(self, row, col): <span class="keyword">if</span> self.board[row][col] == <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>: <span class="keyword">if</span> self.player_turn:
「用 FastAPI 把井字棋对局暴露成接口」
这段实现把前面的 TicTacToe 类包了一层 HTTP 服务,核心依赖只有 fastapi、pydantic 和 uvicorn 三个库。games 字典以自增整数做 game_id,新开一局时 id 等于当前局数加 1,本地多局并行不会串状态。 两个数据模型 GameBoard 与 PlayerMove 用 pydantic 锁字段类型,其中 PlayerMove 只收 row、col 两个 int,避免前端传错坐标类型直接 422。 路由层面,GET /start-game/ 负责建局并返回 game_id;POST /play/{game_id}/ 先查局号,查不到抛 404,落子异常转 400。每次落子后服务端会 print_board() 打到控制台,方便你盯着终端看棋盘演化,返回体里带 board 和 player_turn 供前端续接。 最后 __main__ 里 uvicorn.run 绑 0.0.0.0:8000,意味着同网段机器都能访问。你在 MT5 之外想做策略沙盒联调时,这套轻量接口可以直接复用,改 TicTacToe 为你的行情状态机即可。
from fastapi class="kw">import FastAPI, HTTPException from pydantic class="kw">import BaseModel from tic_tac_toe class="kw">import TicTacToe app = FastAPI() games = {} class GameBoard(BaseModel): board: list player_turn: str class PlayerMove(BaseModel): row: class="type">int col: class="type">int @app.get("/start-game/") def start_game(): game_id = len(games) + class="num">1 game = TicTacToe() games[game_id] = game class="kw">return {"game_id": game_id} @app.post("/play/{game_id}/") def play(game_id: class="type">int, move: PlayerMove): game = games.get(game_id) if not game: raise HTTPException(status_code=class="num">404, detail="Game not found") try: game.make_move(move.row, move.col) except ValueError as e: raise HTTPException(status_code=class="num">400, detail=str(e)) game.print_board() class="kw">return { "board": game.board, "player_turn": game.player_turn, } if __name__ == "__main__": class="kw">import uvicorn uvicorn.run(app, host="class="num">0.0.class="num">0.0", port=class="num">8000)
◍ 把井字棋脚本跑通看 MQL5 怎么调外部 API
下面这段 MQL5 脚本是拿井字游戏当载体,验证它跟本地 FastAPI 服务能不能真正对话。思路不复杂:脚本启动后先 GET 一个开局接口,拿到 gameId,再带着坐标 JSON 用 POST 逐步落子,每一步都把返回写进日志。 代码里第一行的 apiUrl 写死成 http://localhost:8000,意味着你本机得先起好 FastAPI 服务,否则 Request 必然失败。PrintResponse 只是把响应原样打到 MQL5 日志,方便你核对每次请求到底回了什么。 OnStart 里用 Request("GET", response, apiUrl + "/start-game/") 开新局,startGameRes 存的是 HTTP 层返回码而非业务逻辑;真正要用的是后面从 response 抠出来的 gameId——脚本用 StringFind 找冒号再 Substr 取子串,这种解析方式只适配返回体是 "gameId:123" 这种极简格式,换成正经 JSON 就得改。 落子部分 jsonMove1 写的是 {"row":0,"col":0},通过 POST 到 /play/{gameId}/ 发过去。整段只演示了三次交替移动里的第一手,后续重复同样结构即可。外汇与贵金属交易外接 API 同样有这类网络依赖风险,实盘前务必在模拟环境验证连通性与超时处理。 别把正态当圣经:MQL5 原生不擅长解析复杂 JSON,上面靠字符串截取拿 ID 只是测试取巧。真要接行情或信号 API,建议先确认对方返回结构稳定,或改用第三方 JSON 库,不然版本一变脚本就哑火。
input class="type">class="kw">string apiUrl = "http:class=class="str">"cmt">//localhost:class="num">8000"; class="type">void PrintResponse(class="type">class="kw">string response) { Print("Resposta da API: ", response); } class="type">void OnStart() { class="type">class="kw">string response; class="type">int startGameRes = Request("GET", response, apiUrl + "/start-game/"); class=class="str">"cmt">// ... } class="type">class="kw">string gameIdStr = StringSubstr(response, StringFind(response, ":") + class="num">1); class="type">uint gameId = StringToInteger(gameIdStr); class="type">class="kw">string jsonMove1 = "{\"row\": class="num">0, \"col\": class="num">0}"; class=class="str">"cmt">// ... class="type">int playRes1 = Request("POST", response, apiUrl + "/play/" + IntegerToString(gameId) + "/", jsonMove1);
别急着下结论
前面几节把 MQL5 的 HTTP 调用、FastAPI 桥接和井字游戏测试脚本都跑通了,但这类跨语言整合离直接用于外汇或贵金属实盘还差得远。MQL5 调 Python 服务端只是验证了「终端能当客户端用」这件事,真正要落到行情研判或下单,还得自己接行情源、写策略层。 附带工程 Parte_02.zip 约 35.95 KB,里面是第二部分的脚本与 API 定义,开 MT5 加载测试 EA 就能复现终端拉取 FastAPI 返回棋步的过程。外汇与贵金属杠杆高、滑点无常,跨进程调用还引入额外延迟,实盘前务必在策略测试器里压测。 技术世界没有终点,MQL5 和 Python 只是手里的两件工具。下次想扩能力边界,不妨从改一改测试脚本的请求参数开始。