解密开盘区间突破(ORB)日内交易策略·进阶篇
📊

解密开盘区间突破(ORB)日内交易策略·进阶篇

(2/3)· 跳过基础概念,直接拆解 VWAP 跟踪、通道突破与组合分散化的统计表现

偏理论进阶 第 2/3 篇
把经纪商时区直接套用论文里的美东交易时段,是 ORB 回测最隐蔽的坑。UTC+2/3 下对应服务器时间 18:30-24:00,忽略这一步会让突破信号整体偏移。还有人拿指数现货当标的测策略,但纳斯达克-100 本身不可交易,只能走差价合约或 ETF。

用VWAP加300均線過濾噪音的M15趨勢跟蹤

這套做法本質是把成交量加權均線當成日內多空分界:15分鐘K線收盤價同時站上VWAP與300周期SMA才做多,同時跌破才做空。原論文在一分鐘跑、不設止損,但CFD手續費吃掉了頻繁進出的空間,實測M15配合0.8%止損、每筆風險2%更穩。 回測五年USTEC差價合約,收益率約501%,最大回撤16%,夏普3.57、盈虧比1.26。勝率只有42%屬趨勢跟蹤常態,利潤靠少數大波段拉回,外匯與貴金屬槓桿更高,實盤可能觸發更頻繁的止損。 平均每日超過一筆交易,因為盤中價格再穿越VWAP就允許重新進場;α值約為開盤突破策略的三倍,可能來自更短持倉與同日多空分散。下方EA代碼直接掛MT5的USTEC-M15就能驗。

MQL5 / C++
class=class="str">"cmt">//USTEC-M15
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
input class="type">int startHour = class="num">18;
input class="type">int startMinute = class="num">35;
input class="type">int endHour = class="num">23;
input class="type">int endMinute = class="num">45;
input class="type">class="kw">double risk = class="num">2.0;
input class="type">class="kw">double slp = class="num">0.008;
input class="type">int MaPeriods = class="num">300;
input class="type">int Magic = class="num">0;
class="type">int barsTotal = class="num">0;
class="type">int handleMa;
class="type">class="kw">double lastClose=class="num">0;
class="type">class="kw">double lot = class="num">0.1;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Initialization function                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMa = iMA(_Symbol,PERIOD_CURRENT,MaPeriods,class="num">0,MODE_SMA,PRICE_CLOSE);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Deinitialization function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void OnDeinit(const class="type">int reason)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|On tick function                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void OnTick()
  {
   class="type">int bars = iBars(_Symbol, PERIOD_CURRENT);
   if(barsTotal != bars){
     barsTotal=bars;
     class="type">bool NotInPosition = true;
     class="type">class="kw">double ma[];
     CopyBuffer(handleMa,class="num">0,class="num">1,class="num">1,ma);
     if(MarketOpened()&&!MarketClosed()){
       lastClose = iClose(_Symbol,PERIOD_CURRENT,class="num">1);
       class="type">int startIndex = getSessionStartIndex();
       class="type">class="kw">double vwap = getVWAP(startIndex);

「用 VWAP 与均线交叉管住持仓和开仓」

这段逻辑把持仓巡检和时段开关揉在一起:先倒序扫一遍所有持仓,只处理 Magic 与当前品种匹配的那些;若多单且收盘价跌破 VWAP、或空单且收盘价升破 VWAP,就直接平掉,否则把 NotInPosition 标记为 false,说明场上还有同策略仓位。 平仓之后才看新信号:无持仓时,收盘价低于 VWAP 且同时低于 ma[0] 才执行 sell;高于 VWAP 且高于 ma[0] 才执行 buy。把 VWAP 当动态中轴、ma[0] 当趋势确认,两个条件都满足才动手,能过滤掉一半以上的毛刺穿越。 市场关闭判断独立成函数:MarketClosed() 取服务器时间,小时与分钟分别大于等于 endHour / endMinute 才返回 true,此时无条件把同策略持仓全平。外汇与贵金属杠杆高,这种收盘强平能避免周末跳空把账户直接掀翻,但跳空仍可能触发滑点亏损。 MarketOpened() 同理比对 startHour / startMinute,只在开启时段内允许新信号。开 MT5 把 startHour、endHour 换成你盯的盘面活跃段(比如伦敦盘 08:00 起),回测一下 VWAP 参数对平仓频率的影响。

MQL5 / C++
for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){
   class="type">ulong pos = PositionGetTicket(i);
   class="type">class="kw">string symboll = PositionGetSymbol(i);
   if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){
      if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos);
      else NotInPosition=false;
   }
}
if(lastClose<vwap&&NotInPosition&&lastClose<ma[class="num">0])executeSell();
if(lastClose>vwap&&NotInPosition&&lastClose>ma[class="num">0]) executeBuy();
}
if(MarketClosed()){
   for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){
      class="type">ulong pos = PositionGetTicket(i);
      class="type">class="kw">string symboll = PositionGetSymbol(i);
      if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos);
   }
}
}

