MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·进阶篇
📘

MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·进阶篇

第 2/3 篇

◍ 日线和H1开盘数据的抓取骨架

做跨周期日内策略,第一步是把「昨天的日线开收」和「今天H1每根bar」拉进数组。上面这段代码把日线数据用 CopyClose/CopyOpen 按 PERIOD_D1 取前1根(shift=1),再用 ArraySetAsSeries 设成最新在前,避免后面索引错位。 时间窗用字符串转 datetime 来控制:start="00:00"、end="20:00"、firsttrade="02:00",分别管交易时段与首单时点。外汇和贵金属波动受时段影响大,这种硬切断能回避流动性差的尾盘,但高杠杆下仍属高风险操作,参数须自己回测。 OnInit 里只干了 trade.SetExpertMagicNumber(103432),给 CTrade 实例打魔术号。MagicNumber 不冲突是多EA共存的底线,复制代码时先改掉这个 103432。 OnTick 前几行就是时间转换加日线拷贝,没下任何单。你开 MT5 新建 EA 把这段贴进 OnTick 头部,用 Print 把 daily_open[0] 和 daily_close[0] 打出来,就能确认「昨日开盘/收盘」是否如预期到位。

MQL5 / C++
CopyOpen(_Symbol, PERIOD_D1, start_date, end_date, open_prices);
class=class="str">"cmt">// Declaring time strings
class="type">class="kw">string start_time_str = "class="num">00:class="num">00";  class=class="str">"cmt">// Start time
class="type">class="kw">string end_time_str = "class="num">20:class="num">00";    class=class="str">"cmt">// End time
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">// Converting time strings to class="type">class="kw">datetime values
   class="type">class="kw">datetime start_time = StringToTime(start_time_str);
   class="type">class="kw">datetime end_time = StringToTime(end_time_str);
   Print("start_time: ", start_time,"\nend_time: ",end_time);
  }
class="macro">#include <Trade/Trade.mqh>
class=class="str">"cmt">// Create an instance of the CTrade class for trading operations
CTrade trade;
class=class="str">"cmt">// Unique identifier for the EA&class="macro">#x27;s trades
class="type">int MagicNumber = class="num">103432;
class=class="str">"cmt">// Arrays to store the previous day&class="macro">#x27;s open and close prices
class="type">class="kw">double daily_close[];
class="type">class="kw">double daily_open[];
class=class="str">"cmt">// Arrays to store the first H1 bar&class="macro">#x27;s open and close prices of the day
class="type">class="kw">double first_h1_price_close[];
class=class="str">"cmt">// Arrays to store H1 bars&class="macro">#x27; open and close prices
class="type">class="kw">double H1_price_close[];
class="type">class="kw">double H1_price_open[];
class=class="str">"cmt">// Strings to define the trading start and end times and the first trade time
class="type">class="kw">string start = "class="num">00:class="num">00";
class="type">class="kw">string end = "class="num">20:class="num">00";
class="type">class="kw">string firsttrade  = "class="num">02:class="num">00";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Convert time strings to class="type">class="kw">datetime format
   class="type">class="kw">datetime start_time = StringToTime(start); class=class="str">"cmt">// Convert start time class="type">class="kw">string to class="type">class="kw">datetime
   class="type">class="kw">datetime end_time = StringToTime(end); class=class="str">"cmt">// Convert end time class="type">class="kw">string to class="type">class="kw">datetime
   class="type">class="kw">datetime current_time = TimeCurrent(); class=class="str">"cmt">// Get the current time
   class="type">class="kw">datetime first_tradetime = StringToTime(firsttrade); class=class="str">"cmt">// Convert first trade time class="type">class="kw">string to class="type">class="kw">datetime
class=class="str">"cmt">// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, class="num">1, class="num">1, daily_close); class=class="str">"cmt">// Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, class="num">1, class="num">1, daily_open); class=class="str">"cmt">// Copy the open price of the previous day
class=class="str">"cmt">// Set the arrays to be copied from right to left(latest to oldest)
   ArraySetAsSeries(daily_close, true); class=class="str">"cmt">// Set daily_close array as series
   ArraySetAsSeries(daily_open, true); class=class="str">"cmt">// Set daily_open array as series

「用日线方向过滤 H1 回抽入场」

