网格和马丁格尔:它们是什么?如何使用它们?·进阶篇
📘

网格和马丁格尔:它们是什么?如何使用它们?·进阶篇

第 2/3 篇

马丁格尔的递归手数方程

马丁格尔的核心逻辑和网格相似:不预测方向,只靠加倍手数把亏损周期填平。某一笔订单亏损后,下一笔手数相对已亏头寸总和放大 n 倍;一旦获利,周期结束、手数重置为初始值。 关键约束是:周期内最后一笔盈利单的利润必须覆盖前面所有亏损单的损失。用账户货币(非点数)表达为 Pr = L[Nl+Np] * TP[Nl+Np] * TickSize,已亏部分 Ls = Sum(1,Nl)(L[i]*SL[i]) * TickSize。 手数靠递归得出:L[1]=StartLot,从 i=2 起 L[i] = (K * Sum(1,i-1)(L[j]*SL[j])) / TP[i],其中 K 是周期利润系数。原文未计点差佣金隔夜息,但方程本身可直接改。 外汇与贵金属属高风险品种,马丁格尔在实盘里随时可能因存款不足以完成周期而爆仓,上述方程仅用于 MT5 回测验证,不代表任何盈利保证。

MQL5 / C++
L[class="num">1]=StartLot
for(class="num">2, Nl ) L[i]=(K*Sum(class="num">1, i-class="num">1 )(L[j]*SL[j]))/TP[i]

◍ 手搓一个最简网格EA看回测真相

先在模板里挂上 MT5 自带的 PositionInfo 和 Trade 两个类,编译不会报错,省去自己写底层仓位管理的麻烦。 网格的核心输入就这几项:通道半宽 MaxChannelSizePoints=500 点、最小平仓波动 MinMoveToClose=100 点、网格步长 GridStepPoints=20 点、回算 BarsI=999 根 K 线、非对称系数 KClose=3.5。手数走最简固定 0.01,滑点上限 15 点,Magic 编号 679034 用于隔离本 EA 的订单。 EA 重启时要能恢复上一会话的网格状态,否则异常断开后网格参数就丢了。RestoreGrid() 先给 Time/High/Low 数组按 BarsI 扩维,再一次性把历史高低和时间的序列拷进来,最后用 PositionSelect 按 Magic 匹配持仓、把开仓时间读回 GridStartTime。 回测结论很直接:趋势段网格吃得挺香,但横盘损耗把利润吐光,最终净值为负——点差吃掉了边际收益,且一次『网格不够宽』的极端波动就能击穿此前所有盈利。外汇与贵金属属高风险品种,这类裸网格在实盘中断电或滑点扩大时可能直接爆仓,仅适合用极小仓位在策略测试器里验证逻辑。

MQL5 / C++
class="macro">#include <Trade\PositionInfo.mqh>
class="macro">#include <Trade\Trade.mqh>
CPositionInfo  m_position=CPositionInfo();class=class="str">"cmt">// trade position object
CTrade         m_trade=CTrade();          class=class="str">"cmt">// trading object
class=class="str">"cmt">///grid variables
input class="type">int MaxChannelSizePoints=class="num">500;class=class="str">"cmt">//Max Of a+d
input class="type">int MinMoveToClose=class="num">100;class=class="str">"cmt">//Mininum Move
input class="type">int GridStepPoints=class="num">20;class=class="str">"cmt">//Grid Step In Points
input class="type">int BarsI=class="num">999;class=class="str">"cmt">//Bars To Start Calculate
input class="type">class="kw">double KClose=class="num">3.5;class=class="str">"cmt">//Asymmetry
class=class="str">"cmt">///
class=class="str">"cmt">////////minimum trading implementation
input class="type">int SlippageMaxOpen=class="num">15; class=class="str">"cmt">//Slippage For Open In Points
input class="type">class="kw">double Lot=class="num">0.01;class=class="str">"cmt">//Lot
input class="type">int MagicC=class="num">679034;class=class="str">"cmt">//Magic
class=class="str">"cmt">/////////
class="type">void DimensionAllMQL5Values()class=class="str">"cmt">//////////////////////////////
  {
  ArrayResize(Time,BarsI,class="num">0);
  ArrayResize(High,BarsI,class="num">0);
  ArrayResize(Low,BarsI,class="num">0);
  }
class="type">void CalcAllMQL5Values()class=class="str">"cmt">///////////////////////////////////
  {
  ArraySetAsSeries(High,false);
  ArraySetAsSeries(Low,false);
  ArraySetAsSeries(Time,false);
  CopyHigh(_Symbol,_Period,class="num">0,BarsI,High);
  CopyLow(_Symbol,_Period,class="num">0,BarsI,Low);
  CopyTime(_Symbol,_Period,class="num">0,BarsI,Time);
  ArraySetAsSeries(High,true);
  ArraySetAsSeries(Low,true);
  ArraySetAsSeries(Time,true);
  }