class="type">bool MarketOpened()
{
   class="type">class="kw">datetime currentTime = TimeTradeServer();
   class="type">MqlDateTime timeStruct;
   TimeToStruct(currentTime, timeStruct);
   class="type">int currentHour = timeStruct.hour;
   class="type">int currentMinute = timeStruct.min;
   if (currentHour >= startHour &&currentMinute>=startMinute)class="kw">return true;
   else class="kw">return false;
}

class="type">bool MarketClosed()
{
   class="type">class="kw">datetime currentTime = TimeTradeServer();
   class="type">MqlDateTime timeStruct;
   TimeToStruct(currentTime, timeStruct);
   class="type">int currentHour = timeStruct.hour;
   class="type">int currentMinute = timeStruct.min;
   if (currentHour >= endHour && currentMinute >= endMinute)class="kw">return true;
   else class="kw">return false;
}

◍ 挂单执行与VWAP的底层写法

EA里卖单和买单的触发函数,核心是先抓实时报价再做精度归一。executeSell() 取 SYMBOL_BID,乘以 (1+slp) 算止损位,slp 是外部传入的止损比例参数;executeBuy() 对称地取 SYMBOL_ASK 乘 (1-slp)。两个函数都调 NormalizeDouble 按 _Digits 截断,避免报价小数位不匹配导致 MT5 拒单。 仓位不是写死的,lot = calclots(bid*slp) 把止损幅度当风险基数传给计算函数,意味着止损空间越大、单手开得越小。外汇与贵金属杠杆高,这种按波动距离反推手数的逻辑能压住尾部风险,但参数 slp 设太大仍可能触发平台最小手数限制。 VWAP 没有用内置指标句柄,而是从 startCandle 往回循环到 1(故意排除当前未收线蜡烛)。每根用 (High+Low+Close)/3 当典型价,乘 iVolume 累加 sumPV,除以 sumV 得到成交量加权均价。若 sumV 为 0 直接返 0.0,实盘里仅出现在无成交的冷清品种或周末衔接 Bar。 函数末尾用 iTime(...,0) 拿当前 Bar 时间,拼成 'VWAP'+TimeToString(...,TIME_MINUTES) 的对象名,准备在图表画点。你可以直接把这段抄进 MT5 的 include 区,改 slp 看止损价跳动是否符合预期。

MQL5 / C++
class="type">void executeSell() {
   class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   bid = NormalizeDouble(bid,_Digits);
   class="type">class="kw">double sl = bid*(class="num">1+slp);
   sl = NormalizeDouble(sl, _Digits);
   lot = calclots(bid*slp);
   trade.Sell(lot,_Symbol,bid,sl);
}
class="type">void executeBuy() {
   class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   ask = NormalizeDouble(ask,_Digits);
   class="type">class="kw">double sl = ask*(class="num">1-slp);
   sl = NormalizeDouble(sl, _Digits);
   lot = calclots(ask*slp);
   trade.Buy(lot,_Symbol,ask,sl);
}
class="type">class="kw">double getVWAP(class="type">int startCandle)
{
   class="type">class="kw">double sumPV = class="num">0.0;  class=class="str">"cmt">// Sum of(price * volume)
   class="type">long sumV = class="num">0.0;     class=class="str">"cmt">// Sum of volume
   for(class="type">int i = startCandle; i >= class="num">1; i--)
   {
      class="type">class="kw">double high = iHigh(_Symbol, PERIOD_CURRENT, i);
      class="type">class="kw">double low = iLow(_Symbol, PERIOD_CURRENT, i);
      class="type">class="kw">double close = iClose(_Symbol, PERIOD_CURRENT, i);
      class="type">class="kw">double typicalPrice = (high + low + close) / class="num">3.0;
      class="type">long volume = iVolume(_Symbol, PERIOD_CURRENT, i);
      sumPV += typicalPrice * volume;
      sumV += volume;
   }
   if(sumV == class="num">0)
      class="kw">return class="num">0.0;
   class="type">class="kw">double vwap = sumPV / sumV;
   class="type">class="kw">datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, class="num">0);
   class="type">class="kw">string objName = "VWAP" + TimeToString(currentBarTime, TIME_MINUTES);

从VWAP箭头到按风险反推仓位

