使用 MetaTrader 5 Python 构建类似 MQL5 的交易类·进阶篇
◍ 用 COrderInfo 把挂单属性一次读透
做价格行为复盘时,挂单的到期类型、成交状态、SL/TP 挂价这些字段如果靠手动翻 MT5 终端,效率低且容易漏看。COrderInfo 这个类把订单属性按整数/日期、双精度、文本三类拆开,等于给每笔挂单做了结构化快照。 从实测输出看,一笔 Sell Limit 挂单(ticket 153201235)在 2025-05-21 23:56:16 下发,初始与未成交成交量都是 0.01 手,挂价 1.13594,当时 EURUSD 现价 1.1324,距离挂价还有约 35 点——这种价差关系直接决定了它是否被触发的概率。 下面这段 Python 封装调用,核心是先用 mt5.orders_get() 拉全部挂单,再循环用 select_order 绑定到 COrderInfo 实例上逐个读字段。注意 mt5.initialize 要填你本机终端路径,路径不对会直接 last_error 退出。 别把终端路径写死在脚本里 多人共用或换电脑时,Pepperstone 这类经纪商的 terminal64.exe 路径带用户目录,硬编码极易失效。建议用环境变量或配置文件传入,否则一换机器脚本就跑不起来。
class="kw">import MetaTrader5 as mt5 from Trade.OrderInfo class="kw">import COrderInfo if not mt5.initialize(r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe"): print("Failed to initialize Metatrader5 Error = ",mt5.last_error()) quit() # Get all orders from MT5 orders = mt5.orders_get() # Loop and print info m_order = COrderInfo() for i, order in enumerate(orders): if m_order.select_order(order=order): print(f""" Order #{i} --- Integer & class="type">class="kw">datetime type properties --- Ticket: {m_order.ticket()} Type Time: {m_order.type_time()} ({m_order.type_time_description()}) Time Setup: {m_order.time_setup()} Time Setup(ms): {m_order.time_setup_msc()} State: {m_order.state()} ({m_order.state_description()}) Order Type: {m_order.order_type()} ({m_order.order_type_description()}) Magic Number: {m_order.magic()} Position ID: {m_order.position_id()} Type Filling: {m_order.type_filling()} ({m_order.type_filling_description()}) Time Done: {m_order.time_done()} Time Done(ms): {m_order.time_done_msc()} Time Expiration: {m_order.time_expiration()} External ID: {m_order.external_id()} --- Double type properties --- Volume Initial: {m_order.volume_initial()} Volume Current: {m_order.volume_current()} Price Open: {m_order.price_open()} Price Current: {m_order.price_current()} Stop Loss: {m_order.stop_loss()} Take Profit: {m_order.take_profit()} Price StopLimit: {m_order.price_stop_limit()} --- Text type properties --- Comment: {m_order.comment()} Symbol: {m_order.symbol()} """) mt5.shutdown()
用 CHistoryOrderInfo 扒开历史订单的底
做价格行为复盘时,光看成交价不够,得把挂单时间、成交毫秒戳、魔法号和仓位 ID 全拉出来,才能还原当时 EA 或手动单的真实意图。CHistoryOrderInfo 这个类就是把 MetaTrader5.history_orders_get 返回的字典对象封装成可点名调用的属性集,整数、双精度、文本三类字段分开取,不用自己写解析。 时间类里 time_setup_msc 和 time_done_msc 返回的是自 1970-01-01 以来的毫秒数,比如实测一条卖停挂单 setup 毫秒戳 1747860977335,对应 UTC+0 的 2025-05-21 23:56:17,比秒级时间更方便算订单驻留时长。双精度里的 volume_initial 与 volume_current 差值,能直接看出部分成交或撤单残量。 下面这段是可直接抄去 MT5 终端 Python 环境跑的调用样例,初始化路径按你本机改一下即可。循环里 select_order 命中后,把订单的魔法号、ticket、类型描述、止损止盈一口气打印,外汇和贵金属品种波动大,历史订单的滑点痕迹都在这些字段里,复盘时大概率能揪出执行异常。
class="kw">import MetaTrader5 as mt5 from Trade.HistoryOrderInfo class="kw">import CHistoryOrderInfo from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta if not mt5.initialize(r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe"): print("Failed to initialize Metatrader5 Error = ",mt5.last_error()) quit() # The date range from_date = class="type">class="kw">datetime.now() - timedelta(hours=class="num">5) to_date = class="type">class="kw">datetime.now() # Get history orders history_orders = mt5.history_orders_get(from_date, to_date) if history_orders == None: print(f"No deals, error code={mt5.last_error()}") exit() # m_order instance m_order = CHistoryOrderInfo() # Loop and print each order for i, order in enumerate(history_orders): if m_order.select_order(order): print(f""" History Order #{i} --- Integer, Datetime & String type properties --- Time Setup: {m_order.time_setup()} Time Setup(ms): {m_order.time_setup_msc()} Time Done: {m_order.time_done()} Time Done(ms): {m_order.time_done_msc()} Magic Number: {m_order.magic()} Ticket: {m_order.ticket()} Order Type: {m_order.order_type()} ({m_order.type_description()}) Order State: {m_order.state()} ({m_order.state_description()}) Expiration Time: {m_order.time_expiration()} Filling Type: {m_order.type_filling()} ({m_order.type_filling_description()}) Time Type: {m_order.type_time()} ({m_order.type_time_description()}) Position ID: {m_order.position_id()} Position By ID: {m_order.position_by_id()} --- Double type properties --- Volume Initial: {m_order.volume_initial()} Volume Current: {m_order.volume_current()} Price Open: {m_order.price_open()} Price Current: {m_order.price_current()} Stop Loss: {m_order.stop_loss()} Take Profit: {m_order.take_profit()} Price Stop Limit: {m_order.price_stop_limit()} --- Access to text properties --- Symbol: {m_order.symbol()} Comment: {m_order.comment()} """) mt5.shutdown()
「从一笔挂单回执看清订单结构」
在 MT5 交易环境下,一笔挂单成交或挂出后会回写一组结构化字段,直接反映订单的生命周期与参数。上面这段回执来自 EURUSD 的 Sell Stop 挂单,Time Type 为 2,对应 ORDER_TIME_SPECIFIED,说明该单是在指定时间生效的挂单而非即时单。 Position ID 153201241 是该持仓在交易池中的唯一索引,Position By ID 为 0 表示它并非由另一笔持仓反向派生,而是一张独立挂单。Volume Initial 0.01 与 Volume Current 0.0 的落差,说明原始计划手数为 0.01,但当前已无未平量——可能是已撤或已完全平仓。 价格字段里 Price Open 与 Price Current 都是 1.13194,Stop Loss 和 Take Profit 均为 0.0,意味着这张单子没有挂止损止盈,属于裸方向单。外汇与贵金属杠杆品种中,无防护挂单在跳空时可能瞬时扩大浮亏,属高风险操作,实盘前应在策略测试器里复现该回执结构。 Comment 字段写明 Sell Stop Order,结合 Symbol EURUSD,可确认这是欧元兑美元下方突破卖单。复制这段回执到 MT5 的订单详情面板对照,能快速校验你自己 EA 输出的字段是否缺漏。
Time <span>Type</span>: <span class="number">class="num">2</span> (ORDER_TIME_SPECIFIED) Position ID: <span class="number">class="num">153201241</span> Position By ID: <span class="number">class="num">0</span> --- Double <span class="built_in">type</span> properties --- Volume Initial: <span class="number">class="num">0.01</span> Volume Current: <span class="number">class="num">0.0</span> Price Open: <span class="number">class="num">1.13194</span> Price Current: <span class="number">class="num">1.13194</span> Stop Loss: <span class="number">class="num">0.0</span> Take Profit: <span class="number">class="num">0.0</span> Price Stop Limit: <span class="number">class="num">0.0</span> --- Access to text properties --- Symbol: EURUSD Comment: Sell Stop Order
◍ 用 CPositionInfo 把持仓属性一次读透
做价格行为复盘时,最怕手工去 MT5 终端抄持仓数据。CPositionInfo 这个类把未平仓位的关键字段封装成方法,调一下就能拿到开仓时间、魔数、成交量、止损止盈和浮动盈亏,不用自己解析 positions_get() 返回的字典。 它把属性分成三类:整数和日期时间类(ticket、time、time_msc、magic、position_id 等),双精度类(volume、price_open、profit、swap 等),以及文本类(symbol、comment)。time_msc 给的是 1970-01-01 以来的毫秒数,time_update 则是秒数,做毫秒级延迟分析时别混用。 下面这段是直接跑得通的 Python 封装用法。先 mt5.initialize() 指定终端路径,positions_get() 拿全部持仓,再 select_position() 逐个绑定到 CPositionInfo 实例上读字段。 代码里的样例输出能看到真实结构:EURUSD 卖单 0.1 手,开仓价 1.12961,当前价 1.1296,浮盈 0.1,无止损止盈,ticket 153362497,time_msc 为 1747915326225(对应 2025-05-22 15:02:06)。外汇和贵金属杠杆高,持仓盈亏随点差和库存费跳动,读出来的 profit 只是瞬时值,别当成落袋数字。
ticket() time() time_msc() time_update() time_update_msc() position_type() position_type_description() magic() position_id() volume() price_open() stop_loss() take_profit() price_current() profit() swap() comment() symbol() select_position(self, position) -> class="type">bool class="kw">import MetaTrader5 as mt5 from Trade.PositionInfo class="kw">import CPositionInfo if not mt5.initialize(r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe"): print("Failed to initialize Metatrader5 Error = ",mt5.last_error()) quit() positions = mt5.positions_get() m_position = CPositionInfo() # Loop and print each position for i, position in enumerate(positions): if m_position.select_position(position): print(f""" Position #{i} --- Integer type properties --- Time Open: {m_position.time()} Time Open(ms): {m_position.time_msc()} Time Update: {m_position.time_update()} Time Update(ms): {m_position.time_update_msc()} Magic Number: {m_position.magic()} Ticket: {m_position.ticket()} Position Type: {m_position.position_type()} ({m_position.position_type_description()}) --- Double type properties --- Volume: {m_position.volume()} Price Open: {m_position.price_open()} Price Current: {m_position.price_current()} Stop Loss: {m_position.stop_loss()} Take Profit: {m_position.take_profit()} Profit: {m_position.profit()} Swap: {m_position.swap()} --- Access to text properties --- Symbol: {m_position.symbol()} Comment: {m_position.comment()} """) mt5.shutdown() Position #class="num">1 --- Integer type properties --- Time Open: class="num">2025-class="num">05-class="num">22 class="num">15:class="num">02:class="num">06 Time Open(ms): class="num">1747915326225 Time Update: class="num">2025-class="num">05-class="num">22 class="num">15:class="num">02:class="num">06 Time Update(ms): class="num">1747915326225 Magic Number: class="num">0 Ticket: class="num">153362497 Position Type: class="num">1 (Sell) --- Double type properties --- Volume: class="num">0.1 Price Open: class="num">1.12961 Price Current: class="num">1.1296 Stop Loss: class="num">0.0 Take Profit: class="num">0.0 Profit: class="num">0.1 Swap: class="num">0.0 --- Access to text properties --- Symbol: EURUSD Comment:
用 CDealInfo 把成交单拆开看
在 MT5 的 Python 环境里,CDealInfo 类把每一笔成交的属性按整数/日期时间、双精度、字符串三类暴露出来。整数类含 ticket、time、deal_type、entry、magic、position_id;双精度类含 volume、price、commission、swap、profit;字符串类则是 symbol 和 comment,足够还原一笔单子的来龙去脉。 选单方式有两种:select_by_index 按历史列表下标抓,select_deal 直接吃 history_deals_get 返回的字典对象,返回布尔值告诉你抓没抓到。实战里更常用后者,因为可以直接遍历接口给的 deals 列表。 下面这段脚本拉取最近 24 小时成交,逐笔打印。注意初始化时填的是你本机终端 exe 路径,路径不对 initialize 会返回 False。外汇与贵金属杠杆高,历史统计仅反映过去,不代表后续盈亏倾向。 代码跑出来的样例里,一笔 EURUSD 卖单:volume 0.01 手、price 1.13194、commission -0.04、swap 0.0、profit 0.0,time_msc 为 1747868267618(自 1970-01-01 起的毫秒)。这种细颗粒数据,正是做交易成本归因的基础。
class="kw">import MetaTrader5 as mt5 from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta from Trade.DealInfo class="kw">import CDealInfo # The date range from_date = class="type">class="kw">datetime.now() - timedelta(hours=class="num">24) to_date = class="type">class="kw">datetime.now() if not mt5.initialize(r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe"): print("Failed to initialize Metatrader5 Error = ",mt5.last_error()) quit() m_deal = CDealInfo() # Get all deals from MT5 history deals = mt5.history_deals_get(from_date, to_date) for i, deal in enumerate(deals): if (m_deal.select_deal(deal=deal)): print(f""" Deal #{i} --- integer and dateteime properties --- Ticket: {m_deal.ticket()} Time: {m_deal.time()} Time(ms): {m_deal.time_msc()} Deal Type: {m_deal.deal_type()} ({m_deal.type_description()}) Entry Type: {m_deal.entry()} ({m_deal.entry_description()}) Order: {m_deal.order()} Magic Number: {m_deal.magic()} Position ID: {m_deal.position_id()} --- class="type">class="kw">double type properties --- Volume: {m_deal.volume()} Price: {m_deal.price()} Commission: {m_deal.commission()} Swap: {m_deal.swap()} Profit: {m_deal.profit()} --- class="type">class="kw">string type properties --- Comment: {m_deal.comment()} Symbol: {m_deal.symbol()} External ID: {m_deal.external_id()} """) mt5.shutdown()
「读终端环境参数别靠猜」
做自动化策略前,先确认 MT5 客户端本身处在什么状态。CTerminalInfo 这个类把终端名称、公司、语言、数据路径、构建号、连接与权限开关全部暴露出来,省得你脚本跑一半才发现 DLL 被禁或者没连服务器。 字符串类属性里,name() 给终端名,company() 是券商实体,path() 指安装目录,data_path() 和 common_data_path() 分开存个人与公共数据,语言用 language() 读。整数类里 build() 返回构建版本,is_connected() 看服务器连线,is_trade_allowed() 和 is_dlls_allowed() 直接决定你的 EA 能不能下单或调外部库。 下面这段 Python 封装调用,初始化时写死 Pepperstone 的 terminal64.exe 路径,连不上就 quit()。实测回读的数据里,Build 4755、Max Bars 1 亿、Ping Last 251410 微秒、Community Balance 900.03、重传率 0.536,这些数每次启动都可能浮动,连不上经纪商时 ping 会明显变大。 外汇和贵金属杠杆高、滑点狠,终端权限没核对就跑实盘,可能瞬间触发异常平仓。先用这段代码把环境打印出来,确认 Trade Allowed 和 Connected 都是 True 再接信号逻辑。
class="kw">import MetaTrader5 as mt5 from Trade.TerminalInfo class="kw">import CTerminalInfo if not mt5.initialize(r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe"): print("Failed to initialize Metatrader5 Error = ",mt5.last_error()) quit() terminal = CTerminalInfo() print(f""" Terminal Information --- String type --- Name: {terminal.name()} Company: {terminal.company()} Language: {terminal.language()} Terminal Path: {terminal.path()} Data Path: {terminal.data_path()} Common Data Path: {terminal.common_data_path()} --- Integers type --- Build: {terminal.build()} Connected: {terminal.is_connected()} DLLs Allowed: {terminal.is_dlls_allowed()} Trade Allowed: {terminal.is_trade_allowed()} Email Enabled: {terminal.is_email_enabled()} FTP Enabled: {terminal.is_ftp_enabled()} Notifications Enabled: {terminal.are_notifications_enabled()} Community Account: {terminal.is_community_account()} Community Connected: {terminal.is_community_connection()} MQID: {terminal.is_mqid()} Trade API Disabled: {terminal.is_tradeapi_disabled()} Max Bars: {terminal.max_bars()} Code Page: {terminal.code_page()} Ping Last(μs): {terminal.ping_last()} Community Balance: {terminal.community_balance()} Retransmission Rate: {terminal.retransmission()} """) mt5.shutdown()