//+------------------------------------------------------------------+ //| MidBarFractalEA.mq5 | //| Copyright 2026, Google Gemini | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Google Gemini" #property link "https://www.mql5.com" #property version "2.10" #property strict #include //--- Prototypes bool IsFractal(const MqlRates &rates[], int index, int strength, bool &isUp, bool &isDown); bool FindNearestFractal(ENUM_TIMEFRAMES tf, int start, int strength, bool lookForUp, double &price); void ManagePositions(ENUM_TIMEFRAMES tf, int strength, bool oppositeForBuy, bool oppositeForSell, double meanBuy, double meanSell, int totalBuys, int totalSells); void UpdateCollectiveSL(long posType, double sl); double CalculateNextLot(int currentCount); bool IsGridDistanceOK(long type, double price); //--- Input Parameters input double InpLots = 0.1; // Base Lots input double InpLotMultiplier = 1.2; // Lot Factor (Consecutive Orders) input int InpSLPadding = 5; // SL Padding from Fractal (Points) input int InpTPPadding = 5; // TP Padding from Fractal (Points) input long InpMagic = 987654; // Magic Number input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Chart Timeframe input int InpFractalIndex = 2; // Fractal Strength (Bars on each side) input int InpMaxPosPerSide = 1; // Max Positions Per Side Per Bar input int InpMaxSpread = 2; // Max Spread (Points) input int InpGridDistance = 100; // Min Distance Between Orders (Points) //--- MA Filter Parameters input bool InpUseMAFilter = true; // Use MA Filter? input int InpMAPeriod = 50; // MA Period input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA Method input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE;// MA Applied Price //--- HTF Fractal Filter Parameters input bool InpUseHTFFilter = true; // Use HTF Fractal Filter? input ENUM_TIMEFRAMES InpHTF = PERIOD_H1; // Higher Timeframe //--- Global Variables CTrade trade; int handleMA = INVALID_HANDLE; datetime lastBarTime = 0; int buyCountOnBar = 0; int sellCountOnBar = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(InpMagic); if(InpUseMAFilter) { handleMA = iMA(_Symbol, InpTimeframe, InpMAPeriod, 0, InpMAMethod, InpMAPrice); if(handleMA == INVALID_HANDLE) { Print("Failed to create MA handle"); return(INIT_FAILED); } } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(handleMA != INVALID_HANDLE) IndicatorRelease(handleMA); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // 1. Bar Time & Reset datetime barTime = iTime(_Symbol, InpTimeframe, 0); int barDuration = PeriodSeconds(InpTimeframe); datetime currentTime = TimeCurrent(); if(barTime != lastBarTime && barTime != 0) { lastBarTime = barTime; buyCountOnBar = 0; sellCountOnBar = 0; } // 2. Gather Position Data int totalBuys = 0, totalSells = 0; double sumBuyPrice = 0, sumSellPrice = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue; long type = PositionGetInteger(POSITION_TYPE); double price = PositionGetDouble(POSITION_PRICE_OPEN); if(type == POSITION_TYPE_BUY) { totalBuys++; sumBuyPrice += price; } else if(type == POSITION_TYPE_SELL) { totalSells++; sumSellPrice += price; } } double meanBuy = (totalBuys > 0) ? sumBuyPrice / totalBuys : 0; double meanSell = (totalSells > 0) ? sumSellPrice / totalSells : 0; // 3. CTF Fractal Status (Always updated for management) MqlRates rCTF[]; ArraySetAsSeries(rCTF, true); int countCTF = (InpFractalIndex * 2) + 1; if(CopyRates(_Symbol, InpTimeframe, 0, countCTF, rCTF) < countCTF) return; bool ctfFractalUp = false, ctfFractalDown = false; IsFractal(rCTF, InpFractalIndex, InpFractalIndex, ctfFractalUp, ctfFractalDown); // 4. Position Management (Runs every tick) ManagePositions(InpTimeframe, InpFractalIndex, ctfFractalUp, ctfFractalDown, meanBuy, meanSell, totalBuys, totalSells); // 5. Entry Timing Filter (Mid-Bar) if(currentTime < barTime + (barDuration / 2)) { long elapsed = currentTime - barTime; Comment("Status: WAITING FOR MID-BAR", "\nElapsed: ", elapsed, " / ", barDuration / 2, "s", "\nBasket: Buy(", totalBuys, ") Avg:", DoubleToString(meanBuy, _Digits), " | Sell(", totalSells, ") Avg:", DoubleToString(meanSell, _Digits)); return; } // 6. Final Entry Logic Prep double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); int currentSpread = (int)MathRound((ask - bid) / _Point); bool spreadOK = (currentSpread <= InpMaxSpread); // 7. MA Trend Filter bool maTrendUp = true, maTrendDown = true; double maVal[1] = {0}; if(InpUseMAFilter) { if(CopyBuffer(handleMA, 0, 0, 1, maVal) > 0) { maTrendUp = (ask > maVal[0]); maTrendDown = (bid < maVal[0]); } } // 8. HTF Confluence Filter bool htfAlignedUp = true, htfAlignedDown = true; if(InpUseHTFFilter) { MqlRates rHTF[]; ArraySetAsSeries(rHTF, true); if(CopyRates(_Symbol, InpHTF, 0, countCTF, rHTF) < countCTF) { htfAlignedUp = false; htfAlignedDown = false; } else { bool htfUp = false, htfDown = false; IsFractal(rHTF, InpFractalIndex, InpFractalIndex, htfUp, htfDown); htfAlignedUp = htfUp; htfAlignedDown = htfDown; } } // 9. Execute Trades // BUY Signal: Fractal Down (CTF) + Aligned Down (HTF) + MA UP + Grid OK if(ctfFractalDown && htfAlignedDown && buyCountOnBar < InpMaxPosPerSide && spreadOK && maTrendUp) { if(IsGridDistanceOK(POSITION_TYPE_BUY, ask)) { double lot = CalculateNextLot(totalBuys); if(trade.Buy(lot, _Symbol, ask, 0, 0, "Mid-Bar Buy")) { buyCountOnBar++; Print("Buy Entered: ", lot, " Lots"); } } } // SELL Signal: Fractal Up (CTF) + Aligned Up (HTF) + MA DOWN + Grid OK if(ctfFractalUp && htfAlignedUp && sellCountOnBar < InpMaxPosPerSide && spreadOK && maTrendDown) { if(IsGridDistanceOK(POSITION_TYPE_SELL, bid)) { double lot = CalculateNextLot(totalSells); if(trade.Sell(lot, _Symbol, bid, 0, 0, "Mid-Bar Sell")) { sellCountOnBar++; Print("Sell Entered: ", lot, " Lots"); } } } // Diagnostic Output string maLabel = !InpUseMAFilter ? "Off" : (maTrendUp && maTrendDown ? "Neutral" : (maTrendUp ? "UP" : "DOWN")); string htfLabel = !InpUseHTFFilter ? "Off" : (htfAlignedUp && htfAlignedDown ? "Both" : (htfAlignedUp ? "UP Only" : (htfAlignedDown ? "DOWN Only" : "None"))); Comment("Status: EXECUTING MID-BAR", "\n-----------------------------", "\nMA Trend: ", maLabel, (InpUseMAFilter ? " (" + DoubleToString(maVal[0], _Digits) + ")" : ""), "\nHTF Confluence: ", htfLabel, "\nCTF Signal: UP(Sell):", ctfFractalUp, " | DOWN(Buy):", ctfFractalDown, "\n-----------------------------", "\nBuy Count/Limit: ", buyCountOnBar, " / ", InpMaxPosPerSide, "\nSell Count/Limit: ", sellCountOnBar, " / ", InpMaxPosPerSide, "\nSpread: ", currentSpread, " / ", InpMaxSpread, (spreadOK ? " [OK]" : " [OVER]")); } //+------------------------------------------------------------------+ //| Core Fractal Detection Logic | //+------------------------------------------------------------------+ bool IsFractal(const MqlRates &rates[], int index, int strength, bool &isUp, bool &isDown) { int size = ArraySize(rates); if(index < strength || index >= size - strength) return false; isUp = true; isDown = true; for(int i = 1; i <= strength; i++) { if(rates[index].high <= rates[index-i].high || rates[index].high <= rates[index+i].high) isUp = false; if(rates[index].low >= rates[index-i].low || rates[index].low >= rates[index+i].low) isDown = false; if(!isUp && !isDown) break; } return true; } //+------------------------------------------------------------------+ //| Efficient Historical Fractal Search | //+------------------------------------------------------------------+ bool FindNearestFractal(ENUM_TIMEFRAMES tf, int start, int strength, bool lookForUp, double &price) { int lookback = 500; MqlRates rates[]; ArraySetAsSeries(rates, true); if(CopyRates(_Symbol, tf, 0, lookback, rates) < lookback) return false; for(int i = start; i < lookback - strength; i++) { bool up = false, down = false; if(IsFractal(rates, i, strength, up, down)) { if(lookForUp && up) { price = rates[i].high; return true; } if(!lookForUp && down) { price = rates[i].low; return true; } } } return false; } //+------------------------------------------------------------------+ //| Basket Management - 3+ Pool Logic | //+------------------------------------------------------------------+ void ManagePositions(ENUM_TIMEFRAMES tf, int strength, bool oppositeForBuy, bool oppositeForSell, double meanBuy, double meanSell, int totalBuys, int totalSells) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); // Manage BUYS: If 3+ trades, and identified FRACTAL is past mean, and opposite fractal (UP) appeared if(totalBuys >= 3 && oppositeForBuy) { double slPrice = 0; if(FindNearestFractal(tf, strength, strength, false, slPrice)) // Search for last support { // Update only if the fractal support level itself is past the mean entry (in profit) if(slPrice > meanBuy) { UpdateCollectiveSL(POSITION_TYPE_BUY, slPrice - InpSLPadding * _Point); } } } // Manage SELLS: If 3+ trades, and identified FRACTAL is past mean, and opposite fractal (DOWN) appeared if(totalSells >= 3 && oppositeForSell) { double slPrice = 0; if(FindNearestFractal(tf, strength, strength, true, slPrice)) // Search for last resistance { // Update only if the fractal resistance level itself is past the mean entry (in profit) if(slPrice < meanSell && meanSell > 0) { UpdateCollectiveSL(POSITION_TYPE_SELL, slPrice + InpSLPadding * _Point); } } } } //+------------------------------------------------------------------+ //| Apply collective SL updates to the basket | //+------------------------------------------------------------------+ void UpdateCollectiveSL(long posType, double sl) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_TYPE) != posType) continue; double curSL = PositionGetDouble(POSITION_SL); bool shouldUpdate = false; if(curSL == 0) shouldUpdate = true; else if(posType == POSITION_TYPE_BUY && sl > curSL + 2 * _Point) shouldUpdate = true; else if(posType == POSITION_TYPE_SELL && sl < curSL - 2 * _Point) shouldUpdate = true; if(shouldUpdate) { // Price safety: Don't set SL to current price (causes error) if(posType == POSITION_TYPE_BUY && sl >= SymbolInfoDouble(_Symbol, SYMBOL_BID)) continue; if(posType == POSITION_TYPE_SELL && sl <= SymbolInfoDouble(_Symbol, SYMBOL_ASK)) continue; trade.PositionModify(ticket, sl, 0); } } } //+------------------------------------------------------------------+ //| Calculate next lot based on progression factor | //+------------------------------------------------------------------+ double CalculateNextLot(int currentCount) { double lot = InpLots * MathPow(InpLotMultiplier, currentCount); // Normalize for broker constraints double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lot = MathFloor(lot/lotStep + 0.000001) * lotStep; if(lot < minLot) lot = minLot; if(lot > maxLot) lot = maxLot; return NormalizeDouble(lot, 2); } //+------------------------------------------------------------------+ //| Check if current price is far enough from existing orders | //+------------------------------------------------------------------+ bool IsGridDistanceOK(long type, double price) { if(InpGridDistance <= 0) return true; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_TYPE) != type) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); if(MathAbs(price - openPrice) < InpGridDistance * _Point) return false; } return true; }