这段脚本把前一步算出的 VWAP 值直接画成绿点:用 OBJ_ARROW 在当前 Bar 时间位上钉一个点,颜色 clrGreen、线型 STYLE_DOT、宽度 1,纯视觉标记,方便肉眼核对 VWAP 与价格的距离。外汇与贵金属杠杆高,这类标记只作参考,不代表方向判定。 找会话起点的逻辑靠 getSessionStartIndex:从 i=1 往上翻最多 1000 根 Bar,用 TimeToStruct 拆时间,碰到 hour 等于 startHour 且 min 等于 startMinute-5 就停下,返回该 Bar 索引。注意它写死往前偏移 5 分钟,若你的平台会话开始定义不同,这里要改。 calclots 才是实盘有用的部分:先取账户余额乘 risk% 得风险金额,再用 slpoints 换算每最小仓位步距的亏损额,MathFloor 取整后夹在品种最小/最大成交量之间。回测中 α=4.8714、β=0.0985 是拟合参数,换品种大概率要重估。 开 MT5 把这段粘进 EA,把 risk 和外部 startHour/startMinute 接好,跑 EURUSD 的 M15 看绿点位置和仓位输出是否符合预期。

MQL5 / C++
  ObjectCreate(class="num">0, objName, OBJ_ARROW, class="num">0, currentBarTime, vwap);
  ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrGreen);    class=class="str">"cmt">// Green dot
  ObjectSetInteger(class="num">0, objName, OBJPROP_STYLE, STYLE_DOT);   class=class="str">"cmt">// Dot style
  ObjectSetInteger(class="num">0, objName, OBJPROP_WIDTH, class="num">1);           class=class="str">"cmt">// Size of the dot

  class="kw">return vwap;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Find the index of the candle corresponding to the session open    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int getSessionStartIndex()
{
  class="type">int sessionIndex = class="num">1;
  class=class="str">"cmt">// Loop over bars until we find the session open
  for(class="type">int i = class="num">1; i <=class="num">1000; i++)
  {
      class="type">class="kw">datetime barTime = iTime(_Symbol, PERIOD_CURRENT, i);
      class="type">MqlDateTime dt;
      TimeToStruct(barTime, dt);
      
      if(dt.hour == startHour && dt.min == startMinute-class="num">5)
      {
         sessionIndex = i;
         break;
      }
  }
      
  class="kw">return sessionIndex;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate lot size based on risk and stop loss range              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double calclots(class="type">class="kw">double slpoints) {
    class="type">class="kw">double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / class="num">100;
    class="type">class="kw">double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    class="type">class="kw">double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    class="type">class="kw">double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    class="type">class="kw">double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep;
    class="type">class="kw">double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep;
    lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
    lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
    class="kw">return lots;
}
α:class="num">4.8714
β:class="num">0.0985

「开盘噪音区突破:用波动率通道抓供需失衡」

康克瑞图姆通道突破本质是在开盘时段过滤随机波动、只做真正趋势起点的噪音区间突破。通道基于前一日收盘或当日开盘价,再乘波动率系数画出上下轨,价格呆在轨内视为噪音,1分钟K线突破上轨做多、跌破下轨做空,收盘前全平。 信号规则很直接:开盘后向上破上轨买入,向下破下轨卖出;止损压在入场价1%处,同时挂VWAP作追踪止损,单笔风险设4%。这套逻辑最初用于SPY,这里沿用USTEC标的,结果会和原论文有偏差。 回测里交易频率和首个ORB策略接近,平均两个交易日才约1笔——很多时候价格就在噪音区里晃,不触发突破。因为用了VWAP动态追踪,胜率不到50%,但盈亏比1.3、夏普5.9,收益相对回撤算强劲。 它比买入持有表现略好,最大回撤只有后者一半,但回撤期更频繁,约50%的月份在回撤,盈利月收益却更高。与标的的相关性为-11%,轻微反向,适合想逆趋势找优势的人。外汇和贵金属盘外推时要明白:杠杆品种高风险,样本再大也得扛住长回撤才等得到肥尾。 代码里把交易窗口钉在18:35到23:55,maPeriod=400的SMA拿来近似VWAP参照;想验就开MT5挂USTEC的M1,把startHour改成你broker的开盘时间跑一遍。

MQL5 / C++
class=class="str">"cmt">//USTEC-M1
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
input class="type">int startHour = class="num">18;
input class="type">int startMinute = class="num">35;
input class="type">int endHour = class="num">23;
input class="type">int endMinute = class="num">55;
input class="type">class="kw">double risk = class="num">4.0;
input class="type">class="kw">double slp = class="num">0.01;
input class="type">int Magic = class="num">0;
input class="type">int maPeriod = class="num">400;
class="type">int barsTotal = class="num">0;
class="type">int handleMa;
class="type">class="kw">double lastClose=class="num">0;
class="type">class="kw">double lastOpen = class="num">0;
class="type">class="kw">double lot = class="num">0.1;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Initialization function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMa = iMA(_Symbol,PERIOD_CURRENT,maPeriod,class="num">0,MODE_SMA,PRICE_CLOSE);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Deinitialization function                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|On tick function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void OnTick()
  {
   class="type">int bars = iBars(_Symbol, PERIOD_CURRENT);
   if(barsTotal != bars){
     barsTotal=bars;
     class="type">bool NotInPosition = true;
     class="type">class="kw">double ma[];
     CopyBuffer(handleMa,class="num">0,class="num">1,class="num">1,ma);
     if(MarketOpened()&&!MarketClosed()){
       lastClose = iClose(_Symbol,PERIOD_CURRENT,class="num">1);
       lastOpen = iOpen(_Symbol,PERIOD_CURRENT,class="num">1);
       class="type">int startIndex = getSessionStartIndex();

◍ 用 VWAP 与通道边界管住持仓和开仓

这段逻辑把出场和进场都绑在 VWAP 与上下轨上:先算好当前 VWAP,再倒序扫一遍持仓,只要是本 EA 的魔术码且符号对得上,价格收在 VWAP 另一侧就平掉,否则标记还在场中。 double vwap = getVWAP(startIndex); for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){

if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos);

else NotInPosition=false; } } double lower = getLowerBand(); double upper = getUpperBand(); if(NotInPosition&&lastOpen>lower&&lastClose<lower&&lastClose<ma[0])executeSell(); if(NotInPosition&&lastOpen<upper&&lastClose>upper&&lastClose>ma[0]) executeBuy(); 上面对应的是: 多单收破 VWAP 或空单收上 VWAP 就平仓; 空仓时若蜡烛从下轨上方开盘、收在下轨下方且收在均线之下,倾向触发 sell,对称情况触发 buy。外汇与贵金属杠杆高,这种收线判定在跳空时可能连续误触,建议拿 MT5 历史数据跑一遍看平仓频率。 市场关闭分支更直接,只要 MarketClosed() 为真,同魔术码同符号的仓位全平,不跟行情赌周末。两个时间函数都读 TimeTradeServer() 再拆 hour/minute,startHour 与 startMinute 是外部变量,改这两个值就能平移交易窗口。 bool MarketOpened() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour >= startHour &&currentMinute>=startMinute)return true; else return false; } bool MarketClosed() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min;

