重新审视一种旧时的趋势交易策略:两个随机振荡指标,一个移动平均指标和斐波那契线·进阶篇
📘

重新审视一种旧时的趋势交易策略:两个随机振荡指标,一个移动平均指标和斐波那契线·进阶篇

第 2/2 篇

EA 初始化与指标句柄的落地写法

在 MT5 的 OnInit 里先把要用的指标句柄建好,后面 OnTick 才能稳定取值。下面这段同时挂了两个随机振荡句柄和一个移动平均句柄,随机振荡用 MODE_SMA 做慢线平滑、STO_LOWHIGH 取高低价区间,MA 则靠外部输入的周期、偏移、方法和应用价格决定形态。 [CODE] void OnInit() { Stochastic_handle1 = iStochastic(_Symbol, PERIOD_CURRENT, Signal_0_Stoch_PeriodK, Signal_0_Stoch_PeriodD, Signal_0_Stoch_PeriodSlow, MODE_SMA, STO_LOWHIGH); Stochastic_handle2 = iStochastic(_Symbol, PERIOD_CURRENT, Signal_1_Stoch_PeriodK, Signal_1_Stoch_PeriodD, Signal_1_Stoch_PeriodSlow, MODE_SMA, STO_LOWHIGH); //--- create handle of the indicator iMA handle_iMA=iMA(_Symbol,PERIOD_CURRENT,Inp_MA_ma_period,Inp_MA_ma_shift, Inp_MA_ma_method,Inp_MA_applied_price); //Test Alert("Expert Advisor has been launched"); } MqlTick tick; SymbolInfoTick(_Symbol,tick); int Highest = iHighest(Symbol(),my_timeframe,MODE_CLOSE,shift,1); double High0=iHigh(Symbol(),my_timeframe,0); int Lowest = iLowest(Symbol(),my_timeframe,MODE_CLOSE,shift,1); double Low0=iLow(Symbol(),my_timeframe,0); double highestValue = iHigh(Symbol(),my_timeframe,Highest); double lowestValue = iLow(Symbol(),my_timeframe,Lowest); // Obtener el valor más alto y más bajo de la barra actual double currentHigh = High0; double currentLow = Low0; // Obtener el valor más alto y más bajo de la barra anterior double previousHigh = highestValue; double previousLow = lowestValue; double level0s = currentHigh; double level1s = currentHigh - (currentHigh - previousLow) * 0.236; double level2s = currentHigh - (currentHigh - previousLow) * 0.382; double level3s = currentHigh - (currentHigh - previousLow) * 0.618; double level4s = previousLow; double level0b = currentLow; double level1b = currentLow + (-currentLow + previousHigh) * 0.236; double level2b = currentLow + (-currentLow + previousHigh) * 0.382; double level3b = currentLow + (-currentLow + previousHigh) * 0.618; double level4b = previousHigh; //--- double array_ma[]; [/CODE] 逐行拆一下关键点:前两句 iStochastic 给两个随机指标建句柄,参数里的 PeriodK/PeriodD/PeriodSlow 控制 %K、%D 和慢线周期;iMA 那行把 MA 句柄交给 handle_iMA,Inp_MA_ma_period 若为 20 就是 20 根 K 线的均线。Alert 只是启动确认,实盘里可删。 MqlTick tick 配合 SymbolInfoTick(_Symbol,tick) 抓当前分时买卖价,OnTick 里每次刷新就能拿到实时 bid/ask。iHighest / iLowest 用 MODE_CLOSE 和 shift 回溯 N 根 K 线找极值,High0、Low0 则是 0 号 K 线(当前未收线烛形)的高低价。 斐波那契那段直接用 currentHigh 减 previousLow 乘 0.236、0.382、0.618 算出回调位,previousLow 来自 iLowest 的返回值。外汇和贵金属波动大、滑点频繁,这类回溯极值在跳空时可能偏离预期,回测和实盘都建议先开 MT5 用 EURUSD 的 M15 跑一遍验证。 array_ma 后面要接 ArraySetAsSeries 转序列再 iGetArray 读值,否则取到的索引方向和图表 K 线顺序是反的。句柄建好不等于能直接用,OnInit 里所有外部参数必须已声明,否则编译期就报错。

MQL5 / C++
class="type">void OnInit()
  {
  Stochastic_handle1 = iStochastic(_Symbol, PERIOD_CURRENT, Signal_0_Stoch_PeriodK, Signal_0_Stoch_PeriodD, Signal_0_Stoch_PeriodSlow, MODE_SMA, STO_LOWHIGH);
  Stochastic_handle2 = iStochastic(_Symbol, PERIOD_CURRENT, Signal_1_Stoch_PeriodK, Signal_1_Stoch_PeriodD, Signal_1_Stoch_PeriodSlow, MODE_SMA, STO_LOWHIGH);
class=class="str">"cmt">//--- create handle of the indicator iMA
  handle_iMA=iMA(_Symbol,PERIOD_CURRENT,Inp_MA_ma_period,Inp_MA_ma_shift,
                Inp_MA_ma_method,Inp_MA_applied_price);
class=class="str">"cmt">//Test
  Alert("Expert Advisor has been launched");
  }
  class="type">MqlTick tick;
  SymbolInfoTick(_Symbol,tick);
class="type">int Highest = iHighest(Symbol(),my_timeframe,MODE_CLOSE,shift,class="num">1);
  class="type">class="kw">double High0=iHigh(Symbol(),my_timeframe,class="num">0);
  class="type">int Lowest = iLowest(Symbol(),my_timeframe,MODE_CLOSE,shift,class="num">1);
  class="type">class="kw">double Low0=iLow(Symbol(),my_timeframe,class="num">0);
  class="type">class="kw">double highestValue = iHigh(Symbol(),my_timeframe,Highest);
  class="type">class="kw">double lowestValue = iLow(Symbol(),my_timeframe,Lowest);
class=class="str">"cmt">// Obtener el valor más alto y más bajo de la barra actual
  class="type">class="kw">double currentHigh = High0;
  class="type">class="kw">double currentLow = Low0;
class=class="str">"cmt">// Obtener el valor más alto y más bajo de la barra anterior
  class="type">class="kw">double previousHigh = highestValue;
  class="type">class="kw">double previousLow = lowestValue;
  class="type">class="kw">double level0s = currentHigh;
  class="type">class="kw">double level1s = currentHigh - (currentHigh - previousLow) * class="num">0.236;
  class="type">class="kw">double level2s = currentHigh - (currentHigh - previousLow) * class="num">0.382;
  class="type">class="kw">double level3s = currentHigh - (currentHigh - previousLow) * class="num">0.618;
  class="type">class="kw">double level4s = previousLow;
  class="type">class="kw">double level0b = currentLow;
  class="type">class="kw">double level1b = currentLow + (-currentLow + previousHigh) * class="num">0.236;
  class="type">class="kw">double level2b = currentLow + (-currentLow + previousHigh) * class="num">0.382;
  class="type">class="kw">double level3b = currentLow + (-currentLow + previousHigh) * class="num">0.618;
  class="type">class="kw">double level4b = previousHigh;
class=class="str">"cmt">//---
  class="type">class="kw">double array_ma[];

「把指标缓冲拉进EA的正确姿势」

想让EA直接读iMA的数值,先得把接收数组设成时间序列方向:ArraySetAsSeries(array_ma,true)之后,索引0就是最新一根K线。示例里只取3根(start_pos=0, count=3),足够做短周期斜率判断,不必每次拉整段历史。 iGetArray这个封装函数干了两件容易漏的事:先用ArrayIsDynamic拦住静态数组,再ArrayFree清空旧数据,否则重复调用可能把上次的尾巴带进来。CopyBuffer返回值必须严格等于count,不等就打印错误码并return false——这意味着指标还没算好,EA这帧直接跳过,不拿半成品下单。 取完值用Digits()+1做精度格式化塞进Comment,能在图表左上角直接看到0/1/2三根MA报价,省去开数据窗口。外汇和贵金属波动快,这类实时读数误差可能随点差放大,验证时建议先开模拟盘跑。 下单分支里trade.Buy失败会吐ResultRetcodeDescription,成功也打日志;把return code留痕,后面排查滑点或未成交比看图表直观。rates[1].close > array_ma22[0]这种跨数组比较,前提是你另一个MA数组也按时间序列取数,否则索引对不上会给出反向信号。

MQL5 / C++
ArraySetAsSeries(array_ma,true);
class="type">int start_pos=class="num">0,count=class="num">3;
if(!iGetArray(handle_iMA,class="num">0,start_pos,count,array_ma))
   class="kw">return;
class="type">bool iGetArray(class="kw">const class="type">int handle,class="kw">const class="type">int buffer,class="kw">const class="type">int start_pos,
               class="kw">const class="type">int count,class="type">class="kw">double &arr_buffer[])
  {
   class="type">bool result=true;
   if(!ArrayIsDynamic(arr_buffer))
     {
      class=class="str">"cmt">//if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, this a no dynamic array!",__FILE__,__FUNCTION__);
      class="kw">return(false);
     }
   ArrayFree(arr_buffer);
class=class="str">"cmt">//--- reset error code
   ResetLastError();
class=class="str">"cmt">//--- fill a part of the iBands array with values from the indicator buffer
   class="type">int copied=CopyBuffer(handle,buffer,start_pos,count,arr_buffer);
   if(copied!=count)
     {
      class=class="str">"cmt">//--- if the copying fails, tell the error code
      class=class="str">"cmt">//if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, amount to copy: %d, copied: %d, error code %d",
                   __FILE__,__FUNCTION__,count,copied,GetLastError());
      class=class="str">"cmt">//--- quit with zero result - it means that the indicator is considered as not calculated
      class="kw">return(false);
     }
   class="kw">return(result);
  }
   class="type">class="kw">string text="";
   for(class="type">int i=class="num">0; i<count; i++)
      text=text+IntegerToString(i)+": "+DoubleToString(array_ma[i],Digits()+class="num">1)+"\n";
