在 MetaTrader 5 中测试和优化二元期权策略·综合运用
📘

在 MetaTrader 5 中测试和优化二元期权策略·综合运用

第 3/3 篇

◍ 到期平仓与双向触发开仓的逻辑骨架

这段逻辑把买权(Call)和卖权(Put)的到期处理、信号触发、开仓动作全塞在一个 tick 里跑。外汇与贵金属杠杆高,这类二进制到期结算的玩法回撤可能很陡,先看懂再上 MT5 测。 买权持仓若超过 XExpirationTime*60 秒(例如设 5 即 300 秒)就强制结算:盈利则本金加 OptionRate*ProfitPercent/100,亏损则扣掉固定 OptionRate,同时 Profit/Loss 计数加一,最后 ClosePositions 清仓。 卖权触发看前一根收线跌破指标线 ind_In1S1_1[1] 置 TrigerSell=1,真正开仓还要满足:无同魔法号卖仓、再前一根收线高于指标、辅助指标 ind_In2S1[2]>80、当根阴线(收<开),才 OpenSell 0.01 手并把触发复位。 买权镜像条件:前根收破 ind_In1S1_2[1] 置 TrigerBuy=1,再前根收低于指标、ind_In2S1[2]<20、当根阳线,才 OpenBuy 0.01 手。两个方向都靠 iClose/iOpen 跨根比对,避免单根误触。 把下面代码直接贴进 EA 的 OnTick 尾部,改 XExpirationTime 和 ind_In2S1 阈值,就能在策略测试器里观察胜率倾向。

MQL5 / C++
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
     Profit++;
    }
    else{
     XStartDepo=XStartDepo-OptionRate;
     Loss++;
    }
   Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
   ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
   }
class=class="str">"cmt">//Sell(Put)
if(iClose(symbolS1.Name(),XTimeFrame,class="num">1)<ind_In1S1_1[class="num">1]){TrigerSell=class="num">1;}
   if ((TrigerSell==class="num">1) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (iClose(symbolS1.Name(),XTimeFrame,class="num">2)>ind_In1S1_1[class="num">2]) && (ind_In2S1[class="num">2]>class="num">80) && (iClose(symbolS1.Name(),XTimeFrame,class="num">1)<iOpen(symbolS1.Name(),XTimeFrame,class="num">1))){
   OpenSell(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
   TrigerSell=class="num">0;
   }
   
   if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
     Profit++;
    }
    else{
     XStartDepo=XStartDepo-OptionRate;
     Loss++;
    }
   Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
   ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment);
   }
class=class="str">"cmt">//Buy(Call)
if(iClose(symbolS1.Name(),XTimeFrame,class="num">1)>ind_In1S1_2[class="num">1]){TrigerBuy=class="num">1;}
   if ((TrigerBuy==class="num">1) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (iClose(symbolS1.Name(),XTimeFrame,class="num">2)<ind_In1S1_2[class="num">2]) && (ind_In2S1[class="num">2]<class="num">20) && (iClose(symbolS1.Name(),XTimeFrame,class="num">1)>iOpen(symbolS1.Name(),XTimeFrame,class="num">1))){
   OpenBuy(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
   TrigerBuy=class="num">0;
   }
   
   if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
     Profit++;
    }
    else{
     XStartDepo=XStartDepo-OptionRate;
     Loss++;
    }

双指标交叉下的期权式开仓与到期清算

这段逻辑把行情判定拆成 Sell 与 Buy 两套对称分支,本质是用两组指标(In1、In2)在相邻三根 K 线上的相对位置,过滤出趋势反转的触发点。外汇与贵金属杠杆高,这类固定手数 0.01 的二元式清算模型仍可能连续止损,实盘前务必在 MT5 策略测试器跑通。 卖单触发先看前一根 In1 是否上穿 In2,置 TrigerSell=1;真正下单要满足当前与前一根 In1 均低于 In2,且 In2 组内呈现「快线走低、慢线走高、且前一根已预演该形态」的九重条件。任一不满足即不开仓,避免追在假突破上。 持仓后由时间门槛强制清算:当卖单存在且距开仓已超 XExpirationTime*60 秒,若 GetProfit 为正,本金加 OptionRate*ProfitPercent/100、Profit 计数 +1;否则扣掉 OptionRate、Loss +1,随后平掉该方向所有仓位并刷新 Comment 显示资金曲线。 买单分支完全镜像:前一根 In1 下穿 In2 置 TrigerBuy=1,当前与前一根 In1 均高于 In2 且 In2 组内快慢线反向扩张时才 OpenBuy。清算规则与卖单一致,只是 POSITION_TYPE_BUY 替换类型参数,读者可直接复制下面代码对照逐行改自己的魔术号与到期秒数。

MQL5 / C++
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
  }
