如何将聪明钱概念(OB)与斐波那契指标相结合,实现最优进场策略·进阶篇
用左右各5根K线卡出波段点
EA 加载时 OnInit 只做一件事:给 trade 对象挂上 MagicNumber,作为这批订单的唯一标签,不在这里碰任何价格逻辑。 波段识别的核心在一段循环里:len 固定为 5,bar_Now 取倒数第 5 根 K 线,用 getHigh / getLow 把当前 K 线与左右各 5 根比一遍。只要右侧出现更高或相等高点,或左侧有更高高点,isSwingH 就翻 false;低点同理,任一侧更低或相等就否掉 isSwingL。 留到最后的 true 才是真波段:高点写进 fib_high 和 fib_t1,低点写进 fib_low 和 fib_t2,后面直接喂给斐波那契对象。你在 MT5 里把 len 改成 3 或 8,波段灵敏度会明显不同,可立刻回看图表验证。 订单块扫描靠静态变量 prevDay 卡每日只跑一次:在 Time1Hstrt 到 Time1Hend 时段内,三根 K 线组合(阳-阳-阴为看涨,阴-阴-阳为看跌)确认后才 new 一个 COrderBlock,赋好方向、时间、高低价和颜色;日期没变就跳过,避免重复计算吃 CPU。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Expert initialization function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> <span class="functions">OnInit</span>(){ trade.SetExpertMagicNumber(MagicNumber); <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>); } <span class="keyword">const</span> <span class="keyword">class="type">int</span> len = <span class="number">class="num">5</span>; <span class="keyword">class="type">int</span> left_bars, right_bars; <span class="keyword">class="type">int</span> bar_Now = len; <span class="keyword">class="type">bool</span> isSwingH = <span class="keyword">true</span>, isSwingL = <span class="keyword">true</span>; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">1</span>; i <= len; i++){ right_bars = bar_Now - i; left_bars = bar_Now + i; <span class="keyword">if</span>((getHigh(bar_Now) <= getHigh(right_bars)) ||(getHigh(bar_Now) < getHigh(left_bars))){ isSwingH = <span class="macro">class="kw">false</span>; } <span class="keyword">if</span>((getLow(bar_Now) >= getLow(right_bars)) || getLow(bar_Now) > getLow(left_bars)){ isSwingL = <span class="macro">class="kw">false</span>; } <span class="keyword">if</span>(isSwingH){ <span class="functions">Print</span>(<span class="class="type">class="kw">string">"We have a swing high at index: "</span>, bar_Now, <span class="class="type">class="kw">string">"at price: "</span>, getHigh(bar_Now)); fib_high = getHigh(bar_Now); fib_t1 = getTime(bar_Now); } <span class="keyword">if</span>(isSwingL){ <span class="functions">Print</span>(<span class="class="type">class="kw">string">"We have a swing low at index: "</span>, bar_Now,<span class="class="type">class="kw">string">" at price: "</span>, getLow(bar_Now)); fib_low = getLow(bar_Now); fib_t2 = getTime(bar_Now); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Function to find OB |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> getOrderB(){ <span class="keyword">class="kw">static</span> <span class="keyword">class="type">int</span> prevDay = <span class="number">class="num">0</span>; <span class="predefines">class="type">MqlDateTime</span> structTime; <span class="functions">TimeCurrent</span>(structTime); structTime.min = <span class="number">class="num">0</span>; structTime.sec = <span class="number">class="num">0</span>; structTime.hour = Time1Hstrt; <span class="keyword">class="type">class="kw">datetime</span> timestrt = <span class="functions">StructToTime</span>(structTime); structTime.hour = Time1Hend; <span class="keyword">class="type">class="kw">datetime</span> timend = <span class="functions">StructToTime</span>(structTime); <span class="keyword">if</span>(<span class="functions">TimeCurrent</span>() >= timestrt && <span class="functions">TimeCurrent</span>() < timend){ <span class="keyword">if</span>(prevDay != structTime.day_of_year){ <span class="keyword">class="kw">delete</span> OB;
◍ 用三根 K 线结构锁定订单块
订单块(Order Block)的识别不靠肉眼猜,可以用连续三根偏移 K 线的收开盘关系来判定。下面这段 MT5 逻辑遍历最近 100 根柱,找的是看涨 OB:第 i 根收高于开,隔两根的 i+2 也收高于开,而 i+3 必须收低于开且开在 i+2 收盘下方,此时把 i+3 的时空与高低记为 OB 对象。 循环从 i=1 起扫,是因为索引 0 是当前未闭合柱,参与回看会引入未来函数式的偏差;上限 100 根约覆盖常规 H1 图表 4 个交易日,足够抓日内机构痕迹。
| 看跌 OB 是镜像条件:i 与 i+2 均收低于开,i+3 收高于开且开低于 i+2 收盘。打印确认时间用 TIME_DATE | TIME_MINUTES,能直接落到分钟级,方便你对照小布盯盘的成交流。 |
|---|
外汇与贵金属杠杆高,OB 被扫损概率不低,上述结构只提示「倾向」存在供需失衡,实盘须配合止损与多周期确认。
for(class="type">int i = class="num">1; i < class="num">100; i++){ if(getOpen(i) < getClose(i)){ class=class="str">"cmt">// index is i since the loop starts from i which is = class="num">1 "for(class="type">int i = class="num">1)..." if(getOpen(i + class="num">2) < getClose(i + class="num">2)){ if(getOpen(i + class="num">3) > getClose(i + class="num">3) && getOpen(i + class="num">3) < getClose(i + class="num">2)){ Print("Bullish Order Block confirmed at: ", TimeToString(getTime(i + class="num">2), TIME_DATE||TIME_MINUTES)); class=class="str">"cmt">//isBullishOB = true; OB = new COrderBlock(); OB.direction = class="num">1; OB.time = getTime(i + class="num">3); OB.high = getHigh(i + class="num">3); OB.low = getLow(i + class="num">3); isBullishOB = true; OBClr = isBullishOB ? BullOB : BearOB; class=class="str">"cmt">// specify strt time T1 = OB.time; class=class="str">"cmt">// reset BULL OB flag isBullishOB = class="kw">false; prevDay = structTime.day_of_year; class="kw">break; class="kw">delete OB; } } } if(getOpen(i) > getClose(i)){ if(getOpen(i + class="num">2) > getClose(i + class="num">2)){ if(getOpen(i + class="num">3) < getClose(i + class="num">3) && getOpen(i + class="num">3) < getClose(i + class="num">2)){ Print("Bearish Order Block confirmed at: ", TimeToString(getTime(i + class="num">2), TIME_DATE||TIME_MINUTES)); class=class="str">"cmt">//isBearishOB = true;
「熊块实例化与K线取值封装」
下面这段逻辑在扫描到看跌订单块时,会现场 new 一个 COrderBlock 对象,把方向写死为 -1,并取 i+3 这根 K 线的时间、最高、最低作为 OB 的边界。注意 OB.time 被同步赋给 T1,随后 isBearishOB 复位为 false,prevDay 记成当前 structTime.day_of_year,最后 break 跳出循环并 delete OB——这套动作意味着每个新交易日只捕获一次熊块,避免重复标记。 OBClr 用三元表达式在 BearOB / BullOB 之间切换,配色与方向绑定。外汇与贵金属市场波动剧烈,订单块失效概率不低,实盘前务必在 MT5 策略测试器用历史数据回放验证标记频率。 isNewBar() 靠静态变量 last_time 对比 SERIES_LASTBAR_DATE 判断新柱,首次调用直接返回 false 不触发扫描。getHigh / getLow / getOpen / getClose / getTime 五个小函数只是 iHigh、iLow、iOpen、iClose、iTime 的薄封装,调用时传 index 即可,省去每次写 _Symbol、_Period 的冗余。 OnTick 里只在 isNewBar() 为真时才跑 getOrderB(),也就是说订单块识别被钉在每根新 K 线开盘那一瞬,盘中重复报价不会重算,回测时这点直接影响信号触发次数。
OB = new COrderBlock(); OB.direction = -class="num">1; OB.time = getTime(i + class="num">3); OB.high = getHigh(i + class="num">3); OB.low = getLow(i + class="num">3); isBearishOB = true; OBClr = isBearishOB ? BearOB : BullOB; T1 = OB.time; class=class="str">"cmt">// reset the BEAR OB flag isBearishOB = class="kw">false; prevDay = structTime.day_of_year; class="kw">break; class="kw">delete OB; } } } } } } } class="type">bool isNewBar() { class=class="str">"cmt">// Memorize the time of opening of the last bar in the class="kw">static variable class="kw">static class="type">class="kw">datetime last_time = class="num">0; class=class="str">"cmt">// Get current time class="type">class="kw">datetime lastbar_time = (class="type">class="kw">datetime)SeriesInfoInteger(Symbol(), Period(), SERIES_LASTBAR_DATE); class=class="str">"cmt">// First call if (last_time == class="num">0) { last_time = lastbar_time; class="kw">return class="kw">false; } class=class="str">"cmt">// If the time differs(new bar) if (last_time != lastbar_time) { last_time = lastbar_time; class="kw">return true; } class=class="str">"cmt">// If no new bar, class="kw">return class="kw">false class="kw">return class="kw">false; } class="type">class="kw">double getHigh(class="type">int index) { class="kw">return iHigh(_Symbol, _Period, index); } class="type">class="kw">double getLow(class="type">int index) { class="kw">return iLow(_Symbol, _Period, index); } class="type">class="kw">double getOpen(class="type">int index){ class="kw">return iOpen(_Symbol, _Period, index); } class="type">class="kw">double getClose(class="type">int index){ class="kw">return iClose(_Symbol, _Period, index); } class="type">class="kw">datetime getTime(class="type">int index) { class="kw">return iTime(_Symbol, _Period, index); } class="type">void OnTick(){ if(isNewBar()){ getOrderB();
订单块触发后的多空入场逻辑
在抓取摆动点之后,程序先取当前品种的买价与卖价:Bid 用 SymbolInfoDouble(_Symbol, SYMBOL_BID) 拿,Ask 用 SYMBOL_ASK 拿,这是后续判断触发条件的基准。 对于看涨订单块(OB.direction > 0),只有当卖价 Ask 仍低于 OB 区间上沿 OB.high 时才可能介入。此时入场价直接挂 Ask,止盈取 OB 形成时间之后最高价所在 K 线的高点,止损则落在 OB.low 减去缓冲量 Mgtn 的位置,并用 _Digits 做小数位归一。 代码里还会画一条斐波那契对象,按 Fib_Trade_lvls 百分比反推一个 entLvl 参考线。若 OB.high 不高于这条线,才真正画出 OB 并调用 trade.Buy 市价买入;否则直接 delete OB 放弃。外汇与贵金属杠杆高,这类市价单滑点可能吞掉缓冲,实盘前务必在 MT5 策略测试器跑一遍。 看跌分支完全对称:OB.direction < 0 且买价 Bid 高于 OB.low 时,入场价取 Bid,止盈取 OB 时间后最低价所在 K 线低点,止损为 OB.high 加 Mgtn。entLvl 由 fib_low 往上推,OB.low 不低于它才 trade.Sell,否则删块。最后一行把斐波那契线颜色设为黑色,纯视觉处理。
getSwings(); class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(CheckPointer(OB) != POINTER_INVALID && OB.direction > class="num">0 && Ask < OB.high){ class="type">class="kw">double entry = Ask; class="type">class="kw">double tp = getHigh(iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, iBarShift(_Symbol, PERIOD_CURRENT, OB.time))); class="type">class="kw">double sl = NormalizeDouble(OB.low - Mgtn, _Digits); class=class="str">"cmt">// class="type">class="kw">double sl = getLow(iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, class="num">2,iBarShift(_Symbol, PERIOD_CURRENT, OB.time))); ObjectCreate( class="num">0, FIBO_OBJ, OBJ_FIBO, class="num">0, fib_t1, fib_low, fib_t2, fib_high); class="type">class="kw">double entLvl = fib_high - (fib_high - fib_low) * Fib_Trade_lvls / class="num">100; class=class="str">"cmt">// check this if non if(OB.high <= entLvl){ T2 = getTime(class="num">0); OB.draw(T1, T2, OBClr); trade.Buy(Lots, _Symbol, entry, sl, tp, "OB buy"); class="kw">delete OB; }else{ class="kw">delete OB; } } if(CheckPointer(OB) != POINTER_INVALID && OB.direction < class="num">0 && Bid > OB.low){ class="type">class="kw">double entry = Bid; class="type">class="kw">double tp = getLow(iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, iBarShift(_Symbol, PERIOD_CURRENT, OB.time))); class="type">class="kw">double sl = NormalizeDouble(OB.high + Mgtn, _Digits); class=class="str">"cmt">// class="type">class="kw">double sl = getHigh(iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, iBarShift(_Symbol, PERIOD_CURRENT, OB.time))); ObjectCreate( class="num">0, FIBO_OBJ, OBJ_FIBO, class="num">0, fib_t2, fib_high, fib_t1, fib_low); class="type">class="kw">double entLvl = fib_low + (fib_low - fib_high) * Fib_Trade_lvls / class="num">100; if(OB.low >= entLvl){ T2 = getTime(class="num">0); OB.draw(T1, T2, OBClr); trade.Sell(Lots, _Symbol, entry, sl, tp, "OB sell"); class="kw">delete OB; }else{ class="kw">delete OB; } } ObjectSetInteger( class="num">0, FIBO_OBJ, OBJPROP_COLOR, clrBlack);
◍ 批量改斐波那契水平线颜色
在 MT5 里用循环遍历斐波那契对象的全部水平线,是批量改样式的直接办法。下面这段 MQL5 针对名为 FIBO_OBJ 的对象,把每一层的颜色刷成黑色。 代码逻辑并不复杂:先用 ObjectGetInteger 取该对象的总层数 OBJPROP_LEVELS,再从 0 到层数减一循环,对每一层调用 ObjectSetInteger 写入 OBJPROP_LEVELCOLOR 为 clrBlack。
for(class="type">int i = class="num">0; i < ObjectGetInteger(class="num">0, FIBO_OBJ, OBJPROP_LEVELS); i++){ ObjectSetInteger(class="num">0, FIBO_OBJ, OBJPROP_LEVELCOLOR, i, clrBlack); }
for(class="type">int i = class="num">0; i < ObjectGetInteger(class="num">0, FIBO_OBJ, OBJPROP_LEVELS); i++){ ObjectSetInteger(class="num">0, FIBO_OBJ, OBJPROP_LEVELCOLOR, i, clrBlack); }
「记住这一条就够了」
把订单块、波段高低点与斐波那契回撤塞进同一个 OnTick 循环,本质是用代码把机构介入区和大单回撤位做成可重复的过滤网。EA 在每根新 K 线生成时重算看涨/看跌订单块,并用斐波那契水平卡住进场区,止损靠 mgtn 缓冲带挂在区块外侧,这套逻辑让散户和资金流保持同向的概率更高,但不是免死金牌。 外汇与贵金属杠杆高、滑点狠,XAUUSD 一天波个 20~30 美元是常事,EA 再顺结构也扛不住黑天鹅。真要跑 FIB_OB.mq5,先在策略测试器用 2023 年至今的 M15 数据跑一遍,看回撤是否落在你容忍的区间内,再谈实盘。 代码里波段点判定写死左右各 5 根 Bar,主结构抓得稳,但微调结构得把 len 调小,否则小级别假突破会直接被忽略。