class=class="str">"cmt">//---
   Comment(text);
   class="type">int total = PositionsTotal();
   if(total==class="num">0)
{
...code
}
   if(total>class="num">0)
{
...code
}
if(!trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,newStopLossLevelb1,newTakeProfitLevelb2))
                      {
                       class=class="str">"cmt">//--- failure message
                       Print("Buy() method failed. Return code=",trade.ResultRetcode(),
                             ". Code description: ",trade.ResultRetcodeDescription());
                      }
                    else
                      {
                       Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(),
                             " (",trade.ResultRetcodeDescription(),")");
                      }
if(rates[class="num">1].close >array_ma22[class="num">0])
     {

◍ 用 TRADE_ACTION_SLTP 改持仓止损止盈

在 MT5 EA 里改已有仓的 SL/TP,不要去动仓位方向,走 TRADE_ACTION_SLTP 是最干净的做法。上面的片段先判断是否能平仓,若 trade.PositionClose(_Symbol,5) 返回异常就打印 "cerro" 并直接 return,否则才进入修改逻辑。 修改前先把 request 结构体填实:action 设成 TRADE_ACTION_SLTP,position 填持仓 ticket,symbol 填持仓品种,sl 和 tp 换成你算好的新价位,magic 沿用开仓时的 Expert_MagicNumber。注意 sl/tp 变量名在片段里是 newStopLossLevelss 和 newTakeProfitLevelss,实际写代码别照抄拼写,用你自己的计算值。 填完用 OrderSend(request,result) 发出。失败就 PrintFormat("OrderSend error %d",GetLastError()) 把错误码打出来;成功则打印 retcode、deal、order 三个字段,方便在专家日志里核对是否真的改单成功。外汇和贵金属杠杆高,改 SL/TP 涉及实时风险,任何自动修改都可能因点差或报价延迟失败,请在策略测试器先跑一遍再上真实账户。

MQL5 / C++
trade.PositionClose(_Symbol,class="num">5);
Print("cerro");
class="kw">return;
     }
    else
      class="kw">return;
   }
         class=class="str">"cmt">//--- setting the operation parameters
         request.action   =TRADE_ACTION_SLTP; class=class="str">"cmt">// type of trade operation
         request.position=position_ticket;   class=class="str">"cmt">// ticket of the position
         request.symbol=position_symbol;     class=class="str">"cmt">// symbol
         request.sl       =newStopLossLevelss;               class=class="str">"cmt">// Stop Loss of the position
         request.tp       =newTakeProfitLevelss;             class=class="str">"cmt">// Take Profit of the position
         request.magic=Expert_MagicNumber;                  class=class="str">"cmt">// MagicNumber of the position
         class=class="str">"cmt">//--- output information about the modification
         PrintFormat("Modify #%I64d %s %s",position_ticket,position_symbol,EnumToString(type));
         class=class="str">"cmt">//--- send the request
         if(!OrderSend(request,result))
           PrintFormat("OrderSend error %d",GetLastError());  class=class="str">"cmt">// if unable to send the request, output the error code
         class=class="str">"cmt">//--- information about the operation
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);