class=class="str">"cmt">//Sell(Put)
if(ind_In1S1_1[class="num">1]>ind_In1S1_2[class="num">1]){TrigerSell=class="num">1;}
  if ((TrigerSell==class="num">1) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (ind_In1S1_1[class="num">0]<ind_In1S1_2[class="num">0]) && (ind_In1S1_1[class="num">1]<ind_In1S1_2[class="num">1]) && (ind_In2S1_1[class="num">0]<ind_In2S1_2[class="num">0]) && (ind_In2S1_1[class="num">1]<ind_In2S1_2[class="num">1]) && (ind_In2S1_1[class="num">0]<ind_In2S1_1[class="num">1]) && (ind_In2S1_2[class="num">0]>ind_In2S1_2[class="num">1]) && (ind_In2S1_1[class="num">1]<ind_In2S1_1[class="num">2]) && (ind_In2S1_2[class="num">1]>ind_In2S1_2[class="num">2])){
  OpenSell(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  TrigerSell=class="num">0;
  }
  
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
     Profit++;
    }
    else{
     XStartDepo=XStartDepo-OptionRate;
     Loss++;
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment);
  }
class=class="str">"cmt">//Buy(Call)
if(ind_In1S1_1[class="num">1]<ind_In1S1_2[class="num">1]){TrigerBuy=class="num">1;}
  if ((TrigerBuy==class="num">1) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (ind_In1S1_1[class="num">0]>ind_In1S1_2[class="num">0]) && (ind_In1S1_1[class="num">1]>ind_In1S1_2[class="num">1]) && (ind_In2S1_1[class="num">0]>ind_In2S1_2[class="num">0]) && (ind_In2S1_1[class="num">1]>ind_In2S1_2[class="num">1]) && (ind_In2S1_1[class="num">0]>ind_In2S1_1[class="num">1]) && (ind_In2S1_2[class="num">0]<ind_In2S1_2[class="num">1]) && (ind_In2S1_1[class="num">1]>ind_In2S1_1[class="num">2]) && (ind_In2S1_2[class="num">1]<ind_In2S1_2[class="num">2])){
  OpenBuy(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  TrigerBuy=class="num">0;
  }
  
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
     Profit++;
    }
    else{
     XStartDepo=XStartDepo-OptionRate;
     Loss++;
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
  }
class=class="str">"cmt">//Sell(Put)

「马丁加仓的平仓与本金递推逻辑」

这段逻辑把卖单和买单分开处理,核心是在到期时间到达后统一清算,并按盈亏调整虚拟本金 XStartDepo。外汇与贵金属杠杆高,马丁类加仓在连亏时回撤可能急剧放大,实盘前务必在 MT5 策略测试器跑历史数据。 卖单无持仓且 Bid 高于指标缓冲上沿 Distance*PointS1 时开 0.01 手空单;若已有空单且当前时间超过开仓时间加 XExpirationTime*60 秒,则检查盈利:盈利则本金加 OptionRate*ProfitPercent/100 并 Profit++,亏损则减 OptionRate 并 Loss++,最后 Comment 显示 Depo/Profit/Loss 并平仓。 第二段卖单处理引入了 LossIn 倍数:盈利时本金加 (XOptionRate*LossIn)*ProfitPercent/100 且 LossIn 重置为 1;亏损时减 XOptionRate*LossIn、LossIn++,超过 Averaging 则归 1。这等于连亏时下一单风险金按整数倍递增,连亏 5 次且 Averaging=5 时单笔最大扣除可能为初始的 5 倍。 买单分支与卖单对称:Ask 低于指标缓冲下沿时开多,到期平仓后同样用 XStartDepo 累加或扣减。开仓手数写死 0.01,但风险金计算已脱离仓位规模,验证时建议把 OpenBuy 的第三四参数从 0 改成实际 sl/tp 看成交差异。

MQL5 / C++
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (BidS1>(ind_In1S1_1[class="num">0]+(Distance*PointS1)))){
  OpenSell(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)>class="num">0){
    XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
    Profit++;
    }
    else{
    XStartDepo=XStartDepo-OptionRate;
    Loss++;
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment);
  }
class=class="str">"cmt">//Buy(Call)
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (AskS1<(ind_In1S1_2[class="num">0]-(Distance*PointS1)))){
  OpenBuy(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
    XStartDepo=XStartDepo+((OptionRate*ProfitPercent)/class="num">100);
    Profit++;
    }
    else{
    XStartDepo=XStartDepo-OptionRate;
    Loss++;
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
  }
class=class="str">"cmt">//Sell(Put)
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (BidS1>(ind_In1S1_1[class="num">0]+(Distance*PointS1)))){
  OpenSell(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  Print(((XOptionRate*LossIn)*LossIn));
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)>class="num">0){
    XStartDepo=XStartDepo+(((XOptionRate*LossIn)*ProfitPercent)/class="num">100);
    Profit++;
    LossIn=class="num">1;
    }
    else{
    XStartDepo=XStartDepo-(XOptionRate*LossIn);
    Loss++;
    LossIn++;
    if(LossIn>Averaging){LossIn=class="num">1;}
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment);
  }