这段逻辑把「日线定方向、H1 找回抽」拆成了可直接粘贴的 MT5 代码片段。先取当日第一根 H1 的收盘价作为基准线,再拉最近 5 根 H1 的收/开数组,并全部按时间序列倒排,保证 [0] 是最新一根。 日线收阴(daily_close[0] < daily_open[0])时,只找卖点:要求倒数第 3 根 H1 收盘 ≥ 首根 H1 收盘、倒数第 2 根收盘跌破该基准、且当前时间已过 first_tradetime,才在图表打印 "Its a sell"。日线收阳则对称处理,打印 "Its a buy"。 实测把这套挂在 XAUUSD 的 H1 上,首根 H1 基准价往往就是亚盘开盘价的 proxy;外汇与贵金属杠杆高,信号仅作概率倾向,实盘前务必在策略测试器跑 3 个月以上 tick 数据。

MQL5 / C++
class=class="str">"cmt">// Copy close and open prices for the first H1 bar of the day
  CopyClose(_Symbol, PERIOD_H1, start_time, class="num">1, first_h1_price_close); class=class="str">"cmt">// Copy the close price of the first H1 bar
class=class="str">"cmt">// Copy close prices for the latest class="num">5 H1 bars
  CopyClose(_Symbol, PERIOD_H1, class="num">0, class="num">5, H1_price_close); class=class="str">"cmt">// Copy the close prices of the latest class="num">5 H1 bars
  CopyOpen(_Symbol, PERIOD_H1, class="num">0, class="num">5, H1_price_open); class=class="str">"cmt">// Copy the open prices of the latest class="num">5 H1 bars
class=class="str">"cmt">// Set the arrays to be copied from right to left(latest to oldest)
  ArraySetAsSeries(H1_price_close, true); class=class="str">"cmt">// Set H1_price_close array as series
  ArraySetAsSeries(H1_price_open, true); class=class="str">"cmt">// Set H1_price_open array as series
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)
        {
         Comment("Its 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)
        {
         Comment("Its a buy");
        }
    }
  }

用 CTrade 把下单和风控写进 EA

MQL5 里真正下单不用自己拼协议,直接 #include <Trade/Trade.mqh> 后实例化 CTrade,调 trade.Buy / trade.Sell 即可。这两个方法的核心参数是 lotsize、_Symbol、Ask/Bid、sl、tp——止损止盈必须按当前价加减点值算出来再传进去,否则单子会被平台拒。 OnTick 每来一个报价就触发一次,若不在里面加栅栏,EA 会疯一样连续开仓。实操里至少叠三层闸:新柱才允许动手、同魔法数未平仓位为零才动手、当天入场次数不超阈值才动手。三层都过,单子才发得出去。 上面那段代码把交易库、MagicNumber=103432、日线/小时线价格数组和交易时段字符串(start 00:00、end 20:00、firsttrade 02:00)一次性声明好了。开 MT5 新建 EA 时,把这段直接贴到全局区,后续函数就能复用这些变量,不用每次重算。 外汇和贵金属杠杆高,滑点和休市跳空会让 sl/tp 实际成交价偏离,任何限仓逻辑都只是降低过度交易概率,不保证亏损可控。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh> class=class="str">"cmt">// Include the trade library for trading functions
class=class="str">"cmt">// Create an instance of the CTrade class for trading operations
CTrade trade;
class=class="str">"cmt">// Unique identifier for the EA&class="macro">#x27;s trades
class="type">int MagicNumber = class="num">103432;
class=class="str">"cmt">// Arrays to store the previous day&class="macro">#x27;s open and close prices
class="type">class="kw">double daily_close[];
class="type">class="kw">double daily_open[];
class=class="str">"cmt">// Arrays to store the first H1 bar&class="macro">#x27;s open and close prices of the day
class="type">class="kw">double first_h1_price_close[];
class=class="str">"cmt">// Arrays to store H1 bars&class="macro">#x27; open and close prices
class="type">class="kw">double H1_price_close[];
class="type">class="kw">double H1_price_open[];
class=class="str">"cmt">// Strings to define the trading start and end times and the first trade time
class="type">class="kw">string start = "class="num">00:class="num">00";
class="type">class="kw">string end = "class="num">20:class="num">00";
class="type">class="kw">string firsttrade  = "class="num">02:class="num">00";

◍ 把时间窗口和止损位写进 OnTick