记住这一条就够了

双随机+单均线的组合在 2008–2014 危机与 2020 新冠崩盘段几乎失效,但在非危机的趋势市里表现顺手。回测区间给出的夏普比率约 4,风险调整后收益算稳健偏优,但外汇与贵金属本身高杠杆、高波动,实盘仍可能大幅回撤。 代码里用 iHighest 配合 MODE_REAL_VOLUME 取高点,在外汇品种上成交量恒为 1,iHighest 返回的索引其实等价于直接取 High[1],属于绕路且易误导的写法。下方片段就是原示例里这段绕路逻辑: int Highest = iHighest(Symbol(),my_timeframe,MODE_REAL_VOLUME,WHOLE_ARRAY,1); double highestValue = iHigh(Symbol(),my_timeframe,Highest); MqlTick tick; SymbolInfoTick(_Symbol,tick); int highest_index = iHighest(NULL,0,MODE_CLOSE,100,0); int lowest_index = iLowest(NULL,0,MODE_CLOSE,100,0);

if(highest_index == -1lowest_index == -1) {

PrintFormat("iHighest()/iLowest() call error. Error code=%d",GetLastError()); return; } double previousHigh = iHigh(NULL, PERIOD_CURRENT, highest_index); double previousLow = iLow(NULL, PERIOD_CURRENT, lowest_index); double currentHigh = iHigh(NULL, PERIOD_CURRENT, 1); double currentLow = iLow(NULL, PERIOD_CURRENT, 1); 把它当策略原型可以,但别直接挂真实账户;换周期、换品种结果会漂移,自己改完去 MT5 多周期跑一遍再定。