class=class="str">"cmt">//Buy(Call)
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (AskS1<(ind_In1S1_2[class="num">0]-(Distance*PointS1)))){
  OpenBuy(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);

◍ 双向挂单与到期结算的仓位回路

这段逻辑把买 Call、卖 Put 两类方向都跑了一遍对称结构:无持仓时按指标边界触发开仓,有持仓则等到期时间一到就结算盈亏并平仓。开仓手数写死 0.01,触发条件分别是 Bid 上破指标线加 Distance 点、或 Ask 下破另一根指标线减 Distance 点。 到期判定用 TimeCurrent() 对比开仓时间加 XExpirationTime*60 秒。盈利时本金加 (((XOptionRate*LossIn)*LossIn)*ProfitPercent)/100,亏损时本金减等额的 (XOptionRate*LossIn)*LossIn,且 LossIn 自增,超过 Averaging 就重置为 1——这是典型的马丁式亏损加码基底。 Comment 实时把 Depo、Profit、Loss 打到图表左上,ClosePositions 无论输赢都清仓。外汇与贵金属杠杆高,这类回路在连续止损时本金回撤可能加速,MT5 里把 Averaging 设小一点能直观看到重置频率。

MQL5 / C++
  Print(((XOptionRate*LossIn)*LossIn));
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+(((XOptionRate*LossIn)*ProfitPercent)/class="num">100);
     Profit++;
     LossIn=class="num">1;
    }
    else{
     XStartDepo=XStartDepo-(XOptionRate*LossIn);
     Loss++;
     LossIn++;
     if(LossIn>Averaging){LossIn=class="num">1;}
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
  }
class=class="str">"cmt">//Sell(Put)
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (BidS1>(ind_In1S1_1[class="num">0]+(Distance*PointS1)))){
  OpenSell(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  Print(((XOptionRate*LossIn)*LossIn));
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_SELL, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((((XOptionRate*LossIn)*LossIn)*ProfitPercent)/class="num">100);
     Profit++;
     LossIn=class="num">1;
    }
    else{
     XStartDepo=XStartDepo-((XOptionRate*LossIn)*LossIn);
     Loss++;
     LossIn++;
     if(LossIn>Averaging){LossIn=class="num">1;}
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment);
  }
class=class="str">"cmt">//Buy(Call)
  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (AskS1<(ind_In1S1_2[class="num">0]-(Distance*PointS1)))){
  OpenBuy(symbolS1.Name(), class="num">0.01, class="num">0, class="num">0, EAComment);
  Print(((XOptionRate*LossIn)*LossIn));
  }

  if ((CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)>class="num">0) && (TimeCurrent()>=(GetTime(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)+(XExpirationTime*class="num">60)))){
    if(GetProfit(symbolS1.Name(), POSITION_TYPE_BUY, Magic, class="num">0)>class="num">0){
     XStartDepo=XStartDepo+((((XOptionRate*LossIn)*LossIn)*ProfitPercent)/class="num">100);
     Profit++;
     LossIn=class="num">1;
    }
    else{
     XStartDepo=XStartDepo-((XOptionRate*LossIn)*LossIn);
     Loss++;
     LossIn++;
     if(LossIn>Averaging){LossIn=class="num">1;}
    }
  Comment("Depo = ",XStartDepo," Profit = ",Profit," Loss = ",Loss);
  ClosePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment);
  }

把这条线请下神坛

回测过大量指标策略后,一个扎心的现象是:约 99% 的常规指标组合在样本外基本失效,并非参数没调好,而是逻辑前提就站不住。换一种校验思路——入场前先把品种波动结构、持仓成本、滑点容忍度全跑一遍,盈利策略的概率才抬得起来。 MT5 的持仓遍历接口能帮你在实盘前做这道筛检。下面这段从已发布的 EA 里摘出的片段,演示了如何倒序扫描当前所有持仓并提取关键字段,复制进 MetaEditor 即可编译验证: 高风险提示:外汇与贵金属杠杆交易可能令本金快速亏损,以上仅为技术校验方法,不构成任何收益暗示。 真正该下神坛的,是「挂个指标就能盈利」的幻想。把校验动作固化成代码,让每次入场前都先过一遍机器筛检,比追新指标更有用。

MQL5 / C++
  class="type">int         total           = PositionsTotal();
  for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
  {
      class="type">int   position_magic        = position.Magic();
      class="type">class="kw">string position_symbol      = position.Symbol(); class=class="str">"cmt">/*PositionGetString(POSITION_SYMBOL)*/;
      class="type">class="kw">ulong position_ticket       = position.SelectByIndex(i);
class="macro">#include <Trade\PositionInfo.mqh>
CPositionInfo  position;
class="macro">#class="kw">property version   "class="num">1.000"

常见问题

用 OrderSend 下即时市价单并记下开仓时间,在 OnTick 里比对当前时间与到期秒数,到点直接平掉对应 ticket 即可。
先确认交叉信号只在一根K线内触发一次,清算时按魔术码和到期时间过滤持仓,避免重复平错单。
小布可读取你的策略说明并跑历史分时诊断,标出到期清算漏单或错平的位置,省去手动回放。
每轮清算后把净盈亏并入下一笔本金基数,并设最大加仓层数硬上限,触顶就停手不补。
检查挂单成交后是否独立写入到期标签,结算回路要同时扫描买、卖两端 ticket 再统一清场。