开发具有 RestAPI 集成的 MQL5 强化学习代理(第 2 部分):用于与井字游戏 RestAPI 进行 HTTP 交互的 MQL5 函数·进阶篇
📘

开发具有 RestAPI 集成的 MQL5 强化学习代理(第 2 部分):用于与井字游戏 RestAPI 进行 HTTP 交互的 MQL5 函数·进阶篇

第 2/2 篇

用井字棋做 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 分支跑通,外汇与贵金属杠杆高、滑点随机,任何跨进程调用多一跳都可能放大风险。

MQL5 / C++
class="kw">import random
<span class="keyword">class</span> TicTacToe:
&nbsp;&nbsp;&nbsp;&nbsp;def __init__(self):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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>)]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.player_turn = True
&nbsp;&nbsp;&nbsp;&nbsp;def print_board(self):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> row <span class="keyword">in</span> self.board:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(<span class="class="type">class="kw">string">"|"</span>.<span class="keyword">join</span>(row))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(<span class="class="type">class="kw">string">"-"</span> * <span class="number">class="num">5</span>)
&nbsp;&nbsp;&nbsp;&nbsp;def check_winner(self):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> i in range(<span class="number">class="num">3</span>):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> self.board[i][<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][i]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> self.board[<span class="number">class="num">0</span>][<span class="number">class="num">2</span>]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> None
&nbsp;&nbsp;&nbsp;&nbsp;def make_move(self, row, col):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> self.board[row][col] == <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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 为你的行情状态机即可。

MQL5 / C++
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 库,不然版本一变脚本就哑火。

MQL5 / C++
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 只是手里的两件工具。下次想扩能力边界,不妨从改一改测试脚本的请求参数开始。

常见问题

可以。文章用井字棋搭了一个MQL5通过HTTP访问FastAPI的沙盒,先把这套请求响应跑通,再换成你自己的模型接口更安全。
用WebRequest发GET/POST到FastAPI暴露的地址,注意在EA设置里允许URL、处理返回JSON字符串并做错误判断。
小布可以替你跑通接口连通性检查与返回结构诊断,你只需专注策略逻辑,不必手动反复发请求看报错。
不能。井字棋只是验证通信链路的玩具环境,真实行情有滑点延迟和高风险,外部模型决策需另行严谨回测。
服务端做棋盘状态校验并返回状态码与提示,MQL5端解析返回字段,非200或带error字段就放弃本次动作。