MQL5 / C++
class="type">class="kw">double vwap = getVWAP(startIndex);
for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){
  class="type">ulong pos = PositionGetTicket(i);
  class="type">class="kw">string symboll = PositionGetSymbol(i);
  if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){
    if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos);
    else NotInPosition=false;
  }
}
class="type">class="kw">double lower = getLowerBand();
class="type">class="kw">double upper = getUpperBand();
if(NotInPosition&&lastOpen>lower&&lastClose<lower&&lastClose<ma[class="num">0])executeSell();
if(NotInPosition&&lastOpen<upper&&lastClose>upper&&lastClose>ma[class="num">0]) executeBuy();
}
if(MarketClosed()){
  for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){
    class="type">ulong pos = PositionGetTicket(i);
    class="type">class="kw">string symboll = PositionGetSymbol(i);
    if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos);
  }
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Detect if market is opened                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">bool MarketOpened()
{
  class="type">class="kw">datetime currentTime = TimeTradeServer();
  class="type">MqlDateTime timeStruct;
  TimeToStruct(currentTime, timeStruct);
  class="type">int currentHour = timeStruct.hour;
  class="type">int currentMinute = timeStruct.min;
  if (currentHour >= startHour &&currentMinute>=startMinute)class="kw">return true;
  else class="kw">return false;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Detect if market is closed                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">bool MarketClosed()
{
  class="type">class="kw">datetime currentTime = TimeTradeServer();
  class="type">MqlDateTime timeStruct;
  TimeToStruct(currentTime, timeStruct);
  class="type">int currentHour = timeStruct.hour;
  class="type">int currentMinute = timeStruct.min;
把时区与标的映射交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到经纪商时区下的 ORB 窗口与 USTEC 杠杆风险标注,你只需专注信号质量判断。

常见问题

α 表示相对市场基准的超额收益,反映策略独有优势;β 衡量对市场波动的敏感度,值高于 1 波动更大。趋势性资产上需关注方向性偏差对 α 的干扰。
QQQ 是纳斯达克-100 的 ETF,原文论文用其但本身不可直接交易;USTEC 是该指数 CFD,可用杠杆投机且不持有标的,更贴近零售经纪商环境。
前者以成交量加权平均价为基准做趋势跟随,后者依赖康克瑞图姆通道边界突破,统计特性与持仓周期有明显分化。
可以,品种页内置了 ORB 时段标注与波动率分布,配合 AIGC 可输出分散化视角的组合表现参考,但外汇贵金属属高风险,结果仅作概率参考。
主要对冲单一突破方向的时序风险与标的趋势性偏差,通过低相关信号降低组合回撤概率。