//+------------------------------------------------------------------+ //| FVG Trader — W-fractal breakout -> first H4 FVG strategy (MT5 EA)| //| Generated by FVG Trader. Settings: 2026-11-01T18:35 | //| Logic (H4 chart, closed bars only): | //| 1. Weekly Williams fractal (period 2) not yet swept; an H4 bar | //| pierces it with a wick -> breakout event. | //| 2. Phase (weekly) -> scenario: trend/correction with bias in | //| break direction = continuation, otherwise false break. | //| 3. Within N H4 bars after the break, the first H4 FVG whose | //| first candle is the break bar or later and gap >= k*ATR(14). | //| 4. Market entry at the close of the FVG-confirming bar in the | //| FVG direction (bull->long, bear->short). | //| 5. SL beyond nearest unswept H4 fractal (p2), else the extreme | //| since the break. TP = nearest live D/W FVG at >= MinRR*risk. | //| 6. Timeout after N H4 bars. | //+------------------------------------------------------------------+ #property copyright "FVG Trader" #property version "1.00" #include input double InpRiskPct = 1.0; // Risk per trade, % of balance input double InpMinRR = 2.0; // Minimum reward:risk (TP >= MinRR * risk) input double InpFvgMinAtr = 0.30; // Min H4 FVG size, x ATR(14) input int InpFollowBars = 30; // Bars after break to wait for H4 FVG input int InpTradeBars = 180; // Close trade after N H4 bars input int InpFracPeriod = 2; // Williams fractal period input bool InpTradeContinuation = true; // Trade "continuation" breakouts input bool InpTradeFalseBreak = true; // Trade "false break" breakouts input bool InpAgreeOnly = false; // Only H4 FVG in scenario direction input bool InpBeyondOnly = true; // Only FVG entirely beyond the broken W level (bear below low / bull above high) input string InpBeyondPairs = "AUDNZD,GBPNZD,BCOUSD,DE30EUR,UK100GBP,XPTUSD"; // Pairs where the beyond rule applies (comma list, empty = all) input bool InpSkipTrend = false; // Skip phase: trend input bool InpSkipCorrection = false; // Skip phase: correction input bool InpSkipRange = false; // Skip phase: range input bool InpSkipUndefined = false; // Skip phase: undefined input ulong InpMagic = 520520; struct Fr { int i; double price; bool swept; }; struct Zn { int dir; double top; double bottom; }; struct Ev { datetime t; int side; double level; int phase; int bias; bool cont; int sdir; bool done; }; CTrade trade; Ev evs[]; datetime lastBar = 0; //--- Williams fractals with sweep flag relative to the given array (oldest first) void Fractals(const MqlRates &c[], int n, int p, Fr &hs[], Fr &ls[]) { ArrayResize(hs, 0); ArrayResize(ls, 0); for(int i = p; i < n - p; i++) { bool isH = true, isL = true; for(int j = i - p; j < i; j++) { if(!(c[i].high > c[j].high)) isH = false; if(!(c[i].low < c[j].low)) isL = false; } for(int j = i + 1; j <= i + p; j++) { if(!(c[i].high >= c[j].high)) isH = false; if(!(c[i].low <= c[j].low)) isL = false; } if(isH) { Fr f; f.i = i; f.price = c[i].high; f.swept = false; for(int j = i + p + 1; j < n; j++) if(c[j].high > f.price) { f.swept = true; break; } int k = ArraySize(hs); ArrayResize(hs, k + 1); hs[k] = f; } if(isL) { Fr f; f.i = i; f.price = c[i].low; f.swept = false; for(int j = i + p + 1; j < n; j++) if(c[j].low < f.price) { f.swept = true; break; } int k = ArraySize(ls); ArrayResize(ls, k + 1); ls[k] = f; } } } //--- Live (not fully mitigated) FVGs with remaining zone void Fvgs(const MqlRates &c[], int n, Zn &out[]) { ArrayResize(out, 0); for(int i = 2; i < n; i++) { Zn z; bool ok = false; if(c[i].low > c[i - 2].high) { z.dir = 1; z.top = c[i].low; z.bottom = c[i - 2].high; ok = true; } else if(c[i].high < c[i - 2].low) { z.dir = -1; z.top = c[i - 2].low; z.bottom = c[i].high; ok = true; } if(!ok) continue; bool filled = false; for(int j = i + 1; j < n; j++) { if(z.dir == 1) { if(c[j].low <= z.bottom) { filled = true; break; } z.top = MathMin(z.top, c[j].low); } else { if(c[j].high >= z.top) { filled = true; break; } z.bottom = MathMax(z.bottom, c[j].high); } } if(!filled) { int k = ArraySize(out); ArrayResize(out, k + 1); out[k] = z; } } } double Atr14(const MqlRates &c[], int i) { if(i < 15) return c[i].high - c[i].low; double s = 0; for(int k = i - 13; k <= i; k++) s += MathMax(c[k].high - c[k].low, MathMax(MathAbs(c[k].high - c[k - 1].close), MathAbs(c[k].low - c[k - 1].close))); return s / 14.0; } //--- phase: 0 undefined, 1 trend, 2 correction, 3 range; bias 1 up, -1 down, 0 none void Phase(const MqlRates &w[], int n, int &phase, int &bias) { phase = 0; bias = 0; if(n < 20) return; Fr hs[], ls[]; Fractals(w, n, 2, hs, ls); Zn z[]; Fvgs(w, n, z); bool bullF = false, bearF = false; for(int k = 0; k < ArraySize(z); k++) { if(z[k].dir == 1) bullF = true; else bearF = true; } Fr rh[], rl[]; ArrayResize(rh, 0); ArrayResize(rl, 0); for(int k = 0; k < ArraySize(hs); k++) if(hs[k].i >= n - 13) { int m = ArraySize(rh); ArrayResize(rh, m + 1); rh[m] = hs[k]; } for(int k = 0; k < ArraySize(ls); k++) if(ls[k].i >= n - 13) { int m = ArraySize(rl); ArrayResize(rl, m + 1); rl[m] = ls[k]; } int a = ArraySize(rh), b = ArraySize(rl); bool up = a >= 2 && b >= 2 && rh[a - 1].price > rh[a - 2].price && rl[b - 1].price > rl[b - 2].price; bool dn = a >= 2 && b >= 2 && rh[a - 1].price < rh[a - 2].price && rl[b - 1].price < rl[b - 2].price; if(up) { if(rl[b - 1].swept && bullF) { phase = 2; bias = 1; return; } if(bullF) { phase = 1; bias = 1; return; } } if(dn) { if(rh[a - 1].swept && bearF) { phase = 2; bias = -1; return; } if(bearF) { phase = 1; bias = -1; return; } } int nh = ArraySize(hs), nl = ArraySize(ls); if(nh > 0 && nl > 0) { int bi = (int)MathMax(0, nh - 3), li = (int)MathMax(0, nl - 3); Fr sh = hs[bi]; Fr sl = ls[li]; for(int k = bi; k < nh; k++) if(hs[k].price > sh.price) sh = hs[k]; for(int k = li; k < nl; k++) if(ls[k].price < sl.price) sl = ls[k]; if(!sh.swept && !sl.swept) { phase = 3; return; } } } bool PhaseAllowed(int ph) { if(ph == 1 && InpSkipTrend) return false; if(ph == 2 && InpSkipCorrection) return false; if(ph == 3 && InpSkipRange) return false; if(ph == 0 && InpSkipUndefined) return false; return true; } int BarIndex(const MqlRates &h[], int n, datetime t) { for(int k = n - 1; k >= 0; k--) if(h[k].time <= t) return (h[k].time == t) ? k : -1; return -1; } void AddEvent(datetime t, int side, double level, int ph, int bias) { for(int k = 0; k < ArraySize(evs); k++) if(evs[k].t == t && evs[k].side == side) return; Ev e; e.t = t; e.side = side; e.level = level; e.phase = ph; e.bias = bias; e.done = false; e.cont = (ph == 1 || ph == 2) && bias == side; e.sdir = e.cont ? side : -side; int k = ArraySize(evs); ArrayResize(evs, k + 1); evs[k] = e; PrintFormat("Breakout W fractal %s %.5f at %s, phase %d bias %d -> %s", side > 0 ? "UP" : "DOWN", level, TimeToString(t), ph, bias, e.cont ? "continuation" : "false break"); } void DetectBreaks(const MqlRates &h[], int nh) { MqlRates w[]; int nw = CopyRates(_Symbol, PERIOD_W1, 1, 160, w); if(nw < 20) return; Fr hs[], ls[]; Fractals(w, nw, InpFracPeriod, hs, ls); datetime wk = w[nw - 1].time + 7 * 86400; int ph, bias; Phase(w, nw, ph, bias); int last = nh - 1; double bestUp = 0, bestDn = 0; for(int k = 0; k < ArraySize(hs); k++) { if(hs[k].swept) continue; for(int j = 0; j < nh; j++) { if(h[j].time < wk) continue; if(h[j].high > hs[k].price) { if(j == last && hs[k].price > bestUp) bestUp = hs[k].price; break; } } } for(int k = 0; k < ArraySize(ls); k++) { if(ls[k].swept) continue; for(int j = 0; j < nh; j++) { if(h[j].time < wk) continue; if(h[j].low < ls[k].price) { if(j == last && (bestDn == 0 || ls[k].price < bestDn)) bestDn = ls[k].price; break; } } } if(bestUp > 0) AddEvent(h[last].time, 1, bestUp, ph, bias); if(bestDn > 0) AddEvent(h[last].time, -1, bestDn, ph, bias); } double LotsFor(double risk) { double tv = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE), ts = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP), mn = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN), mx = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(tv <= 0 || ts <= 0 || risk <= 0) return 0; double money = AccountInfoDouble(ACCOUNT_BALANCE) * InpRiskPct / 100.0; double lots = money / (risk / ts * tv); lots = MathFloor(lots / step) * step; return MathMax(mn, MathMin(mx, lots)); } bool BeyondPair() { if(StringLen(InpBeyondPairs) == 0) return true; string p[]; int n = StringSplit(InpBeyondPairs, ',', p); for(int k = 0; k < n; k++) if(StringLen(p[k]) > 0 && StringFind(_Symbol, p[k]) == 0) return true; return false; } void TryEntry(Ev &e, const MqlRates &h[], int nh) { int bi = BarIndex(h, nh, e.t); int i = nh - 1; if(bi < 0) { e.done = true; return; } if(i - bi > InpFollowBars) { e.done = true; return; } if(i < 2 || h[i - 2].time < e.t) return; double gap = 0; int dir = 0; if(h[i].low > h[i - 2].high) { dir = 1; gap = h[i].low - h[i - 2].high; } else if(h[i].high < h[i - 2].low) { dir = -1; gap = h[i - 2].low - h[i].high; } if(dir == 0 || gap < InpFvgMinAtr * Atr14(h, i)) return; e.done = true; // first qualifying FVG decides the event if((e.cont && !InpTradeContinuation) || (!e.cont && !InpTradeFalseBreak) || !PhaseAllowed(e.phase)) return; if(InpAgreeOnly && dir != e.sdir) return; if(InpBeyondOnly && BeyondPair() && !((e.side < 0 && dir == -1 && h[i - 2].low < e.level) || (e.side > 0 && dir == 1 && h[i - 2].high > e.level))) return; bool lg = dir == 1; double entry = h[i].close; //--- stop: nearest unswept H4 fractal beyond entry (last 151 bars), else extreme since the break int s0 = (int)MathMax(0, i - 150), cnt = i - s0 + 1; MqlRates seg[]; ArrayResize(seg, cnt); for(int k = 0; k < cnt; k++) seg[k] = h[s0 + k]; Fr hs[], ls[]; Fractals(seg, cnt, 2, hs, ls); double sl = 0; if(lg) { for(int k = 0; k < ArraySize(ls); k++) if(!ls[k].swept && ls[k].price < entry && ls[k].price > sl) sl = ls[k].price; if(sl == 0) { sl = h[bi].low; for(int k = bi; k <= i; k++) sl = MathMin(sl, h[k].low); } } else { sl = DBL_MAX; for(int k = 0; k < ArraySize(hs); k++) if(!hs[k].swept && hs[k].price > entry && hs[k].price < sl) sl = hs[k].price; if(sl == DBL_MAX) { sl = h[bi].high; for(int k = bi; k <= i; k++) sl = MathMax(sl, h[k].high); } } double risk = MathAbs(entry - sl); if(risk <= 0 || (lg && sl >= entry) || (!lg && sl <= entry)) return; //--- take: nearest live D/W FVG edge at >= MinRR * risk MqlRates w[], d[]; int nw = CopyRates(_Symbol, PERIOD_W1, 1, 400, w), nd = CopyRates(_Symbol, PERIOD_D1, 1, 1000, d); Zn zw[], zd[]; if(nw > 2) Fvgs(w, nw, zw); if(nd > 2) Fvgs(d, nd, zd); double tp = 0; for(int pass = 0; pass < 2; pass++) { int m = pass == 0 ? ArraySize(zw) : ArraySize(zd); for(int k = 0; k < m; k++) { Zn z; if(pass == 0) z = zw[k]; else z = zd[k]; if(lg && z.bottom - entry >= InpMinRR * risk && (tp == 0 || z.bottom < tp)) tp = z.bottom; if(!lg && entry - z.top >= InpMinRR * risk && (tp == 0 || z.top > tp)) tp = z.top; } } if(tp == 0) { Print("No D/W FVG target at >= MinRR — skip"); return; } double lots = LotsFor(risk); if(lots <= 0) return; trade.SetExpertMagicNumber(InpMagic); int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); string cm = StringFormat("WFR %s %s", e.cont ? "cont" : "false", lg ? "L" : "S"); bool ok = lg ? trade.Buy(lots, _Symbol, 0, NormalizeDouble(sl, dg), NormalizeDouble(tp, dg), cm) : trade.Sell(lots, _Symbol, 0, NormalizeDouble(sl, dg), NormalizeDouble(tp, dg), cm); PrintFormat("%s entry %.5f SL %.5f TP %.5f RR %.2f -> %s", lg ? "LONG" : "SHORT", entry, sl, tp, MathAbs(tp - entry) / risk, ok ? "ok" : "failed"); } void ManageTimeouts() { for(int k = PositionsTotal() - 1; k >= 0; k--) { ulong tk = PositionGetTicket(k); if(!PositionSelectByTicket(tk) || PositionGetString(POSITION_SYMBOL) != _Symbol || (ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic) continue; int bars = iBarShift(_Symbol, PERIOD_H4, (datetime)PositionGetInteger(POSITION_TIME)); if(bars >= InpTradeBars) trade.PositionClose(tk); } } int OnInit() { if(_Period != PERIOD_H4) Print("Recommended: attach to an H4 chart (logic always uses H4/D1/W1)"); return INIT_SUCCEEDED; } void OnTick() { datetime t0 = iTime(_Symbol, PERIOD_H4, 0); if(t0 == 0 || t0 == lastBar) return; lastBar = t0; MqlRates h[]; int nh = CopyRates(_Symbol, PERIOD_H4, 1, 400, h); if(nh < 50) return; ManageTimeouts(); for(int k = 0; k < ArraySize(evs); k++) if(!evs[k].done && evs[k].t < h[nh - 1].time) TryEntry(evs[k], h, nh); DetectBreaks(h, nh); int m = 0; for(int k = 0; k < ArraySize(evs); k++) if(!evs[k].done) evs[m++] = evs[k]; ArrayResize(evs, m); }