MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·综合运用
(3/3)·从伪代码到完整 EA,解决论坛里最高频的 7 个下单与限仓疑问
不少自学 MQL5 的人能看懂单条代码片段,却卡在「怎么把买卖、限仓、交易时段拼成一套不报错的 EA」。本篇用基于项目的方式,把前一天日线方向判断与当日首根小时线确认直接落成可运行逻辑。
日线定方向后的 H1 破位触发
这套逻辑先把日线收线作为方向过滤器:当日线收阴(close[0] < open[0])只找做空机会,收阳则只找做多。外汇与贵金属波动受消息驱动,日线定调只是概率倾向,不等于趋势延续。 具体触发看 H1 节奏。以空单为例,要求倒数第 3 根 H1 收盘 ≥ 首根参考收盘、倒数第 2 根收盘跌破该参考、且当前时间过了交易起点,三个条件同时满足才 trade.Sell。多单镜像处理。 新 K 线识别用 CopyRates 拉最近 3 根 H1 的 MqlRates,比 bar[0].time 与 lastBarTime:更大就置 newBar=true 并更新时间戳,否则 false。仅在 newBar 为真时跑方向判断,避免同根 K 线内重复发单。 实盘前把 1.0 手数换成你的仓位,sl/tp 变量也需在别处算好。MT5 策略测试器用 2023 年 EURUSD H1 跑一遍,能直接看出该过滤在震荡日容易连续不触发。
if(daily_close[class="num">0] < daily_open[class="num">0]) { class=class="str">"cmt">// Check specific conditions for a sell trade if(H1_price_close[class="num">2] >= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] < first_h1_price_close[class="num">0] && current_time >= first_tradetime) { class=class="str">"cmt">// Execute the sell trade trade.Sell(class="num">1.0, _Symbol, Bid, sl_sell, tp_sell); class=class="str">"cmt">// Replace with your lot size Comment("It&class="macro">#x27;s a sell"); } } class=class="str">"cmt">// If the last daily bar is bullish if(daily_close[class="num">0] > daily_open[class="num">0]) { class=class="str">"cmt">// Check specific conditions for a buy trade if(H1_price_close[class="num">2] <= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] > first_h1_price_close[class="num">0] && current_time >= first_tradetime) { class=class="str">"cmt">// Execute the buy trade trade.Buy(class="num">1.0, _Symbol, Ask, sl_buy, tp_buy); class=class="str">"cmt">// Replace with your lot size Comment("It&class="macro">#x27;s a buy"); } } } class=class="str">"cmt">// Flag to indicate a new bar has formed class="type">bool newBar; class=class="str">"cmt">// Variable to store the time of the last bar class="type">class="kw">datetime lastBarTime; class=class="str">"cmt">// Array to store bar data(OHLC) class="type">MqlRates bar[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Check for a new bar CopyRates(_Symbol, PERIOD_H1, class="num">0, class="num">3, bar); class=class="str">"cmt">// Copy the latest class="num">3 H1 bars if(bar[class="num">0].time > lastBarTime) class=class="str">"cmt">// Check if the latest bar time is greater than the last recorded bar time { newBar = true; class=class="str">"cmt">// Set the newBar flag to true lastBarTime = bar[class="num">0].time; class=class="str">"cmt">// Update the last bar time } else { newBar = class="kw">false; class=class="str">"cmt">// Set the newBar flag to class="kw">false } class=class="str">"cmt">// If a new bar has formed if(newBar == true) { class=class="str">"cmt">// If the last daily bar is bearish if(daily_close[class="num">0] < daily_open[class="num">0]) { class=class="str">"cmt">// Check specific conditions for a sell trade if(H1_price_close[class="num">2] >= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] < first_h1_price_close[class="num">0] && current_time >= first_tradetime) { class=class="str">"cmt">// Execute the sell trade trade.Sell(class="num">1.0, _Symbol, Bid, sl_sell, tp_sell); class=class="str">"cmt">// Replace with your lot size Comment("It&class="macro">#x27;s a sell");
◍ 持仓计数与当日成交回看
日线收阳之后,策略并不急着直接市价买入,而是先盘点当前账户的持仓状态与当日已成交记录,避免重复开仓或超出计划笔数。 下面这段先统计匹配 MagicNumber 的未平仓位:遍历 PositionsTotal(),用 PositionGetTicket(i) 取ticket,再比对 POSITION_MAGIC。每命中一次 totalPositions 自增 1,实盘里你能凭这个数判断「今天是否已经占好坑」。 随后用 HistorySelect(start_time, end_time) 拉取指定时段历史,循环 HistoryDealsTotal() 并筛 DEAL_MAGIC == MagicNumber 且 DEAL_ENTRY == DEAL_ENTRY_IN 的成交,totalDeal 记录当日进场次数。若回测发现 totalDeal 经常大于 2,说明过滤条件过松,外汇与贵金属波动剧烈,需收紧 first_tradetime 或 H1 穿越判定。 利润统计部分另起循环,依旧基于 HistoryDealsTotal(),先取 ticket 再判 DEAL_ENTRY_IN,准备按成交魔术码归集 profit。注意这里 dealsMagic 与 profit 已声明但未在片段内累加完,复制到 MT5 时须补完赋值,否则 totalProfit 恒为 0。
class="type">int totalPositions = class="num">0; for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">class="kw">ulong ticket = PositionGetTicket(i); if(PositionGetInteger(POSITION_MAGIC) == MagicNumber) { totalPositions++; } } class="type">bool success = HistorySelect(start_time, end_time); class="type">int totalDeal = class="num">0; if(success) { for(class="type">int i = class="num">0; i < HistoryDealsTotal(); i++) { class="type">class="kw">ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber) { if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { totalDeal++; } } } } class="type">class="kw">double totalProfit = class="num">0; class="type">long dealsMagic = class="num">0; class="type">class="kw">double profit = class="num">0; if(success) { for(class="type">int i = class="num">0; i < HistoryDealsTotal(); i++) { class="type">class="kw">ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { class=class="str">"cmt">// Get the magic number of the deal
「按魔术码归集平仓盈亏并卡掉特定交易日」
这段逻辑在做两件事:从历史成交里把指定 EA 的平仓利润加总,以及在到达 end_time 时把同魔术码持仓全平。外汇与贵金属杠杆高,历史统计和强制平仓都只是概率性风控,不保证规避回撤。 历史遍历里先用 HistoryDealGetInteger 取 DEAL_MAGIC,再判断 DEAL_ENTRY 是否为 DEAL_ENTRY_OUT。若是平仓且魔术码吻合,就把 DEAL_PROFIT 通过 HistoryDealGetDouble 读出并累加到 totalProfit。这样你能只统计自己 EA 的出场成绩,不会被手动单干扰。 收盘段用 PositionsTotal 循环,PositionGetInteger(POSITION_MAGIC) 等于 MagicNumber 且 current_time == end_time 时调用 trade.PositionClose(ticket)。注意这是等号硬比对,若服务器时间跳秒就可能漏平,实盘建议改成 current_time >= end_time 更稳。 时间过滤部分用 MqlDateTime 取 day_of_week 和 mon。代码里写了 week_day==5 提示周五不交易、week_day==4 提示周四不交易,但只是 Comment 输出,并没有真正阻断下单。要落地禁交易得在开仓前加 return 或信号屏蔽。
dealsMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC); } class=class="str">"cmt">// Check if the deal was an exit if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { class=class="str">"cmt">// Get the profit of the deal profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); class=class="str">"cmt">// Check if the magic number matches if(MagicNumber == dealsMagic) { class=class="str">"cmt">// Add the profit to the total profit totalProfit += profit; } } } } class=class="str">"cmt">// Close trades at the specified end time for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class=class="str">"cmt">// Get the ticket number for the position class="type">class="kw">ulong ticket = PositionGetTicket(i); class=class="str">"cmt">// Check if the position&class="macro">#x27;s magic number matches and if it&class="macro">#x27;s the end time if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && current_time == end_time) { class=class="str">"cmt">// Close the position trade.PositionClose(ticket); } } class=class="str">"cmt">//getting the day of week and month class="type">MqlDateTime day; class=class="str">"cmt">//Declare an class="type">MqlDateTime structure to hold the current time and date TimeCurrent(day); class=class="str">"cmt">// Get the current time and fill the class="type">MqlDateTime structure class="type">int week_day = day.day_of_week; class=class="str">"cmt">//Extract the day of the week(class="num">0 = Sunday, class="num">1 = Monday, ..., class="num">6 = Saturday) class=class="str">"cmt">//getting the current month class="type">MqlDateTime month; class=class="str">"cmt">//Declare a structure to hold current month information TimeCurrent(month); class=class="str">"cmt">//Get the current date and time class="type">int year_month = month.mon; class=class="str">"cmt">//Extract the month component(class="num">1 for January, class="num">2 for February, ..., class="num">12 for December) if(week_day == class="num">5) { Comment("No trades on fridays", "\nday of week: ",week_day); } else if(week_day == class="num">4) { Comment("No trades on Thursdays", "\nday of week: ",week_day); } else { Comment(week_day); }
把这条线请下神坛
走到这一节,你已经能在 MT5 里让 EA 只开一单、圈定交易时段、给盈亏封顶,也知道用项目式练习替代死记语法。前面几篇铺的买卖、取 K 线开收价、防每次 tick 乱触发,合起来就是一套能跑的最小闭环。 真正卡住人的从来不是语言本身。MQL5 只是把你的价格行为逻辑翻译成 broker 能执行的指令,外汇和贵金属杠杆高、滑点跳空随时来,回测顺不代表实盘稳,概率上仍可能连续止损。 下次打开 MQL5Project2.mq5(约 10 KB),先改一处:把单笔交易限制从 1 调到 2,看风控逻辑哪行开始报警。代码是死的,盘面是活的,这条线没必要供着。