class="type">void RestoreGrid()class=class="str">"cmt">//recover the grid if the robot is restarted
  {
  DimensionAllMQL5Values();
  CalcAllMQL5Values();
  class="type">bool ord=PositionSelect(Symbol());
  if ( ord && class="type">int(PositionGetInteger(POSITION_MAGIC)) == MagicC )
    {
    GridStartTime=class="type">class="kw">datetime(PositionGetInteger(POSITION_TIME));

「网格边界与挂单铺设的代码骨架」

这段逻辑把网格的起始价和上下边界绑定到已有持仓的开仓价上。先通过 PositionGetDouble 取出 POSITION_PRICE_OPEN 转成 double 赋给 GridStartPrice,再把 GridUpPrice 和 GridDownPrice 都初始化成同一个值,相当于以当前仓位为原点拉出通道。 随后用 for 循环扫 BarsI 根 K 线:只要某根 High 突破 GridUpPrice 就刷新上边界,Low 跌破 GridDownPrice 就刷新下边界;一旦 Time[i] 早于 GridStartTime 就 break,避免回看超过网格构建时刻的历史。扫完把 bCanUpdate 置 true、bTryedAlready 置 false,标志网格进入可更新状态。 CreateNewGrid 才是真正铺挂单的地方。它先取 SymbolInfoTick 拿最新报价,GridStartPrice 直接吃 LastTick.bid,然后以 GridStepPoints*_Point 为步长,在 ask 之上到 MaxChannelSizePoints*_Point 区间内 while 循环挂 BuyStop,在 bid 之下对称挂 SellStop——步长和通道半宽都是点值整数,改这两个参数就能直接改变网格密度。 UpdateGrid 只做一件事: tick 刷新时把 bid 与上下边界比较并扩边;ClosePosition 按 MagicC 过滤同品种仓位后凭 ticket 平仓;CleanLimitOrders 则遍历 OrdersTotal 逐个 OrderDelete。外汇与贵金属杠杆高,网格马丁类逻辑在单边行情中可能快速放大回撤,参数未经回测勿直接上实盘。

MQL5 / C++
GridStartPrice=class="type">class="kw">double(PositionGetDouble(POSITION_PRICE_OPEN));
GridUpPrice=GridStartPrice;
GridDownPrice=GridStartPrice;

for(class="type">int i=class="num">0;i<BarsI;i++)
  {
  if ( High[i] > GridUpPrice ) GridUpPrice=High[i];
  if ( Low[i] < GridDownPrice ) GridDownPrice=Low[i];
  if ( Time[i] < GridStartTime ) break;
  }
bCanUpdate=true;
bTryedAlready=false;
  }
 }
class="type">class="kw">datetime GridStartTime;class=class="str">"cmt">//grid construction time
class="type">class="kw">double GridStartPrice;class=class="str">"cmt">//grid starting price
class="type">class="kw">double GridUpPrice;class=class="str">"cmt">//upper price within the corridor
class="type">class="kw">double GridDownPrice;class=class="str">"cmt">//lower price within the corridor
class="type">bool bCanUpdate;class=class="str">"cmt">//whether it is possible to update the grid
class="type">bool bTryedAlready;class=class="str">"cmt">//whether there was an attempt to close a position
class="type">void CreateNewGrid()class=class="str">"cmt">//create a new grid
  {
  SymbolInfoTick(Symbol(),LastTick);
  GridStartTime=TimeCurrent();
  GridStartPrice=LastTick.bid;
  GridUpPrice=GridStartPrice;
  GridDownPrice=GridStartPrice;

  class="type">class="kw">double SummUp=LastTick.ask+class="type">class="kw">double(GridStepPoints)*_Point;
  class="type">class="kw">double SummDown=LastTick.bid-class="type">class="kw">double(GridStepPoints)*_Point;

  while ( SummUp <= LastTick.ask+class="type">class="kw">double(MaxChannelSizePoints)*_Point )
    {
    m_trade.BuyStop(Lot,SummUp,Symbol());
    SummUp+=class="type">class="kw">double(GridStepPoints)*_Point;
    }

  while ( SummDown >= LastTick.bid-class="type">class="kw">double(MaxChannelSizePoints)*_Point )
    {
    m_trade.SellStop(Lot,SummDown,Symbol());
    SummDown-=class="type">class="kw">double(GridStepPoints)*_Point;
    }
  }
class="type">void UpdateGrid()class=class="str">"cmt">//update the grid parameters
  {
  SymbolInfoTick(Symbol(),LastTick);
  if ( LastTick.bid > GridUpPrice ) GridUpPrice=LastTick.bid;
  if ( LastTick.bid < GridDownPrice ) GridDownPrice=LastTick.bid;
  }
