创建一个在若干工具上交易的 EA 交易程序·进阶篇
「TEMA 斜率反转如何变成进出场开关」
这段逻辑把三重指数均线(TEMA)的逐根斜率变化,直接映射成多空触发与平仓信号,核心在比较最近三根 TEMA 的差值。 先把 TEMA 数组设为时间序列(最新值在下标 0),再用 iTEMA 拿句柄;若当前 Bars 少于 MinBars[Number] 直接 return(true) 跳过,避免样本不足导致误判。 只在 IsNewBar 或 Recount 为真时重算:将缓冲区 0 从偏移 0 复制 4 根到 TEMA 数组,复制失败置 Recount=true 并返回 false。复制成功后取 Digits_ = 报价小数位+4,把 TEMA[2]-TEMA[3] 记为 dtema2、TEMA[1]-TEMA[2] 记为 dtema1,均做 NormalizeDouble 精度对齐。 信号判定很直白:dtema2>0 且 dtema1<0 置 DnSignal(上涨斜率转跌,倾向开空);dtema2<0 且 dtema1>0 置 UpSignal(下跌斜率转涨,倾向开多)。平仓侧则是 dtema1>0 置 DnStop、dtema1<0 置 UpStop——即斜率方向延续就平掉反向仓。 外汇与贵金属杠杆高,斜率反转只是概率信号,实盘前务必在 MT5 用 EURUSD 15M 跑一遍看信号频率。
ArraySetAsSeries(TEMA,true); class=class="str">"cmt">//--- 取得指标句柄 Handle[Number]=iTEMA(Symbol_,class="num">0,period,class="num">0,ApPrice); } class=class="str">"cmt">//--- 检查柱数是否够用于计算 if(Bars(Symbol_,class="num">0)<MinBars[Number])class="kw">return(true); class=class="str">"cmt">//--- 取得交易信号 if(IsNewBar(Number,Symbol_,class="num">0) || Recount[Number]) class=class="str">"cmt">// 在柱有改变或者复制数据失败时进入此区块 { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; class=class="str">"cmt">//--- 使用指标句柄把指标 class=class="str">"cmt">//--- 缓冲区数值复制到特别准备的静态数组中 if(CopyBuffer(Handle[Number],class="num">0,class="num">0,class="num">4,TEMA)<class="num">0) { Recount[Number]=true; class=class="str">"cmt">//由于没有收到数据,我们应该返回 class=class="str">"cmt">// 进入此区块(当收到交易信号时)在下一个订单号! class="kw">return(false); class=class="str">"cmt">// 没有收到交易信号则退出TradeSignalCounter() 函数 } class=class="str">"cmt">//--- 所有从指标缓冲区复制操作成功完成 Recount[Number]=false; class=class="str">"cmt">// 我们可以直到下一个柱的改变时再回到此区块 class="type">int Digits_ = class="type">int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+class="num">4); dtema2 = NormalizeDouble(TEMA[class="num">2] - TEMA[class="num">3], Digits_); dtema1 = NormalizeDouble(TEMA[class="num">1] - TEMA[class="num">2], Digits_); class=class="str">"cmt">//---- 决定输入信号 if(dtema2 > class="num">0 && dtema1 < class="num">0) DnSignal[Number] = true; if(dtema2 < class="num">0 && dtema1 > class="num">0) UpSignal[Number] = true; class=class="str">"cmt">//---- 决定输出信号 if(dtema1 > class="num">0) DnStop[Number] = true; if(dtema1 < class="num">0) UpStop[Number] = true; } class=class="str">"cmt">//----+ class="kw">return(true); } class="type">bool TradePerformer(class="type">int Number, class="type">class="kw">string Symbol_, class="type">bool Trade, class="type">int StLoss, class="type">int TkProfit, class="type">class="kw">double Lots, class="type">int Slippage, class="type">bool &UpSignal[], class="type">bool &DnSignal[],
◍ 信号触发后的持仓翻转逻辑
这段函数负责在每根 K 线信号判定后执行实际动作:先检查全局 Trade 开关,若禁止交易直接返回 true 不做任何操作。接着根据 UpStop / DnStop 数组状态平掉对应方向持仓,再判断 UpSignal / DnSignal 是否触发新单。 新单成交后立刻把对应信号位复位成 false,保证同一根柱上不会重复开仓——这是避免 MT5 回测里一根 bar 反复刷单的关键细节。外汇与贵金属杠杆高,这类重复触发会让滑点损耗成倍放大。 下方 BuyPositionClose 展示了平仓请求的组装方式:用 PositionSelect 确认品种有买单,否则返回 false;request.type 设为 ORDER_TYPE_SELL 以市价反手,type_filling 用 ORDER_FILLING_FOK 要求全部成交或全不成交。deviation 直接透传,实盘里建议按点差波动设 10~30 点而非 ULONG_MAX。 代码里用 Print 把平仓动作打到日志,方便你开 MT5 后对照「专家」标签页看每笔平单是否按预期触发。复制下面这段到 EA 里,把 Symbol_ 换成自己盯的 XAUUSD 或 EURUSD 就能跑。
class="type">bool &UpStop[], class="type">bool &DnStop[]) { class=class="str">"cmt">//--- 检查交易是否被禁止 if(!Trade)class="kw">return(true); class=class="str">"cmt">//--- 平掉持仓 if(UpStop[Number])BuyPositionClose(Symbol_,Slippage); if(DnStop[Number])SellPositionClose(Symbol_,Slippage); class=class="str">"cmt">//--- 建新仓位 if(UpSignal[Number]) if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) UpSignal[Number]=false; class=class="str">"cmt">//这个柱上不会有更多此交易信号! class=class="str">"cmt">//--- if(DnSignal[Number]) if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) DnSignal[Number]=false; class=class="str">"cmt">//这个柱上不会有更多此交易信号! class=class="str">"cmt">//--- class="kw">return(true); } BuyPositionClose(); SellPositionClose(); BuyPositionOpen(); SellPositionOpen(); class="type">bool BuyPositionClose(const class="type">class="kw">string symbol,class="type">class="kw">ulong deviation) { class=class="str">"cmt">//--- 声明交易请求结构和交易结果 class="type">MqlTradeRequest request; class="type">MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- 检查是否有买入持仓 if(PositionSelect(symbol)) { if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) class="kw">return(false); } else class="kw">return(false); class=class="str">"cmt">//--- 初始化 class="type">MqlTradeRequest 结构用于平掉买入持仓 request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(symbol, SYMBOL_BID); request.action = TRADE_ACTION_DEAL; request.symbol = symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.sl = class="num">0.0; request.tp = class="num">0.0; request.deviation=(deviation==ULONG_MAX) ? deviation : deviation; request.type_filling=ORDER_FILLING_FOK; class=class="str">"cmt">//--- class="type">class="kw">string word=""; StringConcatenate(word, "<<< ============ BuyPositionClose(): 平买入持仓,交易品种 ", symbol," ============ >>>"); Print(word); class=class="str">"cmt">//--- 发送订单平仓交易服务器 if(!OrderSend(request,result)) { Print(ResultRetcodeDescription(result.retcode)); class="kw">return(false); } class=class="str">"cmt">//----+ class="kw">return(true); } class="type">bool IsNewBar(class="type">int Number, class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe); class="type">class="kw">string ResultRetcodeDescription(class="type">int retcode);
跨品种共振怎么喂给EA
把不同金融资产的关联搬进交易系统,核心不是新理论,而是把趋势分析从单标的扩到一组关联标的。以 EUR/USD 为主资产时,同时取 EUR/JPY 与 USD/JPY 上同一指标的读数,用相同变化区间做规范化,才方便横向加减和比较。 随机动量指标(Stoh)和它的信号线 Sign 的差值 dStoh 被用来判方向。若 EUR/JPY 与 USD/JPY 的 dStoh 异号,就求和:和大于 0 视为正向共振,否则负向。主资产无信号、其余两标的 dStoh 合计小于 0 时,平掉已有多仓——这是退出逻辑里最容易被漏掉的一条。 工程上更干净的做法是把分析全塞进多货币指标,EA 只从指标缓存拿现成信号。MultiStochastic.mq5 用绿柱表示做多、红柱做空,图顶的粉点/浅绿点分别是多空退出提示;嫌画图缓存拖慢,就砍到 MultiStochastic_Exp.mq5 只留信号部分。 下面这段 OnTick 只跑三个品种,结构极简:三次 TradeSignalCounter 填信号数组,再三次 TradePerformer 下单。注意 TradeSignalCounter 里对三个时间序列最少柱数做了校验,并用 SynchroCheck 保证各序列柱同步切换——外汇与贵金属跨品种共振受点差和跳空影响,实盘前务必在 MT5 策略测试器用 2001 年后的历史数据核对同步逻辑。
class="type">void OnTick() { class=class="str">"cmt">//--- 声明交易信号的变量数组 class="kw">static class="type">bool UpSignal[], DnSignal[], UpStop[], DnStop[]; class=class="str">"cmt">//--- 取得交易信号 TradeSignalCounter(class="num">0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(class="num">1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(class="num">2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop); class=class="str">"cmt">//--- 进行交易操作 TradePerformer( class="num">0, SymbolA0, Trade0, StopLoss0, class="num">0, Lots0, Slippage0, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( class="num">1, SymbolA1, Trade1, StopLoss1, class="num">0, Lots1, Slippage1, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( class="num">2, SymbolA2, Trade2, StopLoss2, class="num">0, Lots2, Slippage2, UpSignal, DnSignal, UpStop, DnStop); class=class="str">"cmt">//--- } class="type">bool TradeSignalCounter(class="type">int Number, class="type">bool Trade, class="type">int Kperiod, class="type">int Dperiod, class="type">int slowing, ENUM_MA_METHOD ma_method, ENUM_STO_PRICE price_, class="type">class="kw">string SymbolA, class="type">class="kw">string SymbolB, class="type">class="kw">string SymbolC, class="type">bool &UpSignal[], class="type">bool &DnSignal[], class="type">bool &UpStop[], class="type">bool &DnStop[]) { class=class="str">"cmt">//--- 检查交易是否被禁止 if(!Trade)class="kw">return(true);
「多周期随机指标的句柄与数组预扩容」
在 MT5 里用 iCustom 挂载自定义多货币随机指标(如 MultiStochastic_Exp)时,句柄和配套数组必须用 static 存住,否则每次调用函数都会重复申请句柄,造成资源泄漏与计算错位。下面这段逻辑先用 Size_ 记录已分配长度,只有 Number+1 超过当前尺寸才做 ArrayResize,把 Handle、Recount、MinBars 等七个数组一次性扩到目标大小。 扩容失败的分支很关键:只要任意一个 ArrayResize 返回 -1,就拼出错描述并取 GetLastError(),若错误码大于 4000 才 Print 出来,同时把 Size_ 置为 -2 并返回 false,避免后续空句柄跑信号。成功时则把 MinBars[Number] 设为 Kperiod + Dperiod + slowing,这是该随机指标最少需要的柱数。 句柄申请那行把三个货币对 SymbolA/B/C 和均线参数一并传进去,说明它是跨品种共振版随机指标。之后有两道闸门:Rates_Total 小于 MinBars[Number] 或 SynchroCheck 不通过都直接 return(true),即暂不计算,等数据齐了再说。外汇与贵金属跨品种策略对时间同步极敏感,不同服务器点差与停盘会让柱不对齐,这类检查不可省,否则信号可能失真。 真正取信号前还有一道 IsNewBar 或 Recount 判定:不是新柱且不强制重算就跳过,并把四类信号数组(Dn/Up Signal、Dn/Up Stop)先清零,防止上一根柱的状态污染当前判断。
class=class="str">"cmt">//--- 声明变量用于保存变量数组的大小 class="kw">static class="type">int Size_=class="num">0; class=class="str">"cmt">//--- 声明数组用于以静态变量形式保存指标句柄 class="kw">static class="type">int Handle[]; class="kw">static class="type">int Recount[],MinBars[]; class=class="str">"cmt">//--- class="type">class="kw">double dUpSignal_[class="num">1],dDnSignal_[class="num">1],dUpStop_[class="num">1],dDnStop_[class="num">1]; class=class="str">"cmt">//--- 改变变量数组大小 if(Number+class="num">1>Size_) { class="type">uint size=Number+class="num">1; class=class="str">"cmt">//---- if(ArrayResize(Handle,size)==-class="num">1 || ArrayResize(Recount,size)==-class="num">1 || ArrayResize(UpSignal, size) == -class="num">1 || ArrayResize(DnSignal, size) == -class="num">1 || ArrayResize(UpStop, size) == -class="num">1 || ArrayResize(DnStop, size) == -class="num">1 || ArrayResize(MinBars,size) == -class="num">1) { class="type">class="kw">string word=""; StringConcatenate(word,"TradeSignalCounter( ",Number, " ): 错误!!!无法改变变量数组大小!!!"); class="type">int error=GetLastError(); ResetLastError(); class=class="str">"cmt">//--- if(error>class="num">4000) { StringConcatenate(word,"TradeSignalCounter( ",Number," ): Error code ",error); Print(word); } Size_=-class="num">2; class="kw">return(false); } Size_=class="type">int(size); Recount[Number] = false; MinBars[Number] = Kperiod + Dperiod + slowing; class=class="str">"cmt">//--- 取得指标句柄 Handle[Number]=iCustom(SymbolA,class="num">0,"MultiStochastic_Exp", Kperiod,Dperiod,slowing,ma_method,price_, SymbolA,SymbolB,SymbolC); } class=class="str">"cmt">//--- 检查柱数是否够用于计算 if(Rates_Total(SymbolA,SymbolB,SymbolC)<MinBars[Number])class="kw">return(true); class=class="str">"cmt">//--- 检查时间序列同步 if(!SynchroCheck(SymbolA,SymbolB,SymbolC))class="kw">return(true); class=class="str">"cmt">//--- 取得交易信号 if(IsNewBar(Number,SymbolA,class="num">0) || Recount[Number]) { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; class=class="str">"cmt">//--- 使用指标句柄,把指标 class=class="str">"cmt">//--- 缓冲区值复制到特别准备的静态数组
◍ 缓冲区取数失败就重算这一手
这段逻辑干的事很直接:从指标句柄里把 4 个缓冲区各取 1 根bar的数据,分别是下行信号、上行信号、下行止损、上行止损,索引 1 到 4 对应这四个通道。 只要任意一个 CopyBuffer 返回小于 0,就说明这次拷贝没成功,立刻把 Recount[Number] 置 true 并 return(false),放弃本次计算,等下一轮再试。外汇和贵金属行情跳空时这种失败概率会抬升,属于高风险环境下的正常容错。 拷贝成功后,代码把数值等于 300 的缓冲值映射成对应的布尔交易命令:dDnSignal_[0]==300 触发 DnSignal,上行和止损逻辑同理。300 是这个指标自定义的信号魔法数,开 MT5 把指标挂上就能在数据窗口里核对。 最后把 Recount[Number] 清为 false,意味着在当前 bar 走完前不必再进这块重算,省 CPU 也避免重复发指令。
if(CopyBuffer(Handle[Number], class="num">1, class="num">1, class="num">1, dDnSignal_) < class="num">0){Recount[Number] = true; class="kw">return(false);} if(CopyBuffer(Handle[Number], class="num">2, class="num">1, class="num">1, dUpSignal_) < class="num">0){Recount[Number] = true; class="kw">return(false);} if(CopyBuffer(Handle[Number], class="num">3, class="num">1, class="num">1, dDnStop_ ) < class="num">0){Recount[Number] = true; class="kw">return(false);} if(CopyBuffer(Handle[Number], class="num">4, class="num">1, class="num">1, dUpStop_ ) < class="num">0){Recount[Number] = true; class="kw">return(false);} class=class="str">"cmt">//--- 把获得的数值转换为交易命令的逻辑变量值 if(dDnSignal_[class="num">0] == class="num">300)DnSignal[Number] = true; if(dUpSignal_[class="num">0] == class="num">300)UpSignal[Number] = true; if(dDnStop_ [class="num">0] == class="num">300)DnStop [Number] = true; if(dUpStop_ [class="num">0] == class="num">300)UpStop [Number] = true; class=class="str">"cmt">//--- 所有从指标缓冲区复制的操作都已成功完成 class=class="str">"cmt">//--- 直到下次柱改变之前都没有必要回到此区块了 Recount[Number]=false; } class=class="str">"cmt">//----+ class="kw">return(true); }
多货币EA的代码本质与一处类型瑕疵
多货币 EA 在 MQL5 里的代码骨架和单品种 EA 高度相似,差异主要在跨品种循环与全局数组的组织方式,而非语言机制本身。 附件里 exp_tema.mq5 体积 45.47 KB、multistochastic.mq5 仅 29.54 KB,说明多货币逻辑不一定更臃肿,关键看指标调用是否被压缩。 有读者在 EURUSD M1 加载 exp_tema 时报“无法加载三重指数移动平均线指标 [4302]”,这类报错多源于自定义指标未先编译进终端,而非 EA 算法问题。 作者本人确认 exp_tema 里 Recount[] 声明为 int 却存 bool 值,不会影响结果,但本应是逻辑静态变量——这种写法在复制代码时容易误导。 下方片段展示了信号触发与 Recount 标记的写法,注意 volume 写死 0.1 仅作示例,实盘需按仓位规则替换。 外汇与贵金属杠杆高,跨品种同时持仓会放大回撤,任何多货币策略都先在 MT5 策略测试器跑历史再上模拟。
if (dtema2 > class="num">0 && dtema1 < class="num">0) { DnSignal[Number] = true; volume = class="num">0.1; } class=class="str">"cmt">// 如果 if 函数为真,买入和卖出的交易量将为 class="num">0.1 class="kw">static class="type">int Recount[], MinBars[]; Recount[Number] = true; ... Recount[Number] = false;