MQL5 / C++
class="type">int Highest = iHighest(Symbol(),my_timeframe,MODE_REAL_VOLUME,WHOLE_ARRAY,class="num">1);
class="type">class="kw">double highestValue = iHigh(Symbol(),my_timeframe,Highest);
class="type">MqlTick tick;
SymbolInfoTick(_Symbol,tick);
class="type">int highest_index = iHighest(NULL,class="num">0,MODE_CLOSE,class="num">100,class="num">0);
class="type">int lowest_index = iLowest(NULL,class="num">0,MODE_CLOSE,class="num">100,class="num">0);
if(highest_index == -class="num">1 || lowest_index == -class="num">1) {
  PrintFormat("iHighest()/iLowest() call error. Error code=%d",GetLastError());
  class="kw">return;
}
class="type">class="kw">double previousHigh = iHigh(NULL, PERIOD_CURRENT, highest_index);
class="type">class="kw">double previousLow = iLow(NULL, PERIOD_CURRENT, lowest_index);
class="type">class="kw">double currentHigh = iHigh(NULL, PERIOD_CURRENT, class="num">1);
class="type">class="kw">double currentLow = iLow(NULL, PERIOD_CURRENT, class="num">1);

常见问题

在 OnInit 里用 iStochastic、iMA 等函数拿到句柄并判空,空则返回 INIT_FAILED;句柄拿到后再走后续逻辑,避免缓冲读取失败。
多半是句柄无效或指定的缓冲序号、起始位置、数量不对;先确认句柄非 INVALID_HANDLE,再按指标文档核对缓冲索引与拉取根数。
可以。小布能按该策略逻辑持续扫描品种,出现 TRADE_ACTION_SLTP 类信号时直接推送提醒,你只需确认执行。
检查是否用了 TRADE_ACTION_SLTP 且填了正确订单号与魔术码;若点差或报价过期也会导致静默失败,建议加返回值打印。
只能说可能提高过滤概率,外汇贵金属高风险,任何组合都非必胜;建议先开模拟验证信号重合率再上实盘。