使用MQL5和Python集成经纪商API与智能交易系统·进阶篇
(2/3)·手动登portal充钱太慢?这篇用Python脚本把MT5和券商API焊死,余额阈值触发自动入金
不少交易者账户余额跌破警戒线才想起来登经纪商后台手动入金,行情早跑了一段。把补仓动作绑在EA里而不是浏览器里,才能把资金断裂的窗口压到最小。外汇与贵金属杠杆高,断档即意味被动平仓,这条链路值得自己搭一遍。
◍ 用 Python 给 MT5 接上券商 WebSocket 通道
想让 MT5 的 EA 去调券商的账户接口(比如 Deriv 的入金指令),最轻量的办法是写个独立 Python 脚本在后台跑,靠两个 txt 文件跟 MQL5 做中介。脚本命名 deriv_api_handler.py,保存路径必须记牢,后面 EA 里要硬写进去。 初始化阶段先 import 四个库:json 管消息序列化,websocket 负责跟券商 API 握手,time 做必要的 sleep 暂停,os 用来检查指令文件是否存在。API_URL 和 API_TOKEN 是连接 Deriv 服务器的钥匙,文件路径要选 MQL5 的 Common/Files 目录(如 C:/Users/YourComputerName/AppData/Roaming/MetaQuotes/Terminal/Common/Files/),否则两边有权限读不到。 connect_to_deriv() 函数里用 ws.connect(API_URL) 建连,随后发 authorize 消息做身份校验;服务器回的 JSON 若带 error 字段就打印并 return False,用 try-except 包住避免整脚本崩。process_command() 把 MQL5 传来的 JSON 字符串解析成字典,看到键 mt5_deposit 就调专门的 mt5_deposit() 函数,未知命令回错误信息——这种模块化方便以后加新指令类型。 读指令靠检查 mql5_to_python.txt:存在就读内容、去 BOM 和空白、打印调试、然后删文件,保证同一条指令不会被重复处理;没有文件就 return None。回写走 python_to_mql5.txt,用 json.dumps() 把字典转字符串落盘,闭环完成。主循环持续调 read_command,有指令就处理并写回,异常时打印错误干净退出,维持实时响应。外汇/贵金属券商 API 涉及账户资金操作,属于高风险动作,实盘前务必在模拟环境跑通整套文件握手。
class="kw">import json class="kw">import websocket class="kw">import time class="kw">import os # Configuration API_URL = "wss:class=class="str">"cmt">//ws.binaryws.com/websockets/v3?app_id= Your app ID" class="macro">#Replace with your App ID created in Deriv API dashboard API_TOKEN = "Your API token" class="macro">#Replace with your actual token # File paths(Replace YourComputerName with the actual name of your computer) MQL5_TO_PYTHON = "C:/Users/YourComputerName/AppData/Roaming/MetaQuotes/Terminal/Common/Files/mql5_to_python.txt" PYTHON_TO_MQL5 = "C:/Users/YourComputerName/AppData/Roaming/MetaQuotes/Terminal/Common/Files/python_to_mql5.txt" def connect_to_deriv(): """Connects to Deriv&class="macro">#x27;s WebSocket API.""" try: ws.connect(API_URL) ws.send(json.dumps({"authorize": API_TOKEN})) response = json.loads(ws.recv()) print(f"Authorization Response: {response}") if response.get("error"): print("Authorization failed:", response["error"]["message"]) class="kw">return False class="kw">return True except Exception as e: print(f"Error during authorization: {e}") class="kw">return False def process_command(command): """Processes a command from MQL5.""" try: command_data = json.loads(command) # Parse the JSON command if "mt5_deposit" in command_data: class="kw">return mt5_deposit(
MT5 与 Python 桥接的命令收发细节
这段桥接逻辑里,MQL5 侧把指令写进文件,Python 侧用 read_command() 轮询并删除源文件,避免重复执行。实测中若文件带 BOM(\ufeff),json.loads 会直接抛错,所以代码里专门做了 startswith('\ufeff') 的剥离。 mt5_deposit() 通过 WebSocket 向服务端推送 mt5_deposit:1 及金额、来源账户、目标账户字段,再用 ws.recv() 阻塞等回执。任何异常都被捕获成 {'error': ...} 字典返回,不会让守护进程崩掉。 write_response() 把 Python 的回执 json.dumps 后写回 PYTHON_TO_MQL5,MQL5 EA 下次读文件就能拿到结果。外汇与贵金属跨账户调拨属高风险操作,桥接失败可能导致指令丢失,建议先在模拟账户验证文件读写延迟(通常 10~50ms 级)。
def mt5_deposit(amount, from_binary, to_mt5): """Performs a deposit operation to the MT5 account.""" try: ws.send(json.dumps({ "mt5_deposit": class="num">1, "amount": amount, "from_binary": from_binary, "to_mt5": to_mt5 })) response = ws.recv() class="kw">return json.loads(response) except Exception as e: class="kw">return {"error": f"Error during deposit operation: {e}"} def read_command(): """Reads a command from the MQL5 file and deletes the file after reading.""" print(f"Checking for command file at: {MQL5_TO_PYTHON}") if os.path.exists(MQL5_TO_PYTHON): print(f"Command file found: {MQL5_TO_PYTHON}") with open(MQL5_TO_PYTHON, "r", encoding="utf-class="num">8") as file: command = file.read().strip() print(f"Raw Command read: {repr(command)}") # Strip potential BOM and whitespace if command.startswith("\ufeff"): command = command[class="num">1:] print(f"Processed Command: {repr(command)}") os.remove(MQL5_TO_PYTHON) # Remove file after reading class="kw">return command print(f"Command file not found at: {MQL5_TO_PYTHON}") class="kw">return None def write_response(response): """Writes a response to the MQL5 file.""" with open(PYTHON_TO_MQL5, "w", encoding="utf-class="num">8") as file: file.write(json.dumps(response))
「命令轮询的死循环与单次退出」
这段逻辑把主流程包在一个 while True 里,本意是持续监听外部命令文件,但实际只在首次读到或读不到时就 break,等于把“死循环”退化成“跑一次就退”。 read_command() 负责从磁盘或管道取指令;拿到非空字符串就打印 Processing command、交给 process_command 处理、再把回写结果落到 write_response,随后打印 Response written 并 break。若返回空(文件缺失),直接打印 No command file found 并退出。 except 块捕获任意异常,打印 Error in main loop 后同样 break——三种分支殊途同归:循环最多迭代一次。若你想做真正的多指令队列,得把 break 换成 sleep + continue,否则 EA 初始化即停。外汇与贵金属行情瞬息,这类空转脚本可能错过入场窗口,属高风险用法。
class="kw">while True: try: command = read_command() if command: print(f"Processing command: {command}") response = process_command(command) print(f"Response: {response}") write_response(response) print("Response written. Exiting loop.") break # Exit the loop after processing one command else: print("No command file found. Exiting.") break # Exit the loop if the command file is not found except Exception as e: print(f"Error in main loop: {e}") break # Exit the loop on unexpected error
◍ 让EA自己盯着余额去调Python入金
做资金管理的EA不一定非得下单,这一节的重点是把券商API接进MT5里,让程序在余额跌破线时自动喊外部脚本去入金。核心思路是用shell32的ShellExecuteW从EA内部拉起deriv_api_handler.py,MQL5只负责判余额和写指令文件,真正的转账动作交给Python侧。 为了方便测试,作者把检查逻辑全塞进了OnInit(),而不是常规的OnTimer()。这样EA一加载就跑一次API验证,不用等周期触发;但真要长周期监控账户,还是得挪到OnTimer()里做定时轮询,否则每次重启终端才查一次。 输入参数里有两个硬数字值得注意:balance_threshold和top_up_amount默认都是100000.0,也就是余额低于10万才触发、单次补10万。from_binary和to_mt5分别是券商二进制账户ID和MT5账户ID,留了CR0000000和MTRReplace做占位,实盘前必须换成真实账号。 通信靠一个mql5_to_python.txt的JSON指令文件,EA用FileOpen写进TerminalInfoString(TERMINAL_COMMONDATA_PATH)下的Files目录,Python跑完再回写一个响应文件,EA读到了“成功”才继续。外汇和贵金属账户涉及真实资金调拨,这种自动入金链路任何环节失败都可能卡死流程,建议在模拟环境先跑通再碰实盘。 下面这段是EA头部和OnInit入口的原文,逐行看清楚导入和判断怎么落的: #property copyright "Copyright 2024, Clemence Benjamin" // 设EA元数据版权信息 #property link "[MQL5官方文档] // 设作者链接 #property version "1.01" // 设EA版本号 #property description "Deposit and Withdraw funds between broker and trading account within the EA" // 设EA功能描述 #import "shell32.dll" // 导入shell32动态库 int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); // 声明Windows API函数,用于执行外部程序 #import // 结束导入段 input double balance_threshold = 100000.0; // 定义余额阈值输入项,默认10万 input double top_up_amount = 100000.0; // 定义补仓金额输入项,默认10万 input string from_binary = "CR0000000"; // 定义出金源账户ID输入项,占位 input string to_mt5 = "MTRReplace"; // 定义入金目标MT5账户ID输入项,占位 input string python_script_path = "C:\\Users\\YourComputerName\\PathTo\\deriv_api_handler.py"; // 定义Python脚本绝对路径输入项 input string python_exe = "python"; // 定义Python可执行命令输入项,依赖PATH环境 int OnInit() // EA初始化入口函数 { // 函数体开始 double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); // 取当前账户余额 Print("Current Account Balance: ", current_balance); // 打印余额到日志 if(current_balance < balance_threshold) // 判断余额是否低于阈值 { // 条件成立进入入金分支 Print("Balance is below the threshold. Attempting a deposit..."); // 打印触发提示 string command_file = "mql5_to_python.txt"; // 定义指令文件名 string command_path = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files\\" + command_file; // 拼出公共文件目录全路径
| int handle = FileOpen(command_file, FILE_WRITE | FILE_COMMON | FILE_TXT | FILE_ANSI); // 以写/公共/文本/ANSI模式打开指令文件 |
|---|
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fund Manager EA.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, Clemence Benjamin | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, Clemence Benjamin" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.01" class="macro">#class="kw">property description "Deposit and Withdraw funds between broker and trading account within the EA" class="macro">#class="kw">import "shell32.dll" class="type">int ShellExecuteW(class="type">int hwnd, class="type">class="kw">string lpOperation, class="type">class="kw">string lpFile, class="type">class="kw">string lpParameters, class="type">class="kw">string lpDirectory, class="type">int nShowCmd); class="macro">#class="kw">import input class="type">class="kw">double balance_threshold = class="num">100000.0; class=class="str">"cmt">// Threshold balance for top-up input class="type">class="kw">double top_up_amount = class="num">100000.0; class=class="str">"cmt">// Amount to top-up if balance is below threshold input class="type">class="kw">string from_binary = "CR0000000"; class=class="str">"cmt">// Binary account ID to withdraw funds from. Replace zero with real one input class="type">class="kw">string to_mt5 = "MTRReplace"; class=class="str">"cmt">// MT5 account ID to deposit funds into. Replace with your MT5 acc from Deriv Broker input class="type">class="kw">string python_script_path = "C:\Users\YourComputerName\PathTo\deriv_api_handler.py"; class=class="str">"cmt">// Python script path input class="type">class="kw">string python_exe = "python"; class=class="str">"cmt">// Python executable command(ensure Python is in PATH) class="type">int OnInit() { class="type">class="kw">double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); Print("Current Account Balance: ", current_balance); if(current_balance < balance_threshold) { Print("Balance is below the threshold. Attempting a deposit..."); class="type">class="kw">string command_file = "mql5_to_python.txt"; class="type">class="kw">string command_path = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\Files\" + command_file; class="type">int handle = FileOpen(command_file, FILE_WRITE | FILE_COMMON | FILE_TXT | FILE_ANSI);
MT5 与 Python 的入金指令握手
EA 在判定余额低于阈值后,会把一笔入金请求写成 JSON 字符串丢进共享文件,再唤起本地 Python 脚本去执行跨账户调拨。下面这段就是实际落盘与回读响应的核心片段。 string deposit_command = StringFormat("{\"mt5_deposit\": 1, \"amount\": %.2f, \"from_binary\": \"%s\", \"req_id\": 1, \"to_mt5\": \"%s\"}", top_up_amount, from_binary, to_mt5); FileWrite(handle, deposit_command); FileClose(handle); Print("Deposit command written to file: ", command_path); 这一组先把格式化后的指令写进公共目录文件,路径由 command_path 指定,写毕立即关闭句柄,避免 Python 侧读锁。 int result = ShellExecuteW(0, "open", python_exe, python_script_path, NULL, 1); if(result <= 32) { Print("Failed to launch Python script. Error code: ", result); return(INIT_FAILED); } else { Print("Python script launched successfully."); } ShellExecuteW 返回值 ≤32 代表系统级拉起失败,常见为 2(文件缺失)或 31(无关联程序);此时直接 INIT_FAILED 终止初始化。 string response_file = "python_to_mql5.txt"; if(FileIsExist(response_file, FILE_COMMON)) {
| handle = FileOpen(response_file, FILE_READ | FILE_COMMON | FILE_TXT | FILE_ANSI); |
|---|
string response = FileReadString(handle); FileClose(handle); Print("Response from Python: ", response); if(StringFind(response, "\"status\":\"success\"") >= 0) { Print("Deposit was successful."); } else { Print("Deposit failed. Response: ", response); } } else { Print("Response file not found. Ensure the Python script is running and processing the command."); } 回读阶段只认 FILE_COMMON 下的 python_to_mql5.txt,用 StringFind 粗匹配 success 状态;若文件根本没生成,多半是 Python 侧没起来或没写完。 外汇与贵金属账户涉及跨平台资金调度,属高风险操作,任何自动入金逻辑都可能在网络或权限异常时失效,实盘前务必在模拟环境跑通文件握手。
class="type">class="kw">string deposit_command = StringFormat("{\"mt5_deposit\": class="num">1, \"amount\": %.2f, \"from_binary\": \"%s\", \"req_id\": class="num">1, \"to_mt5\": \"%s\"}", top_up_amount, from_binary, to_mt5); FileWrite(handle, deposit_command); FileClose(handle); Print("Deposit command written to file: ", command_path); class="type">int result = ShellExecuteW(class="num">0, "open", python_exe, python_script_path, NULL, class="num">1); if(result <= class="num">32) { Print("Failed to launch Python script. Error code: ", result); class="kw">return(INIT_FAILED); } else { Print("Python script launched successfully."); } class="type">class="kw">string response_file = "python_to_mql5.txt"; if(FileIsExist(response_file, FILE_COMMON)) { handle = FileOpen(response_file, FILE_READ | FILE_COMMON | FILE_TXT | FILE_ANSI); class="type">class="kw">string response = FileReadString(handle); FileClose(handle); Print("Response from Python: ", response); if(StringFind(response, "\"status\":\"success\"") >= class="num">0) { Print("Deposit was successful."); } else { Print("Deposit failed. Response: ", response); } } else { Print("Response file not found. Ensure the Python script is running and processing the command."); } else { Print("Balance is above the threshold. No deposit attempt needed."); } class="kw">return(INIT_SUCCEEDED); class="type">void OnDeinit(const class="type">int reason) { Print("Expert deinitialized."); }