//+------------------------------------------------------------------+ //| FractalGrid.mq5 | //| Copyright 2026, Google Gemini | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Google Gemini" #property link "https://www.mql5.com" #property version "1.00" #property strict #include //--- Input Parameters input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // Timeframe to Trade input double InpLots = 0.1; // Trading Lots input long InpMagic = 123456; // Magic Number input double InpBulkExitPercent = 20.0; // % to close above Mean input int InpExitDistancePoints = 1000; // Min Points from Mean to Exit //--- Global Handles int handleFractals; //--- Trading Variable CTrade trade; //--- State Tracking datetime lastSignalTime = 0; datetime lastBuyExitSignalTime = 0; // Tracks the fractal time chosen for Buy exit datetime lastSellExitSignalTime = 0; // Tracks the fractal time chosen for Sell exit //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { handleFractals = iFractals(_Symbol, InpTimeframe); if(handleFractals == INVALID_HANDLE) { Print("Failed to create fractal handle"); return(INIT_FAILED); } trade.SetExpertMagicNumber(InpMagic); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { IndicatorRelease(handleFractals); ObjectsDeleteAll(0, "GRID_"); Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // 1. Calculate and Visualize Means (Every Tick for UI) double bM = 0, bL = 0, sM = 0, sL = 0; int bCount = 0, sCount = 0; CalculateMeans(bM, bL, bCount, sM, sL, sCount); UpdateMeanVisuals(bM, sM); // 2. Dashboard Status UpdateDashboard(bM, bL, bCount, sM, sL, sCount); // 3. Signal-Based Entry & Exit logic int dir = 0; datetime sigTime = 0; int sigIndex = -1; dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, sigTime, sigIndex); // A Check for Signal-Based Exits EVERY TICK (as long as opposite signal exists) if(dir != 0) { ManagedGridExits(dir, sigTime, bM, bL, bCount, sM, sL, sCount); } // B Handle Current Signal Entry (Accumulation - Once per signal) if(dir != 0 && sigTime != lastSignalTime) { MqlTick lastTick; if(!SymbolInfoTick(_Symbol, lastTick)) return; if(dir == 1) // Signal is BUY { Print("New Buy Fractal detected at ", sigTime); if(trade.Buy(InpLots, _Symbol, lastTick.ask, 0, 0, "Fractal Grid Entry")) { Print("Buy Order Placed - Grid Accumulation"); } else Print("Buy failed: ", GetLastError()); } else if(dir == -1) // Signal is SELL { Print("New Sell Fractal detected at ", sigTime); if(trade.Sell(InpLots, _Symbol, lastTick.bid, 0, 0, "Fractal Grid Entry")) { Print("Sell Order Placed - Grid Accumulation"); } else Print("Sell failed: ", GetLastError()); } lastSignalTime = sigTime; } } //+------------------------------------------------------------------+ //| Bulk Exit logic when price is in profit AND opposite signal | //+------------------------------------------------------------------+ void ManagedGridExits(int currentSignal, datetime signalTime, double bMean, double bLots, int bCount, double sMean, double sLots, int sCount) { MqlTick lastTick; if(!SymbolInfoTick(_Symbol, lastTick)) return; // 0. Imbalance Safety Check int maxC = MathMax(bCount, sCount); double imbalance = (maxC > 0) ? (double)MathAbs(bCount - sCount) / maxC : 0; if(imbalance > 0.20) return; // Difference > 20% blocks exits // Robust pip size calculation for diagnostic display only if needed double minDist = InpExitDistancePoints * _Point; // 1. Buy Positions Bulk Exit: Need Sell Signal (-1) and Profit > minDist if(bLots > 0 && currentSignal == -1) { double profitDist = lastTick.bid - bMean; if(signalTime == lastBuyExitSignalTime) return; // Already used this fractal for an exit if(profitDist >= minDist) { Print("Conditions met for Buy Exit. Triggering 20% close..."); ResetLastError(); if(ClosePartially(POSITION_TYPE_BUY, bLots * InpBulkExitPercent / 100.0)) { lastBuyExitSignalTime = signalTime; Print("SUCCESS: Buy Exit triggered. Dist: ", DoubleToString(profitDist/_Point, 0), " points"); } else Print("ERROR: Buy exit failed. Error: ", GetLastError()); } } // 2. Sell Positions Bulk Exit: Need Buy Signal (1) and Profit > minDist if(sLots > 0 && currentSignal == 1) { double profitDist = sMean - lastTick.ask; if(signalTime == lastSellExitSignalTime) return; // Already used this fractal for an exit if(profitDist >= minDist) { Print("Conditions met for Sell Exit. Triggering 20% close..."); ResetLastError(); if(ClosePartially(POSITION_TYPE_SELL, sLots * InpBulkExitPercent / 100.0)) { lastSellExitSignalTime = signalTime; Print("SUCCESS: Sell Exit triggered. Dist: ", DoubleToString(profitDist/_Point, 0), " points"); } else Print("ERROR: Sell exit failed. Error: ", GetLastError()); } } } //+------------------------------------------------------------------+ //| Calculate directional mean entry prices and counts | //+------------------------------------------------------------------+ void CalculateMeans(double &bMean, double &bLots, int &bCount, double &sMean, double &sLots, int &sCount) { bMean = 0; bLots = 0; bCount = 0; sMean = 0; sLots = 0; sCount = 0; double bSum = 0, sSum = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol) { double vol = PositionGetDouble(POSITION_VOLUME); double price = PositionGetDouble(POSITION_PRICE_OPEN); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { bLots += vol; bCount++; bSum += price * vol; } else { sLots += vol; sCount++; sSum += price * vol; } } } } if(bLots > 0) bMean = bSum / bLots; if(sLots > 0) sMean = sSum / sLots; } //+------------------------------------------------------------------+ //| Close a specific volume from a direction (Oldest First) | //+------------------------------------------------------------------+ bool ClosePartially(long type, double targetVolume) { if(targetVolume <= 0) return false; double volumeRemaining = targetVolume; bool success = false; // Iterate and close oldest first for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_TYPE) == type) { double posVol = PositionGetDouble(POSITION_VOLUME); double toClose = MathMin(posVol, volumeRemaining); if(trade.PositionClose(ticket, toClose)) { volumeRemaining -= toClose; success = true; if(volumeRemaining <= 0) break; } } } } return success; } //+------------------------------------------------------------------+ //| Visualize Means | //+------------------------------------------------------------------+ void UpdateMeanVisuals(double bMean, double sMean) { if(bMean > 0) { ObjectCreate(0, "GRID_BUY_MEAN", OBJ_HLINE, 0, 0, bMean); ObjectSetInteger(0, "GRID_BUY_MEAN", OBJPROP_COLOR, clrLime); ObjectSetInteger(0, "GRID_BUY_MEAN", OBJPROP_STYLE, STYLE_DASH); ObjectSetString(0, "GRID_BUY_MEAN", OBJPROP_TOOLTIP, "Buy Mean: " + DoubleToString(bMean, _Digits)); } else ObjectDelete(0, "GRID_BUY_MEAN"); if(sMean > 0) { ObjectCreate(0, "GRID_SELL_MEAN", OBJ_HLINE, 0, 0, sMean); ObjectSetInteger(0, "GRID_SELL_MEAN", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "GRID_SELL_MEAN", OBJPROP_STYLE, STYLE_DASH); ObjectSetString(0, "GRID_SELL_MEAN", OBJPROP_TOOLTIP, "Sell Mean: " + DoubleToString(sMean, _Digits)); } else ObjectDelete(0, "GRID_SELL_MEAN"); } //+------------------------------------------------------------------+ //| Fractal Detection logic from template | //+------------------------------------------------------------------+ int GetLastFractal(string sym, ENUM_TIMEFRAMES period, int handle, datetime &outTime, int &outIndex) { double upper[], lower[]; ArraySetAsSeries(upper, true); ArraySetAsSeries(lower, true); if(CopyBuffer(handle, 0, 0, 1000, upper) <= 0) return 0; if(CopyBuffer(handle, 1, 0, 1000, lower) <= 0) return 0; for(int i = 2; i < 1000; i++) { if(upper[i] != EMPTY_VALUE && upper[i] > 0) { outTime = iTime(sym, period, i); outIndex = i; return -1; } if(lower[i] != EMPTY_VALUE && lower[i] > 0) { outTime = iTime(sym, period, i); outIndex = i; return 1; } } return 0; } //+------------------------------------------------------------------+ //| Dashboard Status | //+------------------------------------------------------------------+ void UpdateDashboard(double bM, double bL, int bC, double sM, double sL, int sC) { MqlTick lastTick; SymbolInfoTick(_Symbol, lastTick); double pipSize = (_Digits == 3 || _Digits == 5) ? 10 * _Point : _Point; string bInfo = (bL > 0) ? DoubleToString((lastTick.bid - bM) / _Point, 0) + " / " + (string)InpExitDistancePoints : "N/A"; string sInfo = (sL > 0) ? DoubleToString((sM - lastTick.ask) / _Point, 0) + " / " + (string)InpExitDistancePoints : "N/A"; int maxC = MathMax(bC, sC); double imb = (maxC > 0) ? (double)MathAbs(bC - sC) / maxC * 100.0 : 0; string imbStatus = (imb > 20.0) ? "EXITS BLOCKED (Imbalance > 20%)" : "Exits Allowed"; datetime dummyTime; int dummyIndex; int lastFractal = GetLastFractal(_Symbol, InpTimeframe, handleFractals, dummyTime, dummyIndex); Comment("FRACTAL GRID - DIAGNOSTICS", "\n---------------------------", "\nStatus: ", imbStatus, "\nCount Imbalance: ", DoubleToString(imb, 1), "% (", bC, "B | ", sC, "S)", "\nBuy Points/Target: ", bInfo, "\nSell Points/Target: ", sInfo, "\nBuy Mean: ", DoubleToString(bM, _Digits), " (Lots: ", DoubleToString(bL, 2), ")", "\nSell Mean: ", DoubleToString(sM, _Digits), " (Lots: ", DoubleToString(sL, 2), ")", "\nLast Fractal Type: ", (lastFractal == 1 ? "BUY" : (lastFractal == -1 ? "SELL" : "NONE")), "\n---------------------------"); }