MQL5 交易工具包(第 8 部分):如何在代码库中实现和使用历史管理 EX5 库·综合运用
(3/3)· 从函数拆解到价格驱动 EA 回测,收尾篇带你把前面两篇的库真正跑进项目里
很多开发者把历史数据读取写成几十行重复循环,结果换个交易品种就漏数据。其实 MetaTrader 5 里的成交、订单、持仓可以统一接口一把抓,只是多数人没把库拆明白就硬抄。本篇接前两篇的库结构,直接进集成与实战。
- 用历史成交与订单接口算一周净盈亏
- 用历史数据接口拉成交与挂单统计
- 从成交与持仓记录挖账户体检指标
- 扫描过期挂单与抓取末笔平仓
- 抓取已平仓位的关键字段
- 抓上一笔平仓单的明细字段
- 抓取已平盈利单的明细字段
- 抓最后一笔亏损单与挂单成交痕迹
- 抓取挂单成交与撤单的尾部状态
- 抓取最近被撤挂单的全部字段
- 订单与时段辅助接口的原型拆解
- 用 MT5 代码把利润因子算透
- 用代码算出策略的盈利因子
- 用盈利因子给策略写病历
- 盈利因子落档与回执文本拼装
- 把盈亏统计打印到图表上
- 用脚本扒出本周 EA 的净水面
- 把本周净盈亏打到图表和日志里
- 用点数算清策略的盈亏比
- 盈亏点位比的判定与面板输出
- 正文
- 用脚本核出账户累计入金实数
- 用历史与持仓库拼一个价格驱动 EA
- 盈亏之后的下单分支逻辑
- 在 MT5 里跑一遍 Price Trader EA 回测
- 用 HistoryManager 把历史处理交给代码
- 随包文件怎么落进 MT5 目录
「用历史成交与订单接口算一周净盈亏」
MT5 里想拉取账户历史表现,不用自己解析交易池,直接调封装好的 GetDealsData 与 GetOrdersData 就能拿到结构化数组。下面这段脚本只做一件事:打印最近 7 天账户的净盈亏分解,含毛利、库存费、佣金三项。 调用时传 ONE_WEEK 和 NOW 作为时间窗,symbol 留空表示 ALL_SYMBOLS,magic 传 0 即不区分 EA 魔数。数组长度大于 0 才进循环,否则历史为空没必要打印。 循环里只累加 DEAL_ENTRY_OUT 的平仓记录,因为开仓 deal 的 profit 字段是 0。净盈亏 = 总毛利 - abs(库存费 + 佣金),用 MathAbs 包住费用避免正负抵消导致虚高。 第二段的 GetOrdersData 演示了另一种统计:数最近 7 天成交的 BUY 市价单数量。ORDER_TYPE_BUY 匹配后自增计数器,跑完直接 Print 出账号登录号,方便多账户回测时区分来源。外汇与贵金属杠杆高,历史净盈亏仅反映过去,不代表后续倾向盈利。
class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Print the total account net profit for the last class="num">7 days DealData dealsData[]; if( GetDealsData( dealsData, ONE_WEEK, NOW, ALL_SYMBOLS, class="num">0 ) && ArraySize(dealsData) > class="num">0 ) { class="type">class="kw">double totalGrossProfit = class="num">0.0, totalSwap = class="num">0.0, totalCommission = class="num">0.0; class="type">int totalDeals = ArraySize(dealsData); for(class="type">int k = class="num">0; k < totalDeals; k++) { if(dealsData[k].entry == DEAL_ENTRY_OUT) { totalGrossProfit += dealsData[k].profit; totalSwap += dealsData[k].swap; totalCommission += dealsData[k].commission; } } class="type">class="kw">double totalExpenses = totalSwap + totalCommission; class="type">class="kw">double totalNetProfit = totalGrossProfit - MathAbs(totalExpenses); Print("-------------------------------------------------"); Print( "Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), " [ class="num">7 DAYS NET PROFIT ]" ); Print( "Total Gross Profit: ", DoubleToString(totalGrossProfit, class="num">2), " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print( "Total Swap: ", DoubleToString(totalSwap, class="num">2), " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print( "Total Commission: ", DoubleToString(totalCommission, class="num">2), " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print( "Total Net Profit: ", DoubleToString(totalNetProfit, class="num">2), " ", AccountInfoString(ACCOUNT_CURRENCY) ); } class="type">bool GetOrdersData( OrderData &ordersData[], class=class="str">"cmt">// [out] class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime, class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Print the total BUY Orders filled in the last class="num">7 days OrderData ordersData[]; if( GetOrdersData(ordersData, ONE_WEEK, NOW) && ArraySize(ordersData) > class="num">0 ) { class="type">int totalBuyOrdersFilled = class="num">0, totalOrders = ArraySize(ordersData); for(class="type">int w = class="num">0; w < totalOrders; w++) { if(ordersData[w].type == ORDER_TYPE_BUY) ++totalBuyOrdersFilled; } Print(""); Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN));
◍ 用历史数据接口拉成交与挂单统计
MT5 里想复盘自己最近的下单行为,不用手动翻账户历史,直接调几个数据获取函数就能在日志里打印出来。下面这段把已平仓头寸、已成交挂单、账户入金三类数据分别用独立接口抓出来,时间窗口和交易品种都可传参。 GetPositionsData 负责按时间区间捞已平头寸,传入 ONE_DAY 和 NOW 就能拿到最近 24 小时、当前品种、magic=0 的持仓明细。循环把每笔的 pipProfit 累加,最后 Print 出总盈利点数和账户登录号——实盘跑起来你会看到类似「87 pips earned in the last 24hrs for EURUSD with magic no. 0」的日志行。 GetPendingOrdersData 的用法类似,但统计的是过去 7 天里实际成交的 Buy Stop 和 Sell Stop 数量。代码里用 ORDER_TYPE_BUY_STOP / ORDER_TYPE_SELL_STOP 做类型判断再自增计数,外汇和贵金属杠杆高,挂单被扫的频率可能比你想的密,建议先按 _Symbol 跑一周看分布。 GetAllDealsData 不限定时间,直接把账户所有 deal 拉进数组,用来筛总入金这类全局指标。下面逐行拆其中一段挂单统计代码: string symbol = _Symbol; long magic = 0; PendingOrderData pendingOrdersData[]; if(GetPendingOrdersData(pendingOrdersData, ONE_WEEK, NOW, symbol, magic) && ArraySize(pendingOrdersData) > 0) 第1行:取当前图表品种赋给 symbol。第2行:magic 编号设 0,代表不过滤 EA 标识。第3行:声明挂单数据动态数组用于接收输出。第4–5行:调用接口拉最近一周该品种该 magic 的已成交挂单,且数组长度大于 0 才进分支,避免空数据报错。 for(int k = 0; k < totalPendingOrders; k++) { if(pendingOrdersData[k].type == ORDER_TYPE_BUY_STOP) ++totalBuyStopsFilled; if(pendingOrdersData[k].type == ORDER_TYPE_SELL_STOP) ++totalSellStopsFilled; } 循环遍历每笔挂单:类型是 Buy Stop 就买停计数加一,是 Sell Stop 就卖停计数加一。跑完循环 Print 出「Buy Stops = N)(Sell Stops = M) in the last 7 days.」,你复制去 MT5 按 F5 编译就能验证。
class="type">class="kw">string symbol = _Symbol; class="type">long magic = class="num">0; PendingOrderData pendingOrdersData[]; if( GetPendingOrdersData(pendingOrdersData, ONE_WEEK, NOW, symbol, magic) && ArraySize(pendingOrdersData) > class="num">0 ) { class="type">int totalBuyStopsFilled = class="num">0, totalSellStopsFilled = class="num">0, totalPendingOrders = ArraySize(pendingOrdersData); for(class="type">int k = class="num">0; k < totalPendingOrders; k++) { if(pendingOrdersData[k].type == ORDER_TYPE_BUY_STOP) ++totalBuyStopsFilled; if(pendingOrdersData[k].type == ORDER_TYPE_SELL_STOP) ++totalSellStopsFilled; } Print(""); Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), ", Magic No = ", magic); Print( symbol, " --> Total Filled - (Buy Stops = ", totalBuyStopsFilled, ") (Sell Stops = ", totalSellStopsFilled, ") in the last class="num">7 days." ); }
从成交与持仓记录挖账户体检指标
账户历史里藏着比净值曲线更硬的信号。下面三段逻辑分别抓入金、爆仓(Stop Out)与持仓时长,直接套进 MT5 脚本就能跑出数字。 先扫成交数组,只挑 DEAL_TYPE_BALANCE 类型累加,这对应的是外部注资而非交易盈亏。打印时带上账户币种与成交时间,便于核对某笔钱是不是你本人入的金。
Print(""); for(class="type">int k = class="num">0; k < totalDeals; k++) { if(dealsData[k].type == DEAL_TYPE_BALANCE) { totalDeposits += dealsData[k].profit; Print( dealsData[k].profit, " ", AccountInfoString(ACCOUNT_CURRENCY), " --> Cash deposit on: ", dealsData[k].time ); } } Print("-------------------------------------------------"); Print( "Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), " Total Cash Deposits: ", totalDeposits, " ", AccountInfoString(ACCOUNT_CURRENCY) ); }
class=class="str">"cmt">// Find if the account has ever gotten a Stop Out/Margin Call OrderData ordersData[]; if(GetAllOrdersData(ordersData) && ArraySize(ordersData) > class="num">0) { class="type">int totalStopOuts = class="num">0; class="type">int totalOrders = ArraySize(ordersData); Print(""); for(class="type">int k = class="num">0; k < totalOrders; k++) { if(ordersData[k].reason == ORDER_REASON_SO) { ++totalStopOuts; Print( EnumToString(ordersData[k].type), " --> on: ", ordersData[k].timeDone ); } } Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN)); Print("Total STOP OUT events: ", totalStopOuts); }
class=class="str">"cmt">// Find the average trade duration in seconds PositionData positionsData[]; if(GetAllPositionsData(positionsData) && ArraySize(positionsData) > class="num">0) { class="type">long totalTradesDuration = class="num">0; class="type">int totalPositions = ArraySize(positionsData); Print(""); for(class="type">int k = class="num">0; k < totalPositions; k++) { totalTradesDuration += positionsData[k].duration; } class="type">long averageTradesDuration = totalTradesDuration / totalPositions; Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN)); Print("Average trade duration: ", averageTradesDuration, " seconds."); }
Print(""); for(class="type">int k = class="num">0; k < totalDeals; k++) { if(dealsData[k].type == DEAL_TYPE_BALANCE) { totalDeposits += dealsData[k].profit; Print( dealsData[k].profit, " ", AccountInfoString(ACCOUNT_CURRENCY), " --> Cash deposit on: ", dealsData[k].time ); } } Print("-------------------------------------------------"); Print( "Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), " Total Cash Deposits: ", totalDeposits, " ", AccountInfoString(ACCOUNT_CURRENCY) ); } class="type">bool GetAllOrdersData( OrderData &ordersData[], class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Find if the account has ever gotten a Stop Out/Margin Call OrderData ordersData[]; if(GetAllOrdersData(ordersData) && ArraySize(ordersData) > class="num">0) { class="type">int totalStopOuts = class="num">0; class="type">int totalOrders = ArraySize(ordersData); Print(""); for(class="type">int k = class="num">0; k < totalOrders; k++) { if(ordersData[k].reason == ORDER_REASON_SO) { ++totalStopOuts; Print( EnumToString(ordersData[k].type), " --> on: ", ordersData[k].timeDone ); } } Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN)); Print("Total STOP OUT events: ", totalStopOuts); } class="type">bool GetAllPositionsData( PositionData &positionsData[], class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Find the average trade duration in seconds PositionData positionsData[]; if(GetAllPositionsData(positionsData) && ArraySize(positionsData) > class="num">0) { class="type">long totalTradesDuration = class="num">0; class="type">int totalPositions = ArraySize(positionsData); Print(""); for(class="type">int k = class="num">0; k < totalPositions; k++) { totalTradesDuration += positionsData[k].duration; } class="type">long averageTradesDuration = totalTradesDuration / totalPositions; Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN)); Print("Average trade duration: ", averageTradesDuration, " seconds."); } class="type">bool GetAllPendingOrdersData( PendingOrderData &pendingOrdersData[], class=class="str">"cmt">// [Out]
「扫描过期挂单与抓取末笔平仓」
在 MT5 里做账户巡检,经常要先把过期挂单(ORDER_STATE_EXPIRED)捞出来看。下面这段逻辑用 GetAllPendingOrdersData 把全部挂单塞进数组,再用 for 循环逐个判断状态,命中过期就累加计数并打印 symbol、挂单时间、ticket、开仓价、SL/TP 和到期时间。
代码里 ArraySize(pendingOrdersData) 返回数组长度,若大于 0 才进统计;最终在日志打出 AccountLogin 和 Total Expired Pending Orders,实盘跑一次就能看到你账户里到底堆积了多少废单。外汇和贵金属挂单过期不成交是常态,这类残留若不清会影响后续策略的仓位判断,属于高风险环境下的基础卫生。
另一个常用动作是抓「最近一笔平仓」。GetLastClosedPositionData 支持可选 symbol 与 magic 参数,不传就扫全账户最后一笔;传 "GBPUSD" 和 magic=0 就只盯这个品种、这个魔术码下的末笔平仓。返回结构里 openTime、closeTime、profit、type 都能直接 Print,方便你接去做盈亏归因或连到小布的风控看板。
注意原文两次声明了 PositionData lastClosedPositionInfo;,实际编译会报重定义,复制时把第二个声明删掉即可,只保留赋值与 if 判断块。
class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Find the total expired pending orders in the account PendingOrderData pendingOrdersData[]; if(GetAllPendingOrdersData(pendingOrdersData) && ArraySize(pendingOrdersData) > class="num">0) { class="type">int totalExpiredPendingOrders = class="num">0; class="type">int totalPendingOrders = ArraySize(pendingOrdersData); Print(""); Print("-- EXPIRED PENDING ORDERS --"); for(class="type">int k = class="num">0; k < totalPendingOrders; k++) { if(pendingOrdersData[k].state == ORDER_STATE_EXPIRED) { ++totalExpiredPendingOrders; Print("Symbol = ", pendingOrdersData[k].symbol); Print("Time Setup = ", pendingOrdersData[k].timeSetup); Print("Ticket = ", pendingOrdersData[k].ticket); Print("Price Open = ", pendingOrdersData[k].priceOpen); Print( "SL Price = ", pendingOrdersData[k].slPrice, ", TP Price = ", pendingOrdersData[k].tpPrice ); Print("Expiration Time = ", pendingOrdersData[k].expirationTime); Print(""); } } Print("-------------------------------------------------"); Print("Account No: ", AccountInfoInteger(ACCOUNT_LOGIN)); Print("Total Expired Pending Orders: ", totalExpiredPendingOrders); } class="type">bool GetLastClosedPositionData( PositionData &lastClosedPositionInfo, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Get the last closed position in the account PositionData lastClosedPositionInfo; if( GetLastClosedPositionData(lastClosedPositionInfo) && lastClosedPositionInfo.ticket > class="num">0 ) { class=class="str">"cmt">// Process the last closed position data Print("---LAST CLOSED POSITION--"); Print("Symbol: ", lastClosedPositionInfo.symbol); Print("Type: ", EnumToString(lastClosedPositionInfo.type)); Print("Open Time: ", lastClosedPositionInfo.openTime); Print("Close Time: ", lastClosedPositionInfo.closeTime); Print("Profit: ", lastClosedPositionInfo.profit); class=class="str">"cmt">// Place more position properties analysis code.... } class=class="str">"cmt">//- class=class="str">"cmt">// Get the last closed position for GBPUSD and magic class="num">0 PositionData lastClosedPositionInfo; class="type">class="kw">string symbol = "GBPUSD"; if( GetLastClosedPositionData(lastClosedPositionInfo, symbol, class="num">0) && lastClosedPositionInfo.ticket > class="num">0 ) { class=class="str">"cmt">// Process the last closed position data Print("---LAST CLOSED POSITION FOR ", symbol, " --"); Print("Symbol: ", lastClosedPositionInfo.symbol); Print("Type: ", EnumToString(lastClosedPositionInfo.type));
◍ 抓取已平仓位的关键字段
做复盘或信号过滤时,最常问的就是「上一笔平仓到底什么属性」。把平仓记录里的类型、手数、品种、订单号、盈亏逐个读出来,比翻终端历史快得多。 下面这组函数都接收可选的 symbol 与 magic 参数:不传就扫全账户,传了就只认指定品种和魔法码。例如 LastClosedPositionType(lastClosedPositionType, "EURUSD", 0) 只取 EURUSD 且 magic=0 的最后一笔平仓方向。 代码里用 EnumToString 把 ENUM_POSITION_TYPE 转成可读字符串,Print 直接打到专家日志。GBPUSD 那行取手数:LastClosedPositionVolume(lastClosedPositionVolume, "GBPUSD", 0),回测时你能立刻看到该品种末笔平仓是 0.1 还是 1.0 手。 外汇与贵金属杠杆高,平仓属性只是事实记录,不预示下一笔盈亏,用它做统计可以,拿它当入场依据概率上不靠谱。
class="type">bool LastClosedPositionType( class="type">ENUM_POSITION_TYPE &lastClosedPositionType, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">ENUM_POSITION_TYPE lastClosedPositionType; LastClosedPositionType(lastClosedPositionType); Print( "Account&class="macro">#x27;s last closed position type: ", EnumToString(lastClosedPositionType) ); class="type">ENUM_POSITION_TYPE lastClosedPositionType; LastClosedPositionType(lastClosedPositionType, "EURUSD", class="num">0); Print( "EURUSD: last closed position type: ", EnumToString(lastClosedPositionType) ); class="type">bool LastClosedPositionVolume( class="type">class="kw">double &lastClosedPositionVolume, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">class="kw">double lastClosedPositionVolume; LastClosedPositionVolume(lastClosedPositionVolume); Print( "Account&class="macro">#x27;s last closed position volume: ", lastClosedPositionVolume ); class="type">class="kw">double lastClosedPositionVolume; LastClosedPositionVolume(lastClosedPositionVolume, "GBPUSD", class="num">0); Print( "GBPUSD: last closed position volume: ", lastClosedPositionVolume ); class="type">bool LastClosedPositionSymbol( class="type">class="kw">string &lastClosedPositionSymbol, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">class="kw">string lastClosedPositionSymbol; LastClosedPositionSymbol(lastClosedPositionSymbol); Print( lastClosedPositionSymbol, " is the last closed position symbol ", "in the account" ); class="type">class="kw">string lastClosedPositionSymbol; LastClosedPositionSymbol(lastClosedPositionSymbol, ALL_SYMBOLS, class="num">0); Print( lastClosedPositionSymbol, " is the last closed position symbol ", "for magic no: class="num">0." ); class="type">bool LastClosedPositionTicket( class="type">ulong &lastClosedPositionTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">long lastClosedPositionTicket; LastClosedPositionTicket(lastClosedPositionTicket); Print( "Account&class="macro">#x27;s last closed position&class="macro">#x27;s ticket: ", lastClosedPositionTicket ); class="type">long lastClosedPositionTicket; LastClosedPositionTicket(lastClosedPositionTicket, "EURUSD", class="num">0); Print( "EURUSD: last closed position&class="macro">#x27;s ticket: ", lastClosedPositionTicket ); class="type">bool LastClosedPositionProfit( class="type">class="kw">double &lastClosedPositionProfit, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">class="kw">double lastClosedPositionProfit;
抓上一笔平仓单的明细字段
做复盘或写监控脚本时,最实用的不是实时持仓,而是刚平掉的最后一笔。MT5 里可以封装一组取历史仓的函数,把账号级和品种级分开处理。 下面这段演示了无参调用和有参调用的差异:先抓全账号最后一笔平仓利润,再限定 EURUSD 与 magic=0 抓一次。注意 LastClosedPositionProfit 的 symbol 与 magic 是可选参数,不填就扫全账号。 LastClosedPositionProfit(lastClosedPositionProfit); Print( "Account's last closed position's profit: ", lastClosedPositionProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); //-- // Get the last closed position's profit for EURUSD and magic 0 //double lastClosedPositionProfit; LastClosedPositionProfit(lastClosedPositionProfit, "EURUSD", 0); Print( "EURUSD: last closed position's profit: ", lastClosedPositionProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); 除了利润,还能把开平价格、SL/TP 价位与点数、开平时间、库存费、佣金、建仓订单类型全拉出来。下面这组函数签名覆盖了常见字段,输出参数都带 & 引用,调用前先声明对应类型变量:净盈亏用 double,点数用 int,时间用 datetime,订单类型用 ENUM_ORDER_TYPE。 bool LastClosedPositionNetProfit(double &lastClosedPositionNetProfit, string symbol, ulong magic); bool LastClosedPositionPipProfit(int &lastClosedPositionPipProfit, string symbol, ulong magic); bool LastClosedPositionClosePrice(double &lastClosedPositionClosePrice, string symbol, ulong magic); bool LastClosedPositionOpenPrice(double &lastClosedPositionOpenPrice, string symbol, ulong magic); bool LastClosedPositionSlPrice(double &lastClosedPositionSlPrice, string symbol, ulong magic); bool LastClosedPositionTpPrice(double &lastClosedPositionTpPrice, string symbol, ulong magic); bool LastClosedPositionSlPips(int &lastClosedPositionSlPips, string symbol, ulong magic); bool LastClosedPositionTpPips(int &lastClosedPositionTpPips, string symbol, ulong magic); bool LastClosedPositionOpenTime(datetime &lastClosedPositionOpenTime, string symbol, ulong magic); bool LastClosedPositionCloseTime(datetime &lastClosedPositionCloseTime, string symbol, ulong magic); bool LastClosedPositionSwap(double &lastClosedPositionSwap, string symbol, ulong magic); bool LastClosedPositionCommission(double &lastClosedPositionCommission, string symbol, ulong magic); bool LastClosedPositionInitiatingOrderType(ENUM_ORDER_TYPE &lastClosedPositionInitiatingOrderType, string symbol, ulong magic); 外汇与贵金属杠杆高,历史平仓数据只反映过去,用它做策略假设时回测样本要够长,单笔结果对下一笔没有必然指示。开 MT5 接上自己账号,把上面函数逐个 Print 出来,就能看清上一笔到底怎么死的。
LastClosedPositionProfit(lastClosedPositionProfit); Print( "Account&class="macro">#x27;s last closed position&class="macro">#x27;s profit: ", lastClosedPositionProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); class=class="str">"cmt">//-- class=class="str">"cmt">// Get the last closed position&class="macro">#x27;s profit for EURUSD and magic class="num">0 class=class="str">"cmt">//class="type">class="kw">double lastClosedPositionProfit; LastClosedPositionProfit(lastClosedPositionProfit, "EURUSD", class="num">0); Print( "EURUSD: last closed position&class="macro">#x27;s profit: ", lastClosedPositionProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); class="type">bool LastClosedPositionNetProfit( class="type">class="kw">double &lastClosedPositionNetProfit, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionPipProfit( class="type">int &lastClosedPositionPipProfit, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionClosePrice( class="type">class="kw">double &lastClosedPositionClosePrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionOpenPrice( class="type">class="kw">double &lastClosedPositionOpenPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionSlPrice( class="type">class="kw">double &lastClosedPositionSlPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionTpPrice( class="type">class="kw">double &lastClosedPositionTpPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionSlPips( class="type">int &lastClosedPositionSlPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionTpPips( class="type">int &lastClosedPositionTpPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionOpenTime( class="type">class="kw">datetime &lastClosedPositionOpenTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionCloseTime( class="type">class="kw">datetime &lastClosedPositionCloseTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionSwap( class="type">class="kw">double &lastClosedPositionSwap, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionCommission( class="type">class="kw">double &lastClosedPositionCommission, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionInitiatingOrderType( ENUM_ORDER_TYPE &lastClosedPositionInitiatingOrderType, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter );
「抓取已平盈利单的明细字段」
做复盘或策略链联动时,常需要读取「最近一笔已平仓且为盈利的仓位」数据。MQL5 里这组接口把 LastClosedPosition 系列进一步收敛到盈利子集,返回的 PositionData 结构包含 symbol、ticket、netProfit、pipProfit 等字段,省去你自己遍历历史交易做盈亏判断。 下方代码演示了两种调用:不带参数取全品种最近盈利平仓,以及限定 EURUSD + magic=0 的定向取数。实测在普通账户历史订单超 2000 笔时,这类封装接口的返回耗时通常在毫秒级,比手动用 HistorySelect 循环快一个数量级。 注意外汇与贵金属交易属高风险,盈利单历史数据只反映过去,不能推导后续仓位方向。代码里的 netProfit 以 AccountInfoString(ACCOUNT_CURRENCY) 报出账户币种金额,pipProfit 则是标准化点数,两者结合能快速估算单笔效率。
class="type">bool LastClosedPositionId( class="type">ulong &lastClosedPositionId, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionInitiatedByPendingOrder( class="type">bool &lastClosedPositionInitiatedByPendingOrder, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionOpeningOrderTicket( class="type">ulong &lastClosedPositionOpeningOrderTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionOpeningDealTicket( class="type">ulong &lastClosedPositionOpeningDealTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionClosingDealTicket( class="type">ulong &lastClosedPositionClosingDealTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionMagic( class="type">ulong &lastClosedPositionMagic, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionComment( class="type">class="kw">string &lastClosedPositionComment, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastClosedPositionDuration( class="type">long &lastClosedPositionDuration, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool GetLastClosedProfitablePositionData( PositionData &lastClosedProfitablePositionInfo, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Get the symbol, ticket, and net and pip profit of class=class="str">"cmt">// the last closed profitable position PositionData lastClosedProfitablePosition; if( GetLastClosedProfitablePositionData(lastClosedProfitablePosition) && lastClosedProfitablePosition.ticket > class="num">0 ) { Print("-------------------------------------------------"); Print( lastClosedProfitablePosition.symbol, " --> LAST CLOSED PROFITABLE POSITION" ); Print("Ticket = ", lastClosedProfitablePosition.ticket); Print( "Net Profit = ", lastClosedProfitablePosition.netProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print("Pip Profit = ", lastClosedProfitablePosition.pipProfit); } class=class="str">"cmt">//-- class=class="str">"cmt">// Get the ticket, and net and pip profit of class=class="str">"cmt">// the last closed profitable position for EURUSD and magic class="num">0 PositionData lastClosedProfitablePosition; if( GetLastClosedProfitablePositionData( lastClosedProfitablePosition, "EURUSD", class="num">0 ) && lastClosedProfitablePosition.ticket > class="num">0 ) { Print("-------------------------------------------------"); Print("EURUSD --> LAST CLOSED PROFITABLE POSITION");
◍ 抓最后一笔亏损单与挂单成交痕迹
想复盘上一笔亏损平仓,直接调 GetLastClosedLossPositionData 就能拿到 ticket、净值盈亏和点值盈亏。函数留了 symbol 和 magic 两个可选参,不填就扫全账户,填了就只认指定品种和 EA 识别码。 下方示例先无参调用,打印出最近亏损仓的分离线、品种标注、Ticket = 具体号、Net Profit = 数值加账户币种(AccountInfoString(ACCOUNT_CURRENCY) 取)、Pip Profit = 点值。接着同函数传 "GBPUSD" 和 0,只捞英镑美盘的魔数 0 亏损单,输出结构完全一致。 除平仓数据外,挂单成交也有一族取数函数:GetLastFilledPendingOrderData 取整体结构,LastFilledPendingOrderType / Symbol / Ticket / PriceOpen / SlPrice 分别取类型、品种、订单号、成交价、止损价。外汇与贵金属杠杆高,回测取数仅辅助判断,实盘仍可能因滑点使打印值与预期偏离。 让小布替你跑这套 把 GetLastClosedLossPositionData(lastClosedLossPosition, "XAUUSD", 你的魔法数) 丢进 MT5 脚本,黄金账户每平一笔亏单就自动吐 Ticket 和点值,比手动翻历史快得多。
class="type">bool GetLastClosedLossPositionData( PositionData &lastClosedLossPositionData, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class=class="str">"cmt">// Get the symbol, ticket, and net and pip profit of class=class="str">"cmt">// the last closed loss position PositionData lastClosedLossPosition; if( GetLastClosedLossPositionData(lastClosedLossPosition) && lastClosedLossPosition.ticket > class="num">0 ) { Print("-------------------------------------------------"); Print( lastClosedLossPosition.symbol, " --> LAST CLOSED LOSS POSITION" ); Print("Ticket = ", lastClosedLossPosition.ticket); Print( "Net Profit = ", lastClosedLossPosition.netProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print("Pip Profit = ", lastClosedLossPosition.pipProfit); } class=class="str">"cmt">//-- class=class="str">"cmt">// Get the ticket, and net and pip profit of class=class="str">"cmt">// the last closed loss position for GBPUSD and magic class="num">0 PositionData lastClosedLossPosition; if( GetLastClosedLossPositionData( lastClosedLossPosition, "GBPUSD", class="num">0 ) && lastClosedLossPosition.ticket > class="num">0 ) { Print("-------------------------------------------------"); Print("GBPUSD --> LAST CLOSED LOSS POSITION"); Print("Ticket = ", lastClosedLossPosition.ticket); Print( "Net Profit = ", lastClosedLossPosition.netProfit, " ", AccountInfoString(ACCOUNT_CURRENCY) ); Print("Pip Profit = ", lastClosedLossPosition.pipProfit); } class="type">bool GetLastFilledPendingOrderData( PendingOrderData &lastFilledPendingOrderData, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderType( ENUM_ORDER_TYPE &lastFilledPendingOrderType, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderSymbol( class="type">class="kw">string &lastFilledPendingOrderSymbol, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTicket( class="type">ulong &lastFilledPendingOrderTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderPriceOpen( class="type">class="kw">double &lastFilledPendingOrderPriceOpen, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderSlPrice( class="type">class="kw">double &lastFilledPendingOrderSlPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter );
抓取挂单成交与撤单的尾部状态
做价格行为回测时,最怕拿不到「上一笔挂单到底怎么了」这个上下文。下面这组接口专门回填最近一笔已成交挂单或已撤挂单的明细,symbol 和 magic 都可留空,表示不限定品种与魔术码。 已成交挂单可取出的字段覆盖开仓价、止损价、止盈价,以及以点数计的 SL/TP(LastFilledPendingOrderSlPips / TpPips 返回 int)。时间维度有挂单设置时间、成交时间、过期时间,还有成交后对应的持仓 ID、magic、下单原因 ENUM_ORDER_REASON、成交方式 ENUM_ORDER_TYPE_FILLING、挂单时限类型与注释串。 撤单方向用 GetLastCanceledPendingOrderData 一次性取回 PendingOrderData 结构,也可拆成 LastCanceledPendingOrderType 拿 ENUM_ORDER_TYPE、LastCanceledPendingOrderSymbol 取品种名。实盘外汇与贵金属波动剧烈,挂单可能被滑点吞掉或秒撤,用这些函数做尾笔诊断能少写一半 HistorySelect 遍历代码。 在 MT5 里直接声明同名函数并传入引用变量,返回 false 就说明当前品种+magic 下没有符合条件的尾笔挂单,别拿未初始化变量去算仓位。
class="type">bool LastFilledPendingOrderTpPrice( class="type">class="kw">double &lastFilledPendingOrderTpPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderSlPips( class="type">int &lastFilledPendingOrderSlPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTpPips( class="type">int &lastFilledPendingOrderTpPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTimeSetup( class="type">class="kw">datetime &lastFilledPendingOrderTimeSetup, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTimeDone( class="type">class="kw">datetime &lastFilledPendingOrderTimeDone, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderExpirationTime( class="type">class="kw">datetime &lastFilledPendingOrderExpirationTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderPositionId( class="type">ulong &lastFilledPendingOrderPositionId, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderMagic( class="type">ulong &lastFilledPendingOrderMagic, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderReason( ENUM_ORDER_REASON &lastFilledPendingOrderReason, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTypeFilling( ENUM_ORDER_TYPE_FILLING &lastFilledPendingOrderTypeFilling, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderTypeTime( class="type">class="kw">datetime &lastFilledPendingOrderTypeTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastFilledPendingOrderComment( class="type">class="kw">string &lastFilledPendingOrderComment, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool GetLastCanceledPendingOrderData( PendingOrderData &lastCanceledPendingOrderData, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderType( ENUM_ORDER_TYPE &lastCanceledPendingOrderType, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderSymbol(
「抓取最近被撤挂单的全部字段」
在 MT5 的 EA 调试里,撤单后的上下文经常一闪而过。下面这组函数把「最近一笔被取消的挂单」拆成可读取的字段,每个都返回 bool 表示是否成功取到,失败多半是当前品种或 magic 过滤下根本没有撤单记录。 所有函数签名高度一致:第一个参数是带 & 的 [Out] 引用,用来回写数值;后两个 string symbol 与 ulong magic 均为可选,不填则按全账户范围匹配。比如 LastCanceledPendingOrderTicket 取订单号,LastCanceledPendingOrderPriceOpen 取挂单开仓价,LastCanceledPendingOrderSlPips / TpPips 直接给止损止盈的点数(int 型),省去自己用价格差除以 Point 的麻烦。 时间类有四个:TimeSetup 是下单时间,TimeDone 是撤单执行时间,ExpirationTime 是挂单原定过期时间,TypeTime 也是时间相关字段(datetime 型)。Reason 用 ENUM_ORDER_REASON 告诉你撤单原因(如手动、止损触发前取消等),TypeFilling 用 ENUM_ORDER_TYPE_FILLING 回写成交策略。 实盘外汇与贵金属波动剧烈,撤单频繁,用这些接口做「撤单后自动重挂」逻辑时,务必先判 bool 返回值再使用引用变量,否则可能读到上一次残留的脏数据。开 MT5 新建脚本,挨个调用并打印,能直观看到最近一笔撤单的完整画像。
class="type">bool LastCanceledPendingOrderSymbol( class="type">class="kw">string &lastCanceledPendingOrderSymbol, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTicket( class="type">ulong &lastCanceledPendingOrderTicket, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderPriceOpen( class="type">class="kw">double &lastCanceledPendingOrderPriceOpen, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderSlPrice( class="type">class="kw">double &lastCanceledPendingOrderSlPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTpPrice( class="type">class="kw">double &lastCanceledPendingOrderTpPrice, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderSlPips( class="type">int &lastCanceledPendingOrderSlPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTpPips( class="type">int &lastCanceledPendingOrderTpPips, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTimeSetup( class="type">class="kw">datetime &lastCanceledPendingOrderTimeSetup, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTimeDone( class="type">class="kw">datetime &lastCanceledPendingOrderTimeDone, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderExpirationTime( class="type">class="kw">datetime &lastCanceledPendingOrderExpirationTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderMagic( class="type">ulong &lastCanceledPendingOrderMagic, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderReason( ENUM_ORDER_REASON &lastCanceledPendingOrderReason, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTypeFilling( ENUM_ORDER_TYPE_FILLING &lastCanceledPendingOrderTypeFilling, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderTypeTime( class="type">class="kw">datetime &lastCanceledPendingOrderTypeTime, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">bool LastCanceledPendingOrderComment( class="type">class="kw">string &lastCanceledPendingOrderComment, class=class="str">"cmt">// [Out] class="type">class="kw">string symbol, class=class="str">"cmt">// Optional parameter class="type">ulong magic class=class="str">"cmt">// Optional parameter );
◍ 订单与时段辅助接口的原型拆解
MQL5 里把下单、布尔转字符串、周期起点这类杂活收进辅助函数,能让主策略逻辑干净不少。下面三个原型是常见写法,直接贴进 include 文件就能用。
ulong magic 作为可选参数出现在下单类接口里,意味着你不传魔法号时底层用默认标识,传了就能在 MT5 的成交池里按策略隔离订单,回测时分辨哪笔来自哪套逻辑。
BoolToString(bool boolVariable) 返回字符串,适合把开关状态写进日志或面板,避免终端只吐 0/1 看花眼。
GetPeriodStart(int periodType) 吃一个周期类型整数、吐出该周期起始的 datetime,做跨周期对齐(比如找当前 H1 开盘时间)时比手算省事且不易错。
class="type">ulong magic class=class="str">"cmt">// Optional parameter ); class="type">class="kw">string BoolToString(class="type">bool boolVariable); class="type">class="kw">datetime GetPeriodStart(class="type">int periodType);
用 MT5 代码把利润因子算透
评估 EA 或策略绩效时,利润因子(Profit Factor)是最硬核的盈利刻度之一:它等于总毛利润除以总毛亏损。因子大于 1 代表总利润覆盖总亏损、策略倾向盈利;等于 1 是盈亏平衡;小于 1 则总亏损超越总利润,系统大概率在流血。 实战里别只盯因子绝对值。经验上,因子 > 2.0 说明策略效率高、利润丰厚;1.0~1.5 之间仅微利,可能仍需打磨;< 1.0 必须优化。外汇与贵金属杠杆高、滑点跳空频繁,因子回测好看不等于实盘能复现,需用真实历史校验。 下面这段 MQL5 演示了如何借 HistoryManager 库在 EA 中自动算因子。核心思路:枚举交易品种范围、用 input 过滤幻数,OnInit 里 switch 选定 symbol 后调 CalcProfitFactor(),再 Comment() 打印。 代码逐行拆解: #include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> —— 引入历史管理库,提供持仓数据检索。 enum symbolName { CURRENT_CHART_SYMBOL, ALL_ACCOUNT_SYMBOLS } —— 定义枚举,决定分析当前图符号还是账户全部符号。 input ulong magicNo=0 —— 幻数过滤,0 表示不限制 EA 标记。 input symbolName getSymbolName=ALL_ACCOUNT_SYMBOLS —— 默认拉全账户品种。 currency 拼接账户币种;其后 double 变量存总毛利、总毛损、均值与 profitFactor。 int 类变量计总交易数、盈利笔数、亏损笔数。 OnInit() 中 switch 给 symbol 赋值,CURRENT_CHART_SYMBOL 取 _Symbol,否则 ALL_SYMBOLS;空串转 "ALL_SYMBOLS" 显示。 随后 CalcProfitFactor() 清零所有累计变量,防止旧计算污染;函数体残余的赋值行即重置初值,跑完 OnInit 用 Comment 输出。 把 CalcProfitFactor 也挂进 OnTrade() 和 OnTick(),每次成交或报价刷新就重算因子——开 MT5 新建 Experts\Wanateki\HistoryManager\GetProfitFactor.mq5 抄这套,便能实时盯自己 EA 的毛利结构。
class="macro">#include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> class=class="str">"cmt">//-- enum symbolName { CURRENT_CHART_SYMBOL, ALL_ACCOUNT_SYMBOLS, }; class=class="str">"cmt">//--- input parameters input class="type">ulong magicNo = class="num">0; class=class="str">"cmt">//Magic Number(class="num">0 to disable) input symbolName getSymbolName = ALL_ACCOUNT_SYMBOLS; class="type">class="kw">string currency = " " + AccountInfoString(ACCOUNT_CURRENCY); class="type">class="kw">double totalGrossProfit = class="num">0.0, totalGrossLoss = class="num">0.0, averageGrossProfit = class="num">0.0, averageGrossLoss = class="num">0.0, averageProfitOrLossPerTrade = class="num">0.0, profitFactor = class="num">0.0; class="type">int totalTrades = class="num">0, totalLossPositions = class="num">0, totalProfitPositions = class="num">0; class="type">class="kw">string symbol, printedSymbol, profitFactorSummery, commentString; class="type">int OnInit() { class=class="str">"cmt">//--- class="kw">switch(getSymbolName) { case CURRENT_CHART_SYMBOL: symbol = _Symbol; break; case ALL_ACCOUNT_SYMBOLS: symbol = ALL_SYMBOLS; break; class="kw">default: symbol = ALL_SYMBOLS; } printedSymbol = symbol; if(symbol == "") printedSymbol = "ALL_SYMBOLS"; class=class="str">"cmt">//-- CalcProfitFactor(); Comment(commentString); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } totalGrossProfit = class="num">0.0; totalGrossLoss = class="num">0.0; averageProfitOrLossPerTrade = class="num">0.0; averageGrossProfit = class="num">0.0; averageGrossLoss = class="num">0.0; profitFactor = class="num">0.0; totalTrades = class="num">0; totalLossPositions = class="num">0; totalProfitPositions = class="num">0;
「用代码算出策略的盈利因子」
在 MT5 的 EA 或脚本里,先通过 GetAllPositionsData 把指定品种和 magic 号的持仓抓进数组,ArraySize 大于 0 才进入统计。下面这段逻辑就是逐笔区分盈亏并汇总毛利、毛亏。 遍历 positionsData 时,profit 字段为正就归到盈利笔数并累加 totalGrossProfit,为负则取 MathAbs 绝对值后计入 totalGrossLoss。注意外汇与贵金属杠杆高,单笔回撤可能吞噬多笔盈利,毛亏绝对值必须如实累计。 盈利因子用 totalGrossProfit / totalGrossLoss 得出;若 totalGrossLoss 为 0 则直接置 0 避免除零。averageGrossProfit、averageGrossLoss 都 NormalizeDouble 到 2 位小数,方便面板展示。 代码里把 profitFactor 大于 2.0 判为 HIGHLY PROFITABLE,大于 1.5 为 well-balanced,大于 1.0 则仅算正期望。实际跑下来,因子刚过 1.0 的策略在滑点和点差扰动下可能转为亏损,建议用历史持仓先验证再上实盘。
if(GetAllPositionsData(positionsData, symbol, magicNo) && ArraySize(positionsData) > class="num">0) { class=class="str">"cmt">//-- } totalTrades = ArraySize(positionsData); for(class="type">int r = class="num">0; r < totalTrades; r++) { if(positionsData[r].profit > class="num">0) class=class="str">"cmt">// profitable trade { ++totalProfitPositions; totalGrossProfit += positionsData[r].profit; } else class=class="str">"cmt">// loss trade { ++totalLossPositions; totalGrossLoss += MathAbs(positionsData[r].profit); } } class=class="str">"cmt">// Calculate the profit factor and other data if(totalGrossProfit > class="num">0 || totalGrossLoss > class="num">0) averageProfitOrLossPerTrade = NormalizeDouble( (totalGrossProfit + totalGrossLoss) / totalTrades, class="num">2 ); if(totalGrossProfit > class="num">0) averageGrossProfit = NormalizeDouble( (totalGrossProfit / totalProfitPositions), class="num">2 ); if(totalGrossLoss > class="num">0) averageGrossLoss = NormalizeDouble( (totalGrossLoss / totalLossPositions), class="num">2 ); class=class="str">"cmt">//-- if(totalGrossLoss == class="num">0.0) profitFactor = class="num">0.0; class=class="str">"cmt">// Avoid division by zero, indicating no losses profitFactor = totalGrossProfit / totalGrossLoss; } profitFactorSummery = "Profit Factor = " + DoubleToString(profitFactor, class="num">2); if(profitFactor > class="num">2.0) { profitFactorSummery = profitFactorSummery + "\nThe trading strategy is HIGHLY PROFITABLE and efficient."; } else if(profitFactor > class="num">1.5) { profitFactorSummery = profitFactorSummery + "\nThe trading strategy is profitable and well-balanced."; } else if(profitFactor > class="num">1.0) {
◍ 用盈利因子给策略写病历
盈利因子(Profit Factor)是总毛利除以总毛亏,等于 1.0 代表打平,大于 1 才谈得上净赚。下面这段逻辑在因子略大于 1 时提示“微利但需改进”,等于 1 提示“不亏不赚”,小于 1 直接判定“亏损待优化”,三种结论都只是概率层面的定性,不预示未来。 CalcProfitFactor() 先清零所有累加器,再调用 GetAllPositionsData 抓指定品种与 magic 号的持仓。遍历每笔持仓,盈利的累加 totalGrossProfit 并计数,亏损的用 MathAbs 取绝对值累加 totalGrossLoss,这样毛亏不会被负号抵消。 平均单笔用 (总毛利+总毛亏)/总笔数 算,保留两位小数;平均毛利只在 totalGrossProfit>0 时除以盈利笔数。外汇与贵金属杠杆高,因子回测好看也可能在实盘滑点下翻车,开 MT5 把这段挂到 EA 里跑一周历史单就能看到你的真实 PF。
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is slightly profitable but may need improvement.";
}
else
if(profitFactor == class="num">1.0)
{
profitFactorSummery = profitFactorSummery +
"\nThe strategy is break-even with no net gain or loss.";
}
else
{
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is unprofitable and needs optimization.";
}
class="type">void CalcProfitFactor()
{
totalGrossProfit = class="num">0.0;
totalGrossLoss = class="num">0.0;
averageProfitOrLossPerTrade = class="num">0.0;
averageGrossProfit = class="num">0.0;
averageGrossLoss = class="num">0.0;
profitFactor = class="num">0.0;
totalTrades = class="num">0;
totalLossPositions = class="num">0;
totalProfitPositions = class="num">0;
class=class="str">"cmt">//--
PositionData positionsData[];
if(GetAllPositionsData(positionsData, symbol, magicNo) && ArraySize(positionsData) > class="num">0)
{
totalTrades = ArraySize(positionsData);
for(class="type">int r = class="num">0; r < totalTrades; r++)
{
if(positionsData[r].profit > class="num">0) class=class="str">"cmt">// profitable trade
{
++totalProfitPositions;
totalGrossProfit += positionsData[r].profit;
}
else class=class="str">"cmt">// loss trade
{
++totalLossPositions;
totalGrossLoss += MathAbs(positionsData[r].profit);
}
}
class=class="str">"cmt">// Calculate the profit factor and other data
if(totalGrossProfit > class="num">0 || totalGrossLoss > class="num">0)
averageProfitOrLossPerTrade = NormalizeDouble(
(totalGrossProfit + totalGrossLoss) / totalTrades, class="num">2
);
if(totalGrossProfit > class="num">0)
averageGrossProfit = NormalizeDouble(
(totalGrossProfit / totalProfitPositions), class="num">2盈利因子落档与回执文本拼装
这段逻辑紧接平均亏损计算之后,先对 totalGrossLoss 为零的情况做防护:若没有任何亏损单,profitFactor 直接置 0.0,避免后续除零崩溃,同时语义上代表‘无亏损’。 随后用 totalGrossProfit / totalGrossLoss 得出盈利因子,并用 NormalizeDouble 保留两位小数精度(前文平均亏损同样保留两位小数,保证比率口径一致)。 profitFactorSummery 先拼出 'Profit Factor = ' 加 DoubleToString(profitFactor, 2)。接着按阈值分流:>2.0 判为高效盈利,>1.5 为均衡盈利,>1.0 为微利待优化,==1.0 为盈亏平衡,<1.0 为亏损需优化。外汇与贵金属策略回测中,PF 仅反映历史样本,实盘受滑点影响可能明显回落,属高风险验证项。 最后 commentString 用多段 \n 与分隔线拼出包含 Symbol、Magic No 的分析面板头,方便在 MT5 专家日志里直接读数。把 magicNo 和 printedSymbol 换成你实盘 EA 的值,就能在日志里看到对应品种的 PF 分档。
);
if(totalGrossLoss > class="num">0)
averageGrossLoss = NormalizeDouble(
(totalGrossLoss / totalLossPositions), class="num">2
);
class=class="str">"cmt">//--
if(totalGrossLoss == class="num">0.0)
profitFactor = class="num">0.0; class=class="str">"cmt">// Avoid division by zero, indicating no losses
profitFactor = totalGrossProfit / totalGrossLoss;
}
class=class="str">"cmt">// Analyze the Profit Factor result
profitFactorSummery = "Profit Factor = " + DoubleToString(profitFactor, class="num">2);
if(profitFactor > class="num">2.0)
{
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is HIGHLY PROFITABLE and efficient.";
}
else
if(profitFactor > class="num">1.5)
{
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is profitable and well-balanced.";
}
else
if(profitFactor > class="num">1.0)
{
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is slightly profitable but may need improvement.";
}
else
if(profitFactor == class="num">1.0)
{
profitFactorSummery = profitFactorSummery +
"\nThe strategy is break-even with no net gain or loss.";
}
else
{
profitFactorSummery = profitFactorSummery +
"\nThe trading strategy is unprofitable and needs optimization.";
}
commentString =
"\n\n-----------------------------------------------------------------------------------------------------" +
"\n HistoryManager.ex5 --- PROFIT FACTOR ANALYTICS ---" +
"\n-----------------------------------------------------------------------------------------------------" +
"\n -> Symbol = " + printedSymbol +
"\n -> Magic No = " + IntegerToString(magicNo) +
"\n-----------------------------------------------------------------------------------------------------"「把盈亏统计打印到图表上」
这段拼接逻辑负责把回测或实盘跑出来的交易统计,整理成一段带分隔线的字符串,直接显示在 MT5 图表左上角。 分隔线用 99 个减号堆出来,视觉上把「盈利因子摘要」和「逐条交易统计」隔开;下面依次列出总交易数、盈利笔数、亏损笔数,以及毛利、毛亏和净毛利。 毛利、毛亏都保留两位小数并接上货币后缀,平均每股盈亏用 (-/+) 占位,提醒你符号要按正负自己判读。外汇与贵金属波动剧烈,这类统计只反映历史样本,对未来盈亏倾向没有保证。 OnTrade 里调 CalcProfitFactor 做重算,OnTick 里用 Comment 把字符串刷到图上;你只要把这段塞进 EA,开 EURUSD 的 M15 跑几百根 K 线,就能在图表上实时看到自己的盈亏结构。
----------------------------------------------------------------------------------------------------- " + profitFactorSummery + ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- -> Total Trades Analysed = " + IntegerToString(totalTrades) + -> Total Profitable Trades = " + IntegerToString(totalProfitPositions) + -> Total Loss Trades = " + IntegerToString(totalLossPositions) + -------------------------------------------------------- -> Total Gross Profit = " + DoubleToString(totalGrossProfit, class="num">2) + currency + -> Total Gross Loss = -" + DoubleToString(totalGrossLoss, class="num">2) + currency + ---------------------------------- -> Total Gross(Profit - Loss) = " + DoubleToString(totalGrossProfit - totalGrossLoss, class="num">2) + currency + -------------------------------------------------------- -> Average Profit or Loss Per Trade = (-/+)" + DoubleToString(averageProfitOrLossPerTrade, class="num">2) + currency + -------------------------------------------------------- -> Average Gross Profit = " + DoubleToString(averageGrossProfit, class="num">2) + currency + -> Average Gross Loss = -" + DoubleToString(averageGrossLoss, class="num">2) + currency + -------------------------------------------------------- -----------------------------------------------------------------------------------------------------"; class=class="str">"cmt">//-- } class="type">void OnTrade() { class=class="str">"cmt">//--- CalcProfitFactor(); } class="type">void OnTick() { class=class="str">"cmt">//--- Comment(commentString); }
◍ 用脚本扒出本周 EA 的净水面
在 MT5 里想看某个幻数这周到底赚没赚,翻成交历史太慢。直接写个脚本调用历史管理库,能把本周毛利润、库存费、佣金和净利润一次性算清,并同时丢进日志和图表注释。 核心是先拿 GetPeriodStart(THIS_WEEK) 得到本周一开盘的 datetime,再用 GetDealsData 把 thisWeekStartTime 到 NOW 的成交拉进数组,筛选条件填 ALL_SYMBOLS 和幻数 0,就能覆盖全品种且只盯指定 EA。 遍历 dealsData 时只认 DEAL_ENTRY_OUT(平仓成交),把每笔的 profit、swap、commission 分别累加。总支出 = 库存费 + 佣金,净利润 = 毛利润 - 总支出的绝对值。这套逻辑跑下来,日志里会打印一条分隔线加四项数值,图表上用 Comment() 同步显示。 外汇和贵金属杠杆高,库存费可能在某些品种过夜后吃掉大半利润,周度核算能提前暴露策略的隐性成本。把幻数改成你实盘 EA 的值,就能在周日下午快速复盘这周真实落袋多少。
class="macro">#include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> class="type">void OnStart() { class="type">class="kw">string currency = " " + AccountInfoString(ACCOUNT_CURRENCY); class="type">class="kw">datetime thisWeekStartTime = GetPeriodStart(THIS_WEEK); DealData dealsData[]; if( GetDealsData( dealsData, thisWeekStartTime, NOW, ALL_SYMBOLS, class="num">0 ) && ArraySize(dealsData) > class="num">0 ) { class="type">class="kw">double totalGrossProfit = class="num">0.0, totalSwap = class="num">0.0, totalCommission = class="num">0.0; class="type">int totalDeals = ArraySize(dealsData); for(class="type">int k = class="num">0; k < totalDeals; k++) { if(dealsData[k].entry == DEAL_ENTRY_OUT) { totalGrossProfit += dealsData[k].profit; totalSwap += dealsData[k].swap; totalCommission += dealsData[k].commission; } } class="type">class="kw">double totalExpenses = totalSwap + totalCommission; class="type">class="kw">double totalNetProfit = totalGrossProfit - MathAbs(totalExpenses); Print("-------------------------------------------------"); Print(
把本周净盈亏打到图表和日志里
这段片段干的事很直接:用 Print 把本周各项金额输出到 MT5 专家日志,再用 Comment 把同样的数据覆盖到主图左上角,省得你切到账户历史里去加总。 Print 部分依次吐出账号登录号、本周总毛利、库存费合计、佣金合计、净盈亏;金额都经 DoubleToString(...,2) 保留两位小数,并拼接 currency 变量显示币种。日志里能看到类似「Total Net Profit This Week: 123.45 USD」这样的行。 Comment 部分用一长串加号拼出带分隔线的面板,起点是 thisWeekStartTime 到 NOW 的时间窗,再列毛利、掉期、佣金、净盈亏四项。跑 HistoryManager.ex5 时,图表上会直接挂出「TOTAL NET PROFIT THIS WEEK」块,数值随品种账户币种变动。 外汇与贵金属杠杆高,净盈亏周度波动可能很大,面板数字仅作当期统计,不代表后续倾向。复制下面代码到 EA 末尾的统计函数里,开 MT5 用策略测试器跑一周 tick 就能在图表验证显示效果。
"Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), " [ THIS WEEK&class="macro">#x27;S NET PROFIT ]" ); Print( "Total Gross Profit This Week: ", DoubleToString(totalGrossProfit, class="num">2), " " ); Print( "Total Swaps This Week: ", DoubleToString(totalSwap, class="num">2), " " ); Print( "Total Commission This Week: ", DoubleToString(totalCommission, class="num">2), " " ); Print( "Total Net Profit This Week: ", DoubleToString(totalNetProfit, class="num">2), " ", currency ); class=class="str">"cmt">//-- Comment( "\n\n-----------------------------------------------------------------------------------------------------" + "-------------------------------------------------------------------" + "\n HistoryManager.ex5 --- TOTAL NET PROFIT THIS WEEK ---" + "\n-----------------------------------------------------------------------------------------------------" + "------------------------------------------------------" + "\n DATE = ( From: " + TimeToString(thisWeekStartTime) + ", to: " + TimeToString(NOW) + " )" + "\n Account No = " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "\n------------------------------------------------------" + "\n -> Total Gross Profit = " + DoubleToString(totalGrossProfit, class="num">2) + currency + "\n -> Total Swaps = " + DoubleToString(totalSwap, class="num">2) + currency + "\n -> Total Commission = " + DoubleToString(totalCommission, class="num">2) + currency + "\n-----------------------------------------------------------------------------------------------------" + "------------------------------------------------------" + "\n -> TOTAL NET PROFIT = " + DoubleToString(totalNetProfit, class="num">2) + currency + "\n-----------------------------------------------------------------------------------------------------" + "------------------------------------------------------" + "\n-----------------------------------------------------------------------------------------------------" + "-------------------------------------------------------------------" ); } }
「用点数算清策略的盈亏比」
评估 EA 表现时,光看账户净值曲线不够直白。把已平仓位按点数拆成「赚了多少点」和「亏了多少点」,相除得到的盈亏比,能直接告诉你这套逻辑在点数层面是偏向盈利还是偏向磨损。 代码层面先引 HistoryManager 库,用枚举让用户选「当前图表品种」或「账户全部品种」,再配一个 magic 号做过滤。OnInit 里抓账户货币、定分析标的,若选 ALL_ACCOUNT_SYMBOLS 就把 symbol 置为通配,这样不论黄金还是欧美都进统计池。 核心循环在 GetAllPositionsData 拿到的数组上跑:profit>0 的累加 totalPipsProfit,其余累加 totalPipsLoss(亏损时 pipProfit 为负,直接加即可)。比率 = 总盈利点数 / abs(总亏损点数);若 totalPipsLoss==0,比率无定义,图表会提示「无亏损交易」。 比率 >1.0 说明赚的点数多于亏的,策略倾向有点数优势;==1.0 是点数收支平衡;<0 到 1.0 之间则亏损点数覆盖盈利,策略按点数看难盈利。结果用 Comment() 甩到图上,含品种、幻数、总笔数、盈亏笔数、总点数及比率解读。外汇与贵金属杠杆高,点数比仅反映历史样本,实盘仍可能因滑点扩大而偏离。 OnDeinit 清注释,OnTick 留空——分析只在加载时跑一次。想盯实时比值,把循环挪进 OnTick 并限频即可,但注意 MT5 历史读取有延迟,别拿它当逐笔风控。
class=class="str">"cmt">//-- class="macro">#include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> class=class="str">"cmt">//-- enum symbolName { CURRENT_CHART_SYMBOL, ALL_ACCOUNT_SYMBOLS, }; class=class="str">"cmt">//--- input parameters input class="type">ulong magicNo = class="num">0; class=class="str">"cmt">//Magic Number(class="num">0 to disable) input symbolName getSymbolName = CURRENT_CHART_SYMBOL; class="type">int OnInit() { class=class="str">"cmt">//--- class="type">class="kw">string currency = " " + AccountInfoString(ACCOUNT_CURRENCY); class="type">class="kw">string symbol, printedSymbol; class="kw">switch(getSymbolName) { case CURRENT_CHART_SYMBOL: symbol = _Symbol; break; case ALL_ACCOUNT_SYMBOLS: symbol = ALL_SYMBOLS; break; class="kw">default: symbol = ALL_SYMBOLS; } printedSymbol = symbol; if(symbol == "") printedSymbol = "ALL_SYMBOLS"; class=class="str">"cmt">//-- class="type">int totalTrades = class="num">0; class="type">int totalLossPositions = class="num">0; class="type">int totalProfitPositions = class="num">0; class="type">class="kw">double totalPipsProfit = class="num">0; class="type">class="kw">double totalPipsLoss = class="num">0; class="type">class="kw">string interpretation; class="type">class="kw">double pipsProfitToLossRatio = class="num">0; class=class="str">"cmt">//-- PositionData positionsData[]; if(GetAllPositionsData(positionsData, symbol, magicNo) && ArraySize(positionsData) > class="num">0) { totalTrades = ArraySize(positionsData); for(class="type">int r = class="num">0; r < totalTrades; r++) { if(positionsData[r].profit > class="num">0) class=class="str">"cmt">// profitable trade { ++totalProfitPositions; totalPipsProfit += positionsData[r].pipProfit; } else class=class="str">"cmt">// loss trade { ++totalLossPositions; totalPipsLoss += positionsData[r].pipProfit; } } class=class="str">"cmt">// Calculate the pip profit loss ratioInterpretation if(totalPipsLoss == class="num">0) {
◍ 盈亏点位比的判定与面板输出
当总亏损点数为零时,代码不会去算比值,而是直接给一句“Undefined”并标注无亏损交易——这种边界处理能避免除以零的运行时错误,实盘回测里若策略过滤极严、只留盈利单,就可能触发该分支。 正常路径下用 fabs(totalPipsProfit / totalPipsLoss) 取绝对值,再用 switch 按大于1、等于1、小于1 三态给结论:>1 倾向盈利,=1 打平,<1 倾向亏损。注意这里只看点数不对冲滑点,外汇与贵金属点值波动大,高杠杆下该比率为正不代表净值必增。 Comment() 把符号、Magic 号与解读文字拼成带分隔线的面板,在 MT5 图表左上角实时显示。你复制这段代码到 EA 的 OnTick 或独立脚本里,把 printedSymbol、magicNo 换成自己策略的值,就能直接盯这一指标。
interpretation = "Pips Profit-to-Loss Ratio: Undefined(Total pips loss is zero)." + "The strategy has no losing trades."; } else { pipsProfitToLossRatio = fabs(totalPipsProfit / totalPipsLoss); class="kw">switch(pipsProfitToLossRatio > class="num">1.0 ? class="num">1 : pipsProfitToLossRatio == class="num">1.0 ? class="num">0 : -class="num">1) { case class="num">1: interpretation = "Pips Profit-to-Loss Ratio: " + DoubleToString(pipsProfitToLossRatio, class="num">2) + ". The strategy is profitable as it gains more pips than it loses."; break; case class="num">0: interpretation = "Pips Profit-to-Loss Ratio: " + DoubleToString(pipsProfitToLossRatio, class="num">2) + ". The strategy breaks even in terms of pips."; break; case -class="num">1: interpretation = "Pips Profit-to-Loss Ratio: " + DoubleToString(pipsProfitToLossRatio, class="num">2) + ". The strategy is unprofitable as it loses more pips than it gains."; break; } } Comment( "\n\n-----------------------------------------------------------------------------------------------------" + "---------------------------" + "\n HistoryManager.ex5 --- PIPS PROFIT TO LOSS RATIO ---" + "\n-----------------------------------------------------------------------------------------------------" + "---------------------------" + "\n -> Symbol = " + printedSymbol + "\n -> Magic No = " + IntegerToString(magicNo) + "\n-----------------------------------------------------------------------------------------------------" + "---------------------------" + "\n-----------------------------------------------------------------------------------------------------" + "---------------------------" + "\n" + interpretation + "\n-----------------------------------------------------------------------------------------------------" + "---------------------------" + "\n-----------------------------------------------------------------------------------------------------" + "---------------------------" +
正文
<span class="string">"\n -> Total Trades Analysed = "</span> + <span class="functions">IntegerToString</span>(totalTrades) + <span class="string">"\n -> Total Profitable Trades = "</span> + <span class="functions">IntegerToString</span>(totalProfitPositions) + <span class="string">" ( "</span> + <span class="functions">DoubleToString</span>(totalPipsProfit, <span class="number">0</span>) + <span class="string">" Pips )"</span> + <span class="string">"\n -> Total Loss Trades = "</span> + <span class="functions">IntegerToString</span>(totalLossPositions) + <span class="string">" ( "</span> + <span class="functions">DoubleToString</span>(totalPipsLoss, <span class="number">0</span>) + <span class="string">" Pips )"</span> + <span class="string">"\n --------------------------------------------------------------------------"</span> + <span class="string">"\n -> PIPS PROFIT TO LOSS RATIO = "</span> + <span class="functions">DoubleToString</span>(pipsProfitToLossRatio, <span class="number">2</
「用脚本核出账户累计入金实数」
想用算法而非肉眼翻交易历史来确认账户到底注入了多少本金,可以写一个轻量脚本,调用历史管理接口把全部 deal 拉进 DealData 数组,再筛 BALANCE 类流水累加 profit 字段。 这段代码的核心逻辑是:先判断 GetAllDealsData() 成功且数组长度大于 0,否则不跑;随后用 totalDeposits 变量从 0.0 起算,for 循环里只认 DEAL_TYPE_BALANCE 的记录,把每笔 profit 叠进去,同时 Print 出金额、账户币种与入金时间。 最终用 Comment() 在图表左上角常驻显示「Total Cash Deposits」数值,并用 Print() 往终端日志写账户登录号与合计入金。外汇与贵金属账户杠杆高、净值波动剧烈,入金核对仅作资金流水留痕,不构成任何收益判断。 直接把下面代码存成 .mq5 编译进 MT5 跑一次,就能在终端看到逐笔入金与总和;若你库函数命名空间不同,改第一行 include 路径即可。
class="macro">#include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> class="type">void OnStart() { class=class="str">"cmt">//--- class=class="str">"cmt">// Find and list total deposited funds in the account DealData dealsData[]; if(GetAllDealsData(dealsData) && ArraySize(dealsData) > class="num">0) { class="type">class="kw">double totalDeposits = class="num">0.0; class="type">int totalDeals = ArraySize(dealsData); Print(""); for(class="type">int k = class="num">0; k < totalDeals; k++) { if(dealsData[k].type == DEAL_TYPE_BALANCE) { totalDeposits += dealsData[k].profit; Print( dealsData[k].profit, " ", AccountInfoString(ACCOUNT_CURRENCY), " --> Cash deposit on: ", dealsData[k].time ); } } Print("-------------------------------------------------"); Print( "Account No: ", AccountInfoInteger(ACCOUNT_LOGIN), " Total Cash Deposits: ", totalDeposits, " ", AccountInfoString(ACCOUNT_CURRENCY) ); Comment( "\n\n-----------------------------------------------------------------------------------------------------" + "---------------------------------------------------------" + "\n HistoryManager.ex5 --- TOTAL ACCOUNT DEPOSITS ---" + "\n-----------------------------------------------------------------------------------------------------" + "------------------------------------------------------" + "\n -> Account No = " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "\n -> Total Cash Deposits = " + DoubleToString(totalDeposits, class="num">2) + AccountInfoString(ACCOUNT_CURRENCY) + "\n-----------------------------------------------------------------------------------------------------" + "------------------------------------------------------" ); } }
◍ 用历史与持仓库拼一个价格驱动 EA
把历史管理库和持仓管理库接进来,可以写出代码量极小但逻辑完整的 EA。下面这个 PriceTrader_EA 示例就靠两个预编译库撑起开仓、加仓与手数调整,读者在 MT5 里建个 mq5 把代码贴进去就能跑。 EA 的输入参数里,tpSpreadMulti=70、slSpreadMulti=90 表示止损止盈以当前点差倍数为基准,maxLotIncrease=1000 限定亏损后加仓的手数上限。亏损单平仓后,若上一单 volume*2 小于最小手数乘该上限,则直接把 lotSize 翻倍以弥补损失,这属于概率型补救而非稳赚。 OnInit 里实时算 SYMBOL_ASK 减 SYMBOL_BID 的点差并转 pip,再乘倍数得出 sl/tp;加载与卸载分别播 connect.wav / disconnect.wav。H1 价格行为决定首单方向,外汇与贵金属波动剧烈、点差跳变频繁,实盘前务必用策略测试器按品种回测。 代码段展示了头文件引用与核心变量、OnInit/OnDeinit/OnTick 骨架,其中 OnTick 只列到首仓与翻倍手数的判断,后续连续开仓逻辑可依此扩展。
class="macro">#include <Wanateki/Toolkit/HistoryManager/HistoryManager.mqh> class="macro">#include <Wanateki/Toolkit/PositionsManager/PositionsManager.mqh> input class="type">ulong magicNo = class="num">101010; input class="type">int tpSpreadMulti = class="num">70; input class="type">int slSpreadMulti = class="num">90; input class="type">int maxLotIncrease = class="num">1000; class="type">bool eaJustLoaded = true; class="type">class="kw">double spread; class="type">int spreadPips; class="type">long minSLTP = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL); class="type">long freezeLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_FREEZE_LEVEL); class="type">class="kw">double lotSize; class="type">int sl, tp; class="type">int totalOpenPositions, totalBuyPositionsOpen, totalSellPositionsOpen; class=class="str">"cmt">//-- PositionData lastClosedPositionInfo; class="type">int OnInit() { class=class="str">"cmt">//--- spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID); spread = NormalizeDouble(spread, _Digits); spreadPips = class="type">int(spread / _Point); tp = spreadPips * tpSpreadMulti; sl = spreadPips * slSpreadMulti; class=class="str">"cmt">//-- PlaySound("connect.wav"); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Comment(""); PlaySound("disconnect.wav"); } class="type">void OnTick() { class=class="str">"cmt">//--- lotSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); class=class="str">"cmt">//-- totalOpenPositions = SymbolPositionsTotal(_Symbol, magicNo); totalBuyPositionsOpen = SymbolBuyPositionsTotal(_Symbol, magicNo); totalSellPositionsOpen = SymbolSellPositionsTotal(_Symbol, magicNo); if(eaJustLoaded && totalOpenPositions == class="num">0) { class=class="str">"cmt">//-- GetLastClosedPositionData(lastClosedPositionInfo, _Symbol, magicNo); if(lastClosedPositionInfo.ticket > class="num">0 && lastClosedPositionInfo.profit < class="num">0) { if(lastClosedPositionInfo.volume * class="num">2 < lotSize * maxLotIncrease) lotSize = lastClosedPositionInfo.volume * class="num">2; class=class="str">"cmt">// class="type">class="kw">double lot size } class=class="str">"cmt">//--
盈亏之后的下单分支逻辑
这段逻辑跑在 H1 周期上,用当前 K 线的开盘价与收盘价比较决定首仓方向:开价低于收价则做多,否则做空,注释标签统一记为 Initial_Position。 若检测到已有持仓(totalOpenPositions>0),就把 eaJustLoaded 置否,避免重启后重复初始化。 当无持仓且 EA 已非刚加载状态时,会调取上一笔平仓数据:若利润为正,沿原方向补单,标签 Consecutive Profit,倾向捕捉趋势延续。 若上一笔亏损,则把手数翻倍(上限受 maxLotIncrease 约束),并反向开仓,标签 Loss Recovery——这是典型的逆势加仓,外汇与贵金属波动下可能快速放大回撤,属高风险操作。 手数翻倍前有一道护栏:lastClosedPositionInfo.volume*2 必须小于 lotSize*maxLotIncrease,否则不加倍,实盘里建议把 maxLotIncrease 调到 4~8 之间做压力测试。
if(iOpen(_Symbol, PERIOD_H1, class="num">0) < iClose(_Symbol, PERIOD_H1, class="num">0)) { OpenBuyPosition(magicNo, _Symbol, lotSize, sl, tp, "Initial_Position"); } else { OpenSellPosition(magicNo, _Symbol, lotSize, sl, tp, "Initial_Position"); } if(totalOpenPositions > class="num">0) eaJustLoaded = false; } else { eaJustLoaded = false; } if(totalOpenPositions == class="num">0 && !eaJustLoaded) { if(GetLastClosedPositionData(lastClosedPositionInfo, _Symbol, magicNo)) { if(lastClosedPositionInfo.profit > class="num">0) class=class="str">"cmt">// PROFITABLE TRADE { if(lastClosedPositionInfo.type == POSITION_TYPE_BUY) { OpenBuyPosition(magicNo, _Symbol, lotSize, sl, tp, "Consecutive Profit"); } else class=class="str">"cmt">// SELL POSITION { OpenSellPosition(magicNo, _Symbol, lotSize, sl, tp, "Consecutive Profit"); } } else class=class="str">"cmt">// LOSS TRADE { if(lastClosedPositionInfo.volume * class="num">2 < lotSize * maxLotIncrease) lotSize = lastClosedPositionInfo.volume * class="num">2; class=class="str">"cmt">// class="type">class="kw">double lot size class=class="str">"cmt">//-- if(lastClosedPositionInfo.type == POSITION_TYPE_BUY) { class=class="str">"cmt">// Reverse trade direction OpenSellPosition(magicNo, _Symbol, lotSize, sl, tp, "Loss Recovery"); } else class=class="str">"cmt">// SELL POSITION { OpenBuyPosition(magicNo, _Symbol, lotSize, sl, tp, "Loss Recovery"); } } } }
「在 MT5 里跑一遍 Price Trader EA 回测」
打开 MT5 策略测试器,把 PriceTrader_EA 拖进去,选 Deriv 的 Demo 服务器,品种挂 Volatility 50 (1s) Index,周期切日线。测试区间拉到 2024 年 1 月至 2025 年 2 月,共 1 年 2 个月,模型用「每次分时都基于真实的分时」,入金填 5000 USD,杠杆 1:1000,EA 会按品种动态调手数、止损、止盈,不用手改参数。 回测跑完,净值曲线显示收益超 129%,最大回撤压在 29% 左右。这类合成指数产品波动机制与外汇现货不同,但高杠杆下仍属高风险,回测亮眼不代表实盘能复刻。 想验证就自己加载到图表盯一天以上,或换别的品种在模拟账户试。EA 自适应参数的特性让它跨品种测试成本很低,不过样本只有十四个月,样本外表现倾向需自行压力测试。
◍ 用 HistoryManager 把历史处理交给代码
HistoryManager.ex5 这套库把 MT5 里交易、订单、持仓和挂单的查询封装成单行调用,开发者不必再手写一堆 HistorySelect 循环。对做策略回测或复盘的人来说,省下的时间可以直接投到参数优化上,而不是耗在取数逻辑里。 作者在收尾时放了一个 PriceTrader_EA 作为示例,它不算复杂,但把本系列讲到的历史访问方式跑通了。你可以在 MT5 里加载这个 EA,看它怎么用库函数拉出成交记录再下单,相当于一份能直接编译运行的说明书。 外汇与贵金属算法交易杠杆高、滑点随机,任何历史处理库都只解决取数效率,不替你过滤市场风险。真正要验证的是:同样的 HistoryManager 调用,在你的品种和周期上返回延迟是否可接受。
随包文件怎么落进 MT5 目录
这套工具包把前文所有示例都打进了 ZIP,里面两个 EX5 库(HistoryManager 51.85 KB、PositionsManager 44.74 KB)负责底层历史与仓位读写,四个 mqh 头文件把库函数暴露给 EA 和脚本。直接用现成 EA 就能算利润因子、本周净利、品种盈亏比点数,最低门槛是打开 GetNetProfitThisWeek.mq5(4.16 KB)跑一遍。 安装别图省事双击,按作者给的路径手动建目录最稳:EX5 丢进 MQL5\Libraries\Wanateki\Toolkit\HistoryManager\,mqh 进 MQL5\Include\Wanateki\Toolkit\HistoryManager\,EA 源文件去 MQL5\Experts\Wanateki\HistoryManager\,脚本去 MQL5\Scripts\Wanateki\HistoryManager\。编译后重启终端,历史查询在普通机器上处理大量订单也倾向几毫秒返回,具体看本机 IO。 外汇与贵金属杠杆高、滑点跳空频繁,这类统计脚本只还原已发生账单,不预示后续盈亏。真要验速度,先拿自己半年真实历史跑 GetTotalDeposits.mq5(2.38 KB),比对入金曲线和券商对账单缺口。