使用 MetaTrader 5 Python 构建类似 MQL5 的交易类·进阶篇
📘

使用 MetaTrader 5 Python 构建类似 MQL5 的交易类·进阶篇

第 2/3 篇

◍ 用 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 路径带用户目录,硬编码极易失效。建议用环境变量或配置文件传入,否则一换机器脚本就跑不起来。

MQL5 / C++
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、类型描述、止损止盈一口气打印,外汇和贵金属品种波动大,历史订单的滑点痕迹都在这些字段里,复盘时大概率能揪出执行异常。

MQL5 / C++
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 输出的字段是否缺漏。

MQL5 / C++
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 只是瞬时值,别当成落袋数字。

MQL5 / C++
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 起的毫秒)。这种细颗粒数据,正是做交易成本归因的基础。

MQL5 / C++
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 再接信号逻辑。

MQL5 / C++
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()

常见问题

用类似 MQL5 的 COrderInfo 封装类,调用对应属性方法即可一次取出,不必反复查询终端。
用 CHistoryOrderInfo 类读取历史订单记录,可拆出成交价偏差与附加费用字段,回测时直接打印。
小布可接入账户与品种数据,自动汇总挂单、持仓与成交结构并推送异动,你只需打开对应页看结论。
用 CDealInfo 拆开每笔成交,区分真实成交与冲销记录,注意手续费和掉期是否已计入净值。
别硬编码,用环境读取类实时取点值、服务器时区与账户杠杆,避免跨品种切换时算错仓位。