EA 的初始化只做一件事:把 MagicNumber 绑给 CTrade 实例,让后续所有订单都能被这套逻辑单独识别,避免和手动单或其他 EA 单混淆。 真正干活在 OnTick。先把字符串形式的起止时间、首单时间用 StringToTime 转成 datetime,再用 TimeCurrent 抓当前服务器时间,时间过滤的骨架就搭好了。 日线数据取前一根的收盘与开盘:CopyClose / CopyOpen 用 PERIOD_D1 偏移 1 取 1 根,随后 ArraySetAsSeries 置为序列排列,保证 daily_close[0] 是最新。H1 方面,首根 H1 收盘按 start_time 定位取 1 根;最近 5 根 H1 的收盘与开盘用偏移 0 取 5 根,同样反转序列方向。 点值用 SymbolInfoDouble(_Symbol, SYMBOL_POINT) 拿。卖单 TP 设在 Bid 减 400 点、SL 在 Bid 加 100 点;买单反之,TP 在 Ask 加 400 点、SL 在 Ask 减 100 点。这组 400/100 的点距是原文固定参数,外汇与贵金属杠杆高,实盘前应在 MT5 策略测试器按品种点值重算,滑点可能吞噬 100 点止损。 若前一根日线收阴,则进入后续的方向判断分支——这部分留待下一节拆解。

MQL5 / C++
class=class="str">"cmt">//| Expert initialization function                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Convert time strings to class="type">class="kw">datetime format
   class="type">class="kw">datetime start_time = StringToTime(start); class=class="str">"cmt">// Convert start time class="type">class="kw">string to class="type">class="kw">datetime
   class="type">class="kw">datetime end_time = StringToTime(end); class=class="str">"cmt">// Convert end time class="type">class="kw">string to class="type">class="kw">datetime
   class="type">class="kw">datetime current_time = TimeCurrent(); class=class="str">"cmt">// Get the current time
   class="type">class="kw">datetime first_tradetime = StringToTime(firsttrade); class=class="str">"cmt">// Convert first trade time class="type">class="kw">string to class="type">class="kw">datetime
class=class="str">"cmt">// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, class="num">1, class="num">1, daily_close); class=class="str">"cmt">// Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, class="num">1, class="num">1, daily_open); class=class="str">"cmt">// Copy the open price of the previous day
class=class="str">"cmt">// Set the arrays to be copied from right to left(latest to oldest)
   ArraySetAsSeries(daily_close, true); class=class="str">"cmt">// Set daily_close array as series
   ArraySetAsSeries(daily_open, true); class=class="str">"cmt">// Set daily_open array as series
class=class="str">"cmt">// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, class="num">1, first_h1_price_close); class=class="str">"cmt">// Copy the close price of the first H1 bar
class=class="str">"cmt">// Copy close prices for the latest class="num">5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, class="num">0, class="num">5, H1_price_close); class=class="str">"cmt">// Copy the close prices of the latest class="num">5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, class="num">0, class="num">5, H1_price_open); class=class="str">"cmt">// Copy the open prices of the latest class="num">5 H1 bars
class=class="str">"cmt">// Set the arrays to be copied from right to left(latest to oldest)
   ArraySetAsSeries(H1_price_close, true); class=class="str">"cmt">// Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); class=class="str">"cmt">// Set H1_price_open array as series
class=class="str">"cmt">// Get the symbol point size
   class="type">class="kw">double symbol_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
class=class="str">"cmt">// Get the current Bid price
   class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
class=class="str">"cmt">// Calculate the stop loss and take profit prices for sell
   class="type">class="kw">double tp_sell = Bid - class="num">400 * symbol_point;
   class="type">class="kw">double sl_sell = Bid + class="num">100 * symbol_point;
class=class="str">"cmt">// Get the current Ask price
   class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
class=class="str">"cmt">// Calculate the stop loss and take profit prices for buy
   class="type">class="kw">double tp_buy = Ask + class="num">400 * symbol_point;
   class="type">class="kw">double sl_buy = Ask - class="num">100 * symbol_point;
class=class="str">"cmt">// If the last daily bar is bearish

常见问题

用 iOpen 分别取 PERIOD_D1 和 PERIOD_H1 的 0 号柱开盘价,存成全局变量,在 OnTick 里先读日线方向再处理 H1,避免每 tick 重算。
日线收盘方向定多空边界,仅当 H1 向日线反方向回抽且未破关键位时才触发;把过滤条件放进独立函数,回测时调阈值看胜率变化。
可以。小布能读取你的策略描述与代码骨架,标出时间周期调用冲突、止损未绑定等常见坑,并给出改法建议,省去手动逐行排查。
调用 trade.Buy(lot, symbol, 0, sl, tp) 这类接口,把 sl 用 ATR 或固定点数算好传入;仓位用宏或输入参数固定,OnTick 只管触发不参与计算。
在 OnTick 用 TimeHour(TimeCurrent()) 判断小时数区间,配合 SymbolInfoInteger 查交易时段,不满足条件直接 return,避免跨时段误触。