MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·进阶篇
◍ 日线和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] 打出来,就能确认「昨日开盘/收盘」是否如预期到位。
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 数据。
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 实际成交价偏离,任何限仓逻辑都只是降低过度交易概率,不保证亏损可控。
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 点止损。 若前一根日线收阴,则进入后续的方向判断分支——这部分留待下一节拆解。
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