class="type">void ClosePosition()class=class="str">"cmt">//close a position by a symbol
  {
  class="type">bool ord;
  ord=PositionSelect(Symbol());
  if ( ord && class="type">int(PositionGetInteger(POSITION_MAGIC)) == MagicC  )
    {
    if(m_position.SelectByIndex(class="num">0)) m_trade.PositionClose(m_position.Ticket());
    }
  }
class="type">void CleanLimitOrders()class=class="str">"cmt">//clear limit orders
  {
  class="type">int orders=OrdersTotal();
  for(class="type">int i=class="num">0;i<orders;i++)
    {
    class="type">ulong ticket=OrderGetTicket(i);
    if(ticket!=class="num">0)
      {
      m_trade.OrderDelete(ticket);
      }
    }
  }

网格平仓判定与调度落点

网格策略的退出逻辑集中在 bCanClose() 这个函数里。它先区分单向网格与双向网格:当起始价等于上轨或下轨时,只要求对应一侧离起始价的点数差不小于 MinMoveToClose 即触发平仓;双向情况下还要看上下两侧距离的比值是否越过 KClose,且触发侧点数差同样满足 MinMoveToClose。 被注释掉的那段通道突破逻辑(MaxChannelSizePoints)说明作者曾考虑用绝对通道宽度强平,但当前版本已弃用,实盘里不会生效。 Trade() 把更新网格、平仓、清限价单、重建网格串成状态机:有仓且 bCanClose() 为真就平掉并标记 bTryedAlready;若已尝试过则硬平;无仓且不允许更新时清单并重开新网格。外汇与贵金属杠杆高,这套状态切换若参数 MinMoveToClose、KClose 设错,可能在震荡中频繁重建网格放大回撤。 OnInit 里 RestoreGrid() 负责断线重连后恢复网格,OnTick 每笔报价直接调 Trade()。开 MT5 把下面代码挂到回测,重点观察 bCanClose 里 KClose 从 1.5 调到 2.0 时平仓次数是否明显下降。

MQL5 / C++
class="type">bool bCanClose()class=class="str">"cmt">//closure condition
  {
  if ( GridStartPrice == GridUpPrice && (GridStartPrice-GridDownPrice)/_Point >= MinMoveToClose ) class="kw">return true;
  if ( GridStartPrice == GridDownPrice && (GridUpPrice-GridStartPrice)/_Point >= MinMoveToClose ) class="kw">return true;
  
  if ( GridStartPrice != GridUpPrice && GridStartPrice != GridDownPrice
  && (GridStartPrice-GridDownPrice)/(GridUpPrice-GridStartPrice) >= KClose
  && (GridStartPrice-GridDownPrice)/_Point >= MinMoveToClose ) class="kw">return true;
  if ( GridStartPrice != GridDownPrice && GridStartPrice != GridUpPrice
  && (GridUpPrice-GridStartPrice)/(GridStartPrice-GridDownPrice) >= KClose
  && (GridUpPrice-GridStartPrice)/_Point >= MinMoveToClose ) class="kw">return true;
  
  class=class="str">"cmt">/*
  if ( GridUpPrice >= GridStartPrice+MaxChannelSizePoints*_Point
  class=class="str">"cmt">//|| GridDownPrice <= GridStartPrice-MaxChannelSizePoints*_Point ) class="kw">return true;
  */
  class="kw">return false;
  }
class="type">void Trade()class=class="str">"cmt">//the main function where all actions are performed
  {
  class="type">bool ord=PositionSelect(Symbol());
  
  if ( bCanUpdate ) UpdateGrid();
  
  if ( ord && bCanClose() )class=class="str">"cmt">//if there is a position and the closing condition is met
    {
    ClosePosition();
    CleanLimitOrders();
    bCanUpdate=false;
    bTryedAlready=true;
    }
  if ( bTryedAlready ) ClosePosition();
    
  if ( !bCanUpdate && !ord )
    {
    CleanLimitOrders();
    CreateNewGrid();
    bCanUpdate=true;
    bTryedAlready=false;
    }
  }
class="type">int OnInit()
  {
  m_trade.SetExpertMagicNumber(MagicC);class=class="str">"cmt">//set the magic number for positions
  RestoreGrid();class=class="str">"cmt">//restore the grid if present
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
  Trade();
  }

常见问题

递归方程为 Lots(i)=Lots(0)*Multiplier^i,初始0.1手、倍数2时,第7单已达6.4手;实盘须设最大层数硬上限。
先定网格间距、单格手数、总层数三参数,用MT5策略测试器跑极端震荡段,重点看最大回撤而非胜率。
小布可接入品种页实时算网格浮亏与层数预警,超出阈值直接推送,省去人工盯盘。
用循环从基准价双向每间隔Point*GridStep下BuyStop/SellStop,并校验未平仓单总数防重复铺设。
不够;除累计利润达标平仓外,遇单边破边界或总层数用尽须立即全平,避免马丁式塌陷。