//+------------------------------------------------------------------+ //| DualFractal.mq5 | //| Copyright 2026, Google Gemini | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Google Gemini" #property link "https://www.mql5.com" #property version "2.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 int InpMinSLPips = 100; // Min SL distance from Price (Pips) input double InpTPRatio = 2.0; // Reward-to-Risk Ratio input int InpSLPaddingPoints = 100; // SL Padding Buffer (Points) //--- Global Handles int handleFractals; //--- Trading Variable CTrade trade; //--- State Tracking for Avoidance of Duplicates datetime lastSignalTime = 0; //+------------------------------------------------------------------+ //| 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); Comment(""); // Clear chart dashboard } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // 1. Manage Trailing Stop for existing positions //CheckTrailingStop(); // 2. Dashboard Status UpdateDashboard(); // 3. Only one position at a time for this EA if(HasOpenPosition()) return; int dir = 0; datetime sigTime = 0; int sigIndex = -1; // Get last confirmed fractal on the selected timeframe dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, sigTime, sigIndex); // Trading Logic: Trigger Entry if(dir != 0) { // Ensure we don't open multiple trades on the same fractal if(sigTime == lastSignalTime) return; MqlTick lastTick; if(!SymbolInfoTick(_Symbol, lastTick)) return; double sl = 0, tp = 0; double riskPoints = 0; double minSLPoints = 0; // MQL5 Pips to points adjustment minSLPoints = InpMinSLPips*_Point; if(dir == 1) // Signal is BUY (triggered by Down Fractal/Support) { // Find a valid SL (trough) on the same timeframe with min distance if(GetSafeFractalSL(InpTimeframe, handleFractals, 1, lastTick.ask, minSLPoints, InpSLPaddingPoints, sl)) { riskPoints = lastTick.ask - sl; tp = lastTick.ask + (riskPoints * InpTPRatio); if(trade.Buy(InpLots, _Symbol, lastTick.ask, sl, tp, "Fractal Signal Entry")) { lastSignalTime = sigTime; Print("Buy Order Placed SL:", sl, " TP:", tp, " Risk:", riskPoints/10.0, " pips"); } } else { Print("Entry Blocked: No safe BUY SL found within search depth"); } } else if(dir == -1) // Signal is SELL (triggered by Up Fractal/Resistance) { // Find a valid SL (peak) on the same timeframe with min distance if(GetSafeFractalSL(InpTimeframe, handleFractals, -1, lastTick.bid, minSLPoints, InpSLPaddingPoints, sl)) { riskPoints = sl - lastTick.bid; tp = lastTick.bid - (riskPoints * InpTPRatio); if(trade.Sell(InpLots, _Symbol, lastTick.bid, sl, tp, "Fractal Signal Entry")) { lastSignalTime = sigTime; Print("Sell Order Placed SL:", sl, " TP:", tp, " Risk:", riskPoints/10.0, " pips"); } } else { Print("Entry Blocked: No safe SELL SL found within search depth"); } } } } //+------------------------------------------------------------------+ //| Get direction and time of last confirmed fractal | //+------------------------------------------------------------------+ 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++) { bool isUp = (upper[i] < EMPTY_VALUE); bool isDown = (lower[i] < EMPTY_VALUE); if(isUp || isDown) { outTime = iTime(sym, period, i); outIndex = i; return isDown ? 1 : -1; // Down Fractal (Support) = BUY (1), Up Fractal (Resistance) = SELL (-1) } } return 0; } //+------------------------------------------------------------------+ //| Find a fractal SL that satisfies the minimum distance rule | //+------------------------------------------------------------------+ bool GetSafeFractalSL(ENUM_TIMEFRAMES period, int handle, int direction, double priceNow, double minDistancePoints, int paddingPoints, double &outSL) { // direction: 1 for Buy (look for Trough/Low), -1 for Sell (look for Peak/High) double buf[]; ArraySetAsSeries(buf, true); int bufferIdx = (direction == 1) ? 1 : 0; // Buy needs Lower (1), Sell needs Upper (0) if(CopyBuffer(handle, bufferIdx, 0, 1000, buf) <= 0) return false; for(int i = 2; i < 1000; i++) { if(buf[i] < EMPTY_VALUE) { double extreme = (direction == 1) ? iLow(_Symbol, period, i) : iHigh(_Symbol, period, i); double slCandidate = (direction == 1) ? (extreme - paddingPoints * _Point) : (extreme + paddingPoints * _Point); double dist = (direction == 1) ? (priceNow - slCandidate) : (slCandidate - priceNow); if(dist >= minDistancePoints || minDistancePoints == 0) { outSL = slCandidate; return true; } } } return false; } //+------------------------------------------------------------------+ //| Manage Trailing Stop based on new confirming fractals | //+------------------------------------------------------------------+ void CheckTrailingStop() { 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 currentSL = PositionGetDouble(POSITION_SL); datetime dummyTime; int dummyIdx; int dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, dummyTime, dummyIdx); if(type == POSITION_TYPE_BUY && dir == -1) // Buy position, last fractal is Down (Support) { double newSL; if(GetSafeFractalSL(InpTimeframe, handleFractals, 1, PositionGetDouble(POSITION_PRICE_OPEN), 0, InpSLPaddingPoints, newSL)) { if(newSL > currentSL + _Point) { trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP)); Print("Trailing SL (Buy) moved higher to:", newSL); } } } else if(type == POSITION_TYPE_SELL && dir == 1) // Sell position, last fractal is Up (Resistance) { double newSL; if(GetSafeFractalSL(InpTimeframe, handleFractals, -1, PositionGetDouble(POSITION_PRICE_OPEN), 0, InpSLPaddingPoints, newSL)) { if(newSL < currentSL - _Point || currentSL == 0) { trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP)); Print("Trailing SL (Sell) moved lower to:", newSL); } } } } } //+------------------------------------------------------------------+ //| Check for open positions for this EA | //+------------------------------------------------------------------+ bool HasOpenPosition() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == InpMagic && PositionGetString(POSITION_SYMBOL) == _Symbol) return true; } } return false; } //+------------------------------------------------------------------+ //| Update the chart dashboard | //+------------------------------------------------------------------+ void UpdateDashboard() { datetime time; int idx; int dir = GetLastFractal(_Symbol, InpTimeframe, handleFractals, time, idx); string sigStr = (dir == 1) ? "BUY (Support)" : (dir == -1 ? "SELL (Resistance)" : "NONE"); Comment("Timeframe: ", EnumToString(InpTimeframe), "\nLast Signal: ", sigStr, "\nSignal Time: ", time); } //+------------------------------------------------------------------+