如何利用 MQL5 创建简单的多币种智能交易系统(第 1 部分):基于 ADX 指标的信号,并结合抛物线 SAR·进阶篇
「EA 头文件里的全局变量与函数声明骨架」
这段声明块通常出现在 MQL5 多品种 EA 的顶层 include 或主文件开头,先把跨周期、跨品种要复用的变量一次性摊开。 pip、xpip 存点值换算结果,floatprofit 与 fixclprofit 分别记浮动与已锁定的利润;ADXDIp[]、ADXDIm[] 是两个双精度数组,用来接 ADX 指标的 +DI 与 -DI 线,后续判断交叉直接用下标取数。 pairs、hariini、daytrade、trade_mode 是字符串型开关,控制交易品种集合、日内起始、是否日内模式与下单逻辑分支;OPEN[] HIGH[] LOW[] CLOSE[] TIME[] 则是 OHLC 与时间的行情缓存数组,closetime 单独记平仓时刻。 函数声明部分暴露了策略的真实构件:iADXCross 看 ADX 方向线交叉,iADXpct 取某 index 的 ADX 百分比强度,PARSAR05/15/Op 是三个不同周期或启动参数的 SAR 通道判断;LotDig 与 MLots 管手数精度与计算,NonZeroDiv 防除零。 挂单与持仓的止损止盈由 OrderSLSet / OrderTPSet / SetOrderSL / SetOrderTP 按订单或持仓类型分流,TSPrice 做追踪止损价计算,TS_type 区分几种 trailing 逻辑;最后 ReqDate(d,h,m) 把日时分拼成请求用的时间串,方便回测或定时触发。 在 MT5 里把这块直接贴进一个新 include 文件,编译若报 ENUM_ORDER_TYPE 未定义,说明少引了 Trade 相关标准库,补 #include <Trade/Trade.mqh> 即可。外汇与贵金属杠杆高,这类全局变量若在多品种上同时跑,要注意内存与 tick 占用。
pip,
xpip;
class="type">class="kw">double floatprofit,
fixclprofit;
class="type">class="kw">double ADXDIp[];
class="type">class="kw">double ADXDIm[];
class=class="str">"cmt">//--
class="type">class="kw">string pairs,
hariini,
daytrade,
trade_mode;
class=class="str">"cmt">//--
class="type">class="kw">double OPEN[],
HIGH[],
LOW[],
CLOSE[];
class="type">class="kw">datetime TIME[];
class="type">class="kw">datetime closetime;
class=class="str">"cmt">//--
class=class="str">"cmt">//------------
class=class="str">"cmt">//------------
class="type">int iADXCross(const class="type">class="kw">string symbol);
class="type">int iADXpct(const class="type">class="kw">string symbol,const class="type">int index);
class="type">int PARSAR05(const class="type">class="kw">string symbol);
class="type">int PARSAR15(const class="type">class="kw">string symbol);
class="type">int PARSAROp(const class="type">class="kw">string symbol);
class="type">int LotDig(const class="type">class="kw">string symbol);
class=class="str">"cmt">//--
class="type">class="kw">double MLots(const class="type">class="kw">string symbx);
class="type">class="kw">double NonZeroDiv(class="type">class="kw">double val1,class="type">class="kw">double val2);
class="type">class="kw">double OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double SetOrderSL(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double SetOrderTP(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type);
class=class="str">"cmt">//--
class="type">class="kw">string ReqDate(class="type">int d,class="type">int h,class="type">int m);◍ EA 配置结构里的成员布局
这段声明来自一个 ADX+PSAR 复合策略 EA 的类定义,把周期、手句柄、参数数组全部摊在 public 区,方便外部直接读写与调试。 TFt、TFT15、TFT05 三个 ENUM_TIMEFRAMES 变量分别对应主图、15 分钟、5 分钟周期;hParOp、hPar15、hPar05 则是三个周期下 PSAR 指标的句柄数组,若句柄为 INVALID_HANDLE 则指标调用会直接失效。 profitb[] 与 profits[] 是 double 型数组,用来存突破与止损位的缓冲值;Buy、Sell 为 int 计数,记录当前多空挂单或持仓数量。year~sec 这一组 int 只是把 TimeCurrent 拆开存,方便按交易时段过滤。 在 MT5 里把这类结构直接 public 会牺牲封装性,但回测时你可以用 iCustom 或脚本直接打印 expname、slip、SARstep 来确认参数生效,外汇与贵金属波动剧烈,参数误读可能导致滑点远超预期。
class="type">class="kw">string TF2Str(ENUM_TIMEFRAMES period); class="type">class="kw">string timehr(class="type">int hr,class="type">int mn); class="type">class="kw">string TradingDay(class="type">void); class="type">class="kw">string AccountMode(); class="type">class="kw">string GetCommentForOrder(class="type">void) { class="kw">return(expname); } class=class="str">"cmt">//------------ class="kw">public: class=class="str">"cmt">//-- ADXPSAR_MCEA Config -- class="type">class="kw">string DIRI[], AS30[]; class="type">class="kw">string expname; class="type">int handADX[]; class="type">int hParOp[], hPar15[], hPar05[]; class="type">int ALO, dgts, arrsymbx; class="type">int sall, arper; class="type">ulong slip; ENUM_TIMEFRAMES TFt, TFT15, TFT05; class=class="str">"cmt">//-- class="type">class="kw">double SARstep, SARmaxi; class="type">class="kw">double profitb[], profits[]; class=class="str">"cmt">//-- class="type">int Buy, Sell; class="type">int ccur, psec, xtto, checktml; class="type">int OpOr[],xob[],xos[]; class=class="str">"cmt">//-- class="type">int year, class=class="str">"cmt">// Year mon, class=class="str">"cmt">// Month day, class=class="str">"cmt">// Day hour, class=class="str">"cmt">// Hour min, class=class="str">"cmt">// Minutes sec; class=class="str">"cmt">// Seconds
EA 类骨架里的日期字段与交易动作接口
这段 MQL5 代码露出了自定义 EA 类 MCEA 的成员变量与函数声明轮廓。两个整型变量 dow 与 doy 专门承接时间维度:dow 记录星期几(0 为周日,6 为周六),doy 记录年内第几天(1 月 1 日从 0 算起),做跨周期过滤时直接读这两个值比反复调 TimeDayOfWeek 更省。 类里把交易相关动作拆得很碎:OpenBuy / OpenSell 只负责开仓,CloseBuyPositions / CloseSellPositions / CloseAllOrders 分管平仓,ModifySLTP 与 ModifyOrderSLTP 管止损止盈重设。这种拆分让你在 ExpertActionTrade 里能按信号组合调用,而不是写一坨耦合逻辑。 bool 型函数返回执行结果,方便上层判断今天 TradingToday 是否放行、RefreshTick 是否拿到新报价。外汇与贵金属波动剧烈、杠杆高风险,任何自动交易接口在 MT5 策略测试器里跑过历史数据前,都只能当作概率工具,别直接挂真仓。
class="type">int dow, class=class="str">"cmt">// Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) class="type">int doy; class=class="str">"cmt">// Day number of the year(January 1st is assigned the number value of zero) class=class="str">"cmt">//------------ MCEA(class="type">void); ~MCEA(class="type">void); class=class="str">"cmt">//------------ class=class="str">"cmt">//-- class="kw">virtual class="type">void ADXPSAR_MCEA_Config(class="type">void); class="kw">virtual class="type">void ExpertActionTrade(class="type">void); class=class="str">"cmt">//-- class="type">void ArraySymbolResize(class="type">void); class="type">void CurrentSymbolSet(const class="type">class="kw">string symbol); class="type">void Pips(const class="type">class="kw">string symbol); class="type">void TradeInfo(class="type">void); class="type">void Do_Alerts(const class="type">class="kw">string symbx,class="type">class="kw">string msgText); class="type">void CheckOpenPMx(const class="type">class="kw">string symbx); class="type">void SetSLTPOrders(class="type">void); class="type">void CloseBuyPositions(const class="type">class="kw">string symbol); class="type">void CloseSellPositions(const class="type">class="kw">string symbol); class="type">void CloseAllOrders(class="type">void); class="type">void CheckClose(const class="type">class="kw">string symbx); class="type">void TodayOrders(class="type">void); class="type">void UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf); class="type">void RefreshPrice(const class="type">class="kw">string symbx,ENUM_TIMEFRAMES xtf,class="type">int bars); class=class="str">"cmt">//-- class="type">bool RefreshTick(const class="type">class="kw">string symbx); class="type">bool TradingToday(class="type">void); class="type">bool OpenBuy(const class="type">class="kw">string symbol); class="type">bool OpenSell(const class="type">class="kw">string symbol); class="type">bool ModifyOrderSLTP(class="type">class="kw">double mStop,class="type">class="kw">double ordtp); class="type">bool ModifySLTP(const class="type">class="kw">string symbx,class="type">int TS_type); class="type">bool CloseAllProfit(class="type">void);
「多币种EA的初始化与品种注册」
多币种机器人启动靠 OnInit() 完成配置装载,里面只调一句 mc.ADXPSAR_MCEA_Config() 就返回 INIT_SUCCEEDED,真正的品种池在类方法里落地。 配置函数里先声明一个 30 元素的字符串数组 All30[],覆盖 EURUSD、GBPUSD 到 XAUUSD、XAGUSD 等 7 个直盘、21 个交叉盘加黄金白银,把外汇与贵金属放在同一池子里跑。 sall 用 ArraySize(All30) 取到 30,再 ArrayResize(AS30,sall,sall) 把动态数组 AS30 一次性扩到同等长度,后续面板按钮切图、切品种都读这块内存。 类尾巴还挂了 ManualCloseAllProfit、PairsIdxArray、DirectionMove 等 9 个方法声明,负责平仓、索引、弱信号退出等逻辑,初始化阶段不触发,只在 tick 中按需调用。 开 MT5 把 All30 改成你实盘允许交易的 12 对,能明显降算力占用;贵金属波动大,多币种同跑须自担隔夜跳空高风险。
class="type">bool ManualCloseAllProfit(class="type">void); class=class="str">"cmt">//-- class="type">int PairsIdxArray(const class="type">class="kw">string symbol); class="type">int GetOpenPosition(const class="type">class="kw">string symbol); class="type">int DirectionMove(const class="type">class="kw">string symbol); class="type">int GetCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis); class="type">int CheckToCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis); class="type">int ThisTime(const class="type">int reqmode); class=class="str">"cmt">//-- class="type">class="kw">string getUninitReasonText(class="type">int reasonCode); class=class="str">"cmt">//-- class=class="str">"cmt">//------------ class=class="str">"cmt">//--- }; class=class="str">"cmt">//-end class MCEA class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(class="type">void) { class=class="str">"cmt">//--- mc.ADXPSAR_MCEA_Config(); class=class="str">"cmt">//-- class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnInit() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert Configuration | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MCEA::ADXPSAR_MCEA_Config(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//-- Here we will register all the symbols or pairs that will be used on the Multi-Currency Expert Advisor class=class="str">"cmt">//-- class="type">class="kw">string All30[]= {"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY","EURGBP", "EURAUD","EURNZD","EURCAD","EURCHF","EURJPY","GBPAUD","GBPNZD","GBPCAD", "GBPCHF","GBPJPY","AUDNZD","AUDCAD","AUDCHF","AUDJPY","NZDCAD","NZDCHF", "NZDJPY","CADCHF","CADJPY","CHFJPY","XAUUSD","XAGUSD" }; class=class="str">"cmt">// class="num">30 pairs class=class="str">"cmt">//-- sall=ArraySize(All30); ArrayResize(AS30,sall,sall); class=class="str">"cmt">//-- These AS30[] arrays will be used in the symbol list panel and for the buttons to change symbols and charts
◍ 多币种 EA 的初始化与 tick 触发骨架
多币种策略在 MT5 里第一道坎不是信号,而是把市场报价窗口里的品种全部拉进交易上下文。上面这段代码先用 ArrayCopy 把已筛选的 30 个品种名塞进 DIRI[],再跑一个 for 循环对每个 DIRI[x] 调用 SymbolSelect(...,true),否则后续 iADX / iSAR 拿不到句柄。 时间框架不能写死。代码里用 TFs[] 存了 8 个周期:从 PERIOD_H1 到 PERIOD_D1,再用输入的 TimeFrames 整型索引去匹配 TFt。你改 EA 输入里的 TimeFrames=3,实际信号周期就落到了 PERIOD_H4,这种映射方式比硬编码 PERIOD_H4 更方便回测切换。 句柄必须按品种逐个建。循环里给每个品种同时挂了 iADX(当前 TFt)和三个 iSAR(TFt、M15、M5),说明策略可能用多周期 SAR 交叉过滤 ADX 方向。注意 hPar15 / hPar05 用的是独立周期变量,若你只做单周期,可以把这两行删掉省资源。 券商对总持仓品种数有限制。代码用 mc_account.LimitOrders() 和 arrsymbx 取小值赋给 ALO,避免开仓时撞上错误码 10040(TRADE_RETCODE_LIMIT_POSITIONS)。外汇与贵金属保证金交易杠杆高,实盘前务必在策略测试器用「全部品种」模式跑一遍,确认 ALO 逻辑不会在的第 31 个品种上静默跳过。 OnTick 本身极薄,只调了 mc.ExpertActionTrade() 就返回。真正的下单、平仓、风控都封装在那个多币种交易类里,这种结构方便你把配置函数和逐 tick 逻辑解耦,改参数不用动主循环。
ArrayCopy(AS30,All30,class="num">0,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//-- arrsymbx=sall; ArraySymbolResize(); ArrayCopy(DIRI,All30,class="num">0,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//-- The "DIRI[]" array containing the symbol or pair name will be used class=class="str">"cmt">//-- in all trading activities of the multi-currency expert class=class="str">"cmt">//-- class=class="str">"cmt">//-- This function is for Select all symbol in the Market Watch window for(class="type">int x=class="num">0;x<arrsymbx; x++) { SymbolSelect(DIRI[x],true); } pairs="Multi Currency class="num">30 Pairs"; class=class="str">"cmt">//--- class=class="str">"cmt">//-- Here we will provide a Period Timeframe value which will be used for signal calculations according class=class="str">"cmt">//-- to the Timeframe option on the expert class="kw">input class="kw">property. ENUM_TIMEFRAMES TFs[]= {PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1}; class="type">int arTFs=ArraySize(TFs); for(class="type">int x=class="num">0; x<arTFs; x++) { if(x==TimeFrames) { TFt=TFs[x]; break; } } class=class="str">"cmt">//-- class=class="str">"cmt">//-- Indicators handle for all symbol for(class="type">int x=class="num">0; x<arrsymbx; x++) { handADX[x]=iADX(DIRI[x],TFt,ADXPeriod); class=class="str">"cmt">//-- Handle for the iADX indicator according to the selected Timeframe hParOp[x]=iSAR(DIRI[x],TFt,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator according to the selected Timeframe hPar15[x]=iSAR(DIRI[x],TFT15,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M15 Timeframe hPar05[x]=iSAR(DIRI[x],TFT05,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M5 Timeframe } class=class="str">"cmt">//-- class=class="str">"cmt">//-- Since this expert advisor is a multi-currency expert, we must check the maximum number class=class="str">"cmt">//-- of account limit orders allowed by the broker. class=class="str">"cmt">//-- This needs to be checked, so that when the expert opens an order there will be class=class="str">"cmt">//-- no class="kw">return codes of the trade server error class="num">10040 = TRADE_RETCODE_LIMIT_POSITIONS ALO=(class="type">int)mc_account.LimitOrders()>arrsymbx ? arrsymbx : (class="type">int)mc_account.LimitOrders(); class=class="str">"cmt">//-- class=class="str">"cmt">//-- The LotPS variable will later be used for the proportional distribution of Lot sizes for each symbol LotPS=(class="type">class="kw">double)ALO; class=class="str">"cmt">//-- mc_trade.SetExpertMagicNumber(magicEA); class=class="str">"cmt">//-- Set Magic Number as expert ID mc_trade.SetDeviationInPoints(slip); class=class="str">"cmt">//-- Set expert deviation with slip variable value mc_trade.SetMarginMode(); class=class="str">"cmt">//-- Set the Margin Mode expert to the value of Account Margin Mode class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ADXPSAR_MCEA_Config() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(class="type">void) { class=class="str">"cmt">//--- mc.ExpertActionTrade(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnTick()
多币种EA的5秒轮询与终端校验
多币种EA在MT5里跑,第一步不是算信号,而是确认终端允许算法交易。代码里用 MQLInfoInteger(MQL_TRADE_ALLOWED) 做闸门,若禁止且标记位 checktml 为0,就弹一次 alert 并 return,避免每 tick 刷屏报警。 不同品种 SymbolInfoTick 的报价频率不一致,若每个 tick 都全市场扫信号,CPU 会被拖死。原文把轮询降频到每 5 秒一次:用 fmod((double)mcsec,5.0)==0 判定秒数走到 5 的整数倍,再把当前秒存进 mc.ccur,只有 mc.ccur!=mc.psec 才进交易逻辑。 进入逻辑后是一个 for 循环遍历 mc.arrsymbx 个品种,配合 !IsStopped() 防止 EA 被手动停止时还发单。外汇与贵金属多币种自动化波动剧烈、滑点扩大,实盘前务必在策略测试器用真实点差回测,并确认「允许算法交易」已勾选,否则 EA 可能静默不交易。
class="type">void MCEA::ExpertActionTrade(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//Check Trading Terminal ResetLastError(); class=class="str">"cmt">//-- if(!MQLInfoInteger(MQL_TRADE_ALLOWED) && mc.checktml==class="num">0) class=class="str">"cmt">//-- Check whether MT5 Algorithmic trading is Allow or Prohibit { mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting."); mc.checktml=class="num">1; class=class="str">"cmt">//-- Variable checktml is given a value of class="num">1, so that the alert is only done once. class="kw">return; } class=class="str">"cmt">//-- if(!DisplayManualButton("M","C","R")) DisplayManualButton(); class=class="str">"cmt">//-- Show the expert manual button panel class=class="str">"cmt">//-- class=class="str">"cmt">//-- The functions below will be displayed on the expert chart according to class=class="str">"cmt">//-- the Select Display Trading Info on Chart(Yes) or(No) option on expert class="kw">property. class=class="str">"cmt">//-- if(trade_info_display==Yes) mc.TradeInfo(); class=class="str">"cmt">//-- Displayed Trading Info on Chart class=class="str">"cmt">//-- class=class="str">"cmt">//-- if(trade_info_display==Yes) mc.TradeInfo(); class=class="str">"cmt">//-- Displayed Trading Info on Chart class=class="str">"cmt">//--- class=class="str">"cmt">//-- class=class="str">"cmt">//-- Because the current prices of a specified symbol(SymbolInfoTick) will occur differently class=class="str">"cmt">//-- for each symbol, we reduce the tick update frequency to only every class="num">5 seconds. class=class="str">"cmt">//-- So, looping to check the signal for all trading activity of all symbols will only be done every class="num">5 seconds. class=class="str">"cmt">//-- class="type">int mcsec=mc.ThisTime(mc.sec); class=class="str">"cmt">//-- With the function ThisTime(mc.sec), we will retrieve the current seconds value to mcsec variable. class=class="str">"cmt">//-- class=class="str">"cmt">//-- MathMod is a formula that gives the(modulus) real remainder after the division of two numbers. class=class="str">"cmt">//-- By dividing the value of seconds with the value of class="num">5.0, if the result is class="num">0, it means that class="num">5 seconds class=class="str">"cmt">//-- have been reached from the previous psec variable seconds value. class=class="str">"cmt">//-- if(fmod((class="type">class="kw">double)mcsec,class="num">5.0)==class="num">0) mc.ccur=mcsec; class=class="str">"cmt">//-- if(mc.ccur!=mc.psec) class=class="str">"cmt">//-- So, if the seconds value in the ccur variable is not the same as the psec variable value { class=class="str">"cmt">//-- (then the psec variable value already class="num">5 seconds before) class="type">class="kw">string symbol; class=class="str">"cmt">//-- Here we start with the rotation of the name of all symbol or pairs to be traded class=class="str">"cmt">//-- This is the basic framework for the automated trading workflow of this Multi-Currency Expert Advisor class=class="str">"cmt">//-- Here we start with the rotation of the name of all symbol or pairs to be traded class=class="str">"cmt">//-- This is the basic framework for the automated trading workflow of this Multi-Currency Expert Advisor for(class="type">int x=class="num">0; x<mc.arrsymbx && !IsStopped(); x++) { class=class="str">"cmt">//-- if(mc.DIRI[x]==Symbol())