开发Python交易机器人(第三部分):实现基于模型的交易算法(基础篇)
把训练好的模型接进 MT5 实盘
前两步已经把数据打标、模型训练加交叉验证跑完,下一步是用 Python 直连 MT5 下单。MetaTrader 5 官方给的 Python 包把平台里的函数和系统类都暴露出来,等于在本地脚本里直接调终端能力,不必再绕 MQL5 的 EA 壳。 对做价格行为结合 AIGC 的交易者来说,这套接法的价值在于:模型侧用 sklearn 或 torch 跑完,信号可以直接通过同一个进程发到贵金属或外汇账户。外汇与贵金属杠杆高、滑点和跳空频繁,实盘前务必在策略测试器外再用 demo 账户跑一遍连通性。 本小节只交代链路起点——后面才会写具体下单与仓位函数,但你现在就该在终端开好 Python 环境、装好 MetaTrader5 库,否则后续代码粘过来也跑不动。
◍ 用 Python 库把模型信号接进 MT5 下单
把训练好的价格方向模型转成实盘动作,核心就是按标签开仓:模型吐出「涨」就发多头订单,「跌」就发空头订单,两张单都带固定的止损与止盈距离,不靠人工盯盘。 MetaTrader 5 的 Python 库(MetaTrader5 模块)能直接下订单、平仓、改 SL/TP,等于把模型预测和券商执行打通成一条自动链路。之前分析和训练阶段攒下的特征与阈值,在这里变成实时推送的开仓/平仓指令,算法连续性取决于你推送频率和数据延迟。 外汇与贵金属杠杆高,自动算法若标签翻转快,可能在窄幅震荡里连续触发小额止损,实盘前务必在 MT5 策略测试器用历史 tick 跑一遍信号延迟。
「把 MT5 接进 Python 的第一步」
要让小布这类 AIGC 工具直接读 MT5 行情,得先打通本地环境。核心是装齐 Python 依赖,并把终端可执行文件路径指给代码。 依赖建议用 pip 一次性拉齐:numpy、pandas 负责数组与表处理,MetaTrader5 是官方桥接包,scikit-learn 和 xgboost 留给后续训练。路径那行必须换成你机器上 terminal64.exe 的真实位置,RoboForex 这种带券商名的目录只是示例。 外汇与贵金属杠杆高、滑点随机,环境跑通不代表策略能盈利,仅代表数据管道可用。
pip install numpy pandas MetaTrader5 scikit-learn xgboost
terminal_path = "C:/Program Files/RoboForex - MetaTrader class="num">5/Arima/terminal64.exe"让模型预测直接变成MT5挂单
把训练好的模型接进实盘,核心就是一个 online_trading 函数:传入品种、特征向量和模型对象,它负责连终端、取报价、出信号、下单单。函数先按 terminal_path 指向的 exe 初始化 MT5,连不上就直接退出,不浪费后续轮询。 信号判定很直白:模型输出最后一个值大于 0.5 倾向开多,小于 0.5 倾向开空。每笔固定手数 0.3,多单 SL 放在 ask 下方 150 点、TP 放在 ask 上方 800 点;空单对称处理。点值是 symbol_info.point,所以止损止盈是随品种精度自动换算的。 并发控制靠 MAX_OPEN_TRADES 常量:持仓数没到上限且信号明确才发请求,否则空转等下一 tick。外汇和贵金属杠杆高,SL/TP 虽能限损,但滑点和断连仍可能让实际亏损超出预设,跑之前先在策略测试器验证点值计算。 代码里初始化路径写死为 RoboForex 的 terminal64.exe,换券商得改字符串;attempts 设了 30000 次等待品种信息,按每次 sleep 5 秒算最长可能卡 150000 秒,实盘前建议调小。
def online_trading(symbol, features, model): terminal_path = "C:/Program Files/RoboForex - MetaTrader class="num">5/Arima/terminal64.exe" if not mt5.initialize(path=terminal_path): print("Error: Failed to connect to MetaTrader class="num">5 terminal") class="kw">return open_trades = class="num">0 e = None attempts = class="num">30000 while True: symbol_info = mt5.symbol_info(symbol) if symbol_info is not None: class="kw">break else: print("Error: Instrument not found. Attempt {} of {}".format(_ + class="num">1, attempts)) time.sleep(class="num">5) while True: price_bid = mt5.symbol_info_tick(symbol).bid price_ask = mt5.symbol_info_tick(symbol).ask signal = model.predict(features) positions_total = mt5.positions_total() for _ in range(attempts): if positions_total < MAX_OPEN_TRADES and signal[-class="num">1] > class="num">0.5: request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": class="num">0.3, "type": mt5.ORDER_TYPE_BUY, "price": price_ask, "sl": price_ask - class="num">150 * symbol_info.point, "tp": price_ask + class="num">800 * symbol_info.point, "deviation": class="num">20, "magic": class="num">123456, "comment": "Test deal", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } elif positions_total < MAX_OPEN_TRADES and signal[-class="num">1] < class="num">0.5: request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": class="num">0.3, "type": mt5.ORDER_TYPE_SELL, "price": price_bid,
◍ 下单请求结构与失败重试的实盘细节
这段逻辑把止损、止盈直接挂在 bid 上加点数:sl 取 price_bid + 150 * point,tp 取 price_bid - 800 * point,意味着默认按空单思路摆盘;点差容差 deviation 设 20,magic 编号 123456 便于后续在 MT5 终端里筛单。 订单填充方式写死 ORDER_FILLING_FOK(全部成交或废单),时效 ORDER_TIME_GTC(挂到手动取消),这种组合在流动性薄的贵金属时段可能直接被拒,需根据品种调成 IOC 或 RETURN。 发单后若 retcode 不是 TRADE_RETCODE_DONE,会 print 错误码并 sleep(3) 做下一次尝试;外层循环间隔 sleep(4000) 毫秒约 4 秒轮一次。外汇与贵金属杠杆高,重发逻辑若不加全局次数锁,遇到券商拒绝可能刷出连单。 process_symbol 里串了取数、增广、标注、GMM 聚 4 类、特征工程选 10 维、再训 XGBoost,generate_new_features 固定 random_seed=1 生成 100 个候选特征,回测可复现但容易过拟合窄样本。
"sl": price_bid + class="num">150 * symbol_info.point, "tp": price_bid - class="num">800 * symbol_info.point, "deviation": class="num">20, "magic": class="num">123456, "comment": "Test deal", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } else: print("No signal to open a position") class="kw">return None result = mt5.order_send(request) if result.retcode == mt5.TRADE_RETCODE_DONE: if signal[-class="num">1] < class="num">0.5: print("Buy position opened") open_trades += class="num">1 elif signal[-class="num">1] > class="num">0.5: print("Sell position opened") open_trades += class="num">1 class="kw">return result.order else: print("Error: Trade request not executed, retcode={}. Attempt {}/{}".format(result.retcode, _ + class="num">1, attempts)) time.sleep(class="num">3) time.sleep(class="num">4000) def process_symbol(symbol): try: # Retrieve data for the specified symbol raw_data = retrieve_data(symbol) if raw_data is None: print("No data found for symbol {}".format(symbol)) class="kw">return None # Augment data augmented_data = augment_data(raw_data) # Markup data marked_data = markup_data(augmented_data.copy(), &class="macro">#x27;close&class="macro">#x27;, &class="macro">#x27;label&class="macro">#x27;) # Label data labeled_data = label_data(marked_data, symbol) # Generate new features labeled_data_generate = generate_new_features(labeled_data, num_features=class="num">100, random_seed=class="num">1) # Cluster features by GMM labeled_data_clustered = cluster_features_by_gmm(labeled_data_generate, n_components=class="num">4) # Feature engineering labeled_data_engineered = feature_engineering(labeled_data_clustered, n_features_to_select=class="num">10) # Train XGBoost classifier train_data = labeled_data_engineered[labeled_data_engineered.index <= FORWARD]
「让算法在终端里跑起来」
真正进入实盘前,先在 MT5 中调用 process_symbol 并传入具体品种,例如 "XAUUSD",这一步把控制权重交给在线交易逻辑。 在 process_symbol 内部会继续调用 online_trading,把所需参数透传进去,随后进入一个常驻循环:只要不被外部中断,它就会持续监听与处理该品种的信号。外汇与贵金属波动剧烈、杠杆风险极高,常驻逻辑必须能随时手动停掉。 循环尾部固定 sleep 6 秒,目的是给终端喘息,避免高频轮询把 MetaTrader 5 资源吃满。若执行途中触发异常,算法会打印错误并直接中止,而不是带着脏状态继续跑。 这套启动方式没有隐藏收益假设,能否平稳运行只取决于你传入的品种流动性和终端负载,开 MT5 挂个模拟盘跑十分钟就能验证 6 秒节拍是否适合你的机器。