Advertisement IC Markets

The Truth About Bill Williams Fractals

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

This complete MQL5 Expert Advisor (EA) implements the classic Bill Williams Fractal and Alligator breakout strategy for MetaTrader 5.

The code handles two technical pitfalls that break most retail Fractal EAs:

Zero Repainting (Confirmed Bar Indexing): It evaluates fractals exclusively at Bar Index 3 on a newly opened bar (Bar 0), guaranteeing that Bar 1 and Bar 2 have fully closed to the right of the fractal peak before any order is placed.

True Alligator Shift Alignment: In MQL5, iAlligator applies the future shift (8, 5, 3) visually via PLOT_SHIFT, meaning CopyBuffer() returns unshifted SMMA values at index i. To read the actual Alligator Teeth value visually plotted at bar b, this EA correctly reads teethBuffer[b + InpTeethShift].
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

MQL5 Source Code (WilliamsFractalChaosEA.mq5)

Save this file in your MetaTrader 5 directory under MQL5/Experts/WilliamsFractalChaosEA.mq5 and compile it in MetaEditor (F7).

Code: Select all

//+------------------------------------------------------------------+
//|                                       WilliamsFractalChaosEA.mq5 |
//|                     Bill Williams Fractal + Alligator Breakout   |
//+------------------------------------------------------------------+
#property copyright "Forex Strategy Deep Dive"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>

//--- Input Parameters: Alligator Settings
input group "=== Alligator Settings ==="
input int                InpJawPeriod          = 13;          // Jaw Period (Blue)
input int                InpJawShift           = 8;           // Jaw Future Shift
input int                InpTeethPeriod        = 8;           // Teeth Period (Red - Filter)
input int                InpTeethShift         = 5;           // Teeth Future Shift
input int                InpLipsPeriod         = 5;           // Lips Period (Green)
input int                InpLipsShift          = 3;           // Lips Future Shift
input ENUM_MA_METHOD     InpAlligatorMethod    = MODE_SMMA;   // Smoothing Method
input ENUM_APPLIED_PRICE InpAlligatorPrice     = PRICE_MEDIAN;// Applied Price ((H+L)/2)

//--- Input Parameters: Fractal & Execution Rules
input group "=== Fractal Breakout & Stop Rules ==="
input double             InpEntryBufferPips    = 1.5;         // Breakout Buffer Above/Below Fractal (Pips)
input double             InpSLBufferPips       = 2.0;         // Stop-Loss Buffer Beyond Opposite Fractal (Pips)
input int                InpTrailFractalRank   = 2;           // Trail Behind N-th Opposite Fractal (1 or 2)
input int                InpFractalLookback    = 100;         // Max Bars to Search for Opposite Fractals
input int                InpOrderExpirationHrs = 48;          // Pending Order Expiration (Hours, 0 = GTC)
input double             InpMinSLDistancePips  = 10.0;        // Minimum Stop-Loss Distance (Pips)
input double             InpMaxSLDistancePips  = 150.0;       // Maximum Stop-Loss Distance (Pips)

//--- Input Parameters: Risk & Trade Management
input group "=== Risk & Money Management ==="
input double             InpRiskPercent        = 1.0;         // Risk Per Trade (% of Balance, 0 = Fixed Lot)
input double             InpFixedLot           = 0.10;        // Fixed Lot Size (if Risk % is 0)
input double             InpRewardRiskRatio    = 0.0;         // Take Profit R:R Ratio (0 = Disable, Trail Only)
input ulong              InpMagicNumber        = 199505;      // EA Magic Number
input ulong              InpSlippagePoints     = 10;          // Max Slippage (Points)

//--- Global Variables & Handles
CTrade         ExtTrade;
CPositionInfo  ExtPosition;
COrderInfo     ExtOrder;

int            ExtFractalsHandle  = INVALID_HANDLE;
int            ExtAlligatorHandle = INVALID_HANDLE;
datetime       ExtLastBarTime     = 0;
double         ExtPipValue        = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Configure CTrade execution wrapper
   ExtTrade.SetExpertMagicNumber(InpMagicNumber);
   ExtTrade.SetDeviationInPoints(InpSlippagePoints);
   ExtTrade.SetTypeFillingBySymbol(_Symbol);

   //--- Calculate 1 Standard Pip in price terms (handles 3/5 and 2/4 digit brokers)
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   ExtPipValue = (digits == 3 || digits == 5) ? _Point * 10.0 : _Point;

   //--- Initialize Bill Williams Indicators
   ExtFractalsHandle = iFractals(_Symbol, _Period);
   if(ExtFractalsHandle == INVALID_HANDLE)
   {
      Print("Error creating iFractals handle: ", GetLastError());
      return(INIT_FAILED);
   }

   ExtAlligatorHandle = iAlligator(_Symbol, _Period,
                                   InpJawPeriod, InpJawShift,
                                   InpTeethPeriod, InpTeethShift,
                                   InpLipsPeriod, InpLipsShift,
                                   InpAlligatorMethod, InpAlligatorPrice);
   if(ExtAlligatorHandle == INVALID_HANDLE)
   {
      Print("Error creating iAlligator handle: ", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(ExtFractalsHandle  != INVALID_HANDLE) IndicatorRelease(ExtFractalsHandle);
   if(ExtAlligatorHandle != INVALID_HANDLE) IndicatorRelease(ExtAlligatorHandle);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   //--- Process strategy logic strictly once per newly closed candle
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime == ExtLastBarTime)
      return;

   //--- Ensure sufficient historical bars exist
   if(Bars(_Symbol, _Period) < InpFractalLookback + InpJawShift + 10)
      return;

   ExtLastBarTime = currentBarTime;

   //--- 1. Update mechanical fractal trailing stops on open positions
   ManageOpenPositions();

   //--- 2. Cancel invalidated pending orders if price crosses Alligator Teeth
   ValidatePendingOrders();

   //--- 3. Check for newly confirmed 5-bar Fractals at Bar Index 3
   CheckForFractalSignals();
}

//+------------------------------------------------------------------+
//| Retrieve Visually Aligned Alligator Teeth Value at Bar 'barIdx'  |
//+------------------------------------------------------------------+
double GetAlligatorTeethAtBar(int barIdx)
{
   // Because PLOT_SHIFT shifts the line forward by InpTeethShift bars,
   // the Teeth value visually plotted at 'barIdx' resides at buffer index (barIdx + InpTeethShift)
   double teethBuf[];
   ArraySetAsSeries(teethBuf, true);

   int bufferIndex = barIdx + InpTeethShift;
   if(CopyBuffer(ExtAlligatorHandle, 1, bufferIndex, 1, teethBuf) <= 0)
      return(0.0);

   return(teethBuf[0]);
}

//+------------------------------------------------------------------+
//| Find the N-th Most Recent Confirmed Fractal (Rank 1 = Newest)    |
//+------------------------------------------------------------------+
double GetConfirmedFractalLevel(bool isUpFractal, int rank)
{
   double fracBuf[];
   ArraySetAsSeries(fracBuf, true);

   int bufferNum = isUpFractal ? 0 : 1; // 0 = UPPER_LINE, 1 = LOWER_LINE
   if(CopyBuffer(ExtFractalsHandle, bufferNum, 0, InpFractalLookback, fracBuf) <= 0)
      return(0.0);

   int foundCount = 0;
   // Start at index 3 (first bar with 2 closed candles to its right: bars 2 and 1)
   for(int i = 3; i < InpFractalLookback; i++)
   {
      if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
      {
         foundCount++;
         if(foundCount >= rank)
            return(fracBuf[i]);
      }
   }

   // Fallback: if 2nd fractal not found in lookback window, return the 1st found
   if(rank > 1 && foundCount == 1)
   {
      for(int i = 3; i < InpFractalLookback; i++)
         if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
            return(fracBuf[i]);
   }

   return(0.0);
}

//+------------------------------------------------------------------+
//| Evaluate Confirmed Fractals at Bar 3 and Place/Update Orders     |
//+------------------------------------------------------------------+
void CheckForFractalSignals()
{
   double upFracBuf[], dnFracBuf[];
   ArraySetAsSeries(upFracBuf, true);
   ArraySetAsSeries(dnFracBuf, true);

   if(CopyBuffer(ExtFractalsHandle, 0, 3, 1, upFracBuf) <= 0) return;
   if(CopyBuffer(ExtFractalsHandle, 1, 3, 1, dnFracBuf) <= 0) return;

   double teethAtFractal = GetAlligatorTeethAtBar(3);
   double teethAtCurrent = GetAlligatorTeethAtBar(1);
   if(teethAtFractal <= 0.0 || teethAtCurrent <= 0.0) return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread = ask - bid;

   //================================================================
   // BULLISH SETUP: Confirmed Up Fractal at Bar 3 above Teeth
   //================================================================
   if(upFracBuf[0] != EMPTY_VALUE && upFracBuf[0] > teethAtFractal)
   {
      double fractalHigh = upFracBuf[0];
      double entryPrice  = NormalizeDouble(fractalHigh + (InpEntryBufferPips * ExtPipValue) + spread, _Digits);

      // Ensure price hasn't already broken above the entry level and remains above Teeth
      if(entryPrice > ask && fractalHigh > teethAtCurrent && !HasOpenPosition(POSITION_TYPE_BUY))
      {
         // Place initial SL below the N-th opposite Down Fractal (or Teeth if none in range)
         double oppFractalLow = GetConfirmedFractalLevel(false, InpTrailFractalRank);
         double slPrice       = (oppFractalLow > 0.0 && oppFractalLow < entryPrice)
                                ? oppFractalLow - (InpSLBufferPips * ExtPipValue)
                                : teethAtCurrent - (InpSLBufferPips * ExtPipValue);
         slPrice = NormalizeDouble(slPrice, _Digits);

         double slDistPips = (entryPrice - slPrice) / ExtPipValue;

         if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
         {
            double tpPrice = 0.0;
            if(InpRewardRiskRatio > 0.0)
               tpPrice = NormalizeDouble(entryPrice + (entryPrice - slPrice) * InpRewardRiskRatio, _Digits);

            double lots = CalculateLotSize(entryPrice - slPrice);
            PlaceOrReplaceStopOrder(ORDER_TYPE_BUY_STOP, lots, entryPrice, slPrice, tpPrice);
         }
      }
   }

   //================================================================
   // BEARISH SETUP: Confirmed Down Fractal at Bar 3 below Teeth
   //================================================================
   if(dnFracBuf[0] != EMPTY_VALUE && dnFracBuf[0] < teethAtFractal)
   {
      double fractalLow = dnFracBuf[0];
      double entryPrice = NormalizeDouble(fractalLow - (InpEntryBufferPips * ExtPipValue), _Digits);

      // Ensure price hasn't already broken below the entry level and remains below Teeth
      if(entryPrice < bid && fractalLow < teethAtCurrent && !HasOpenPosition(POSITION_TYPE_SELL))
      {
         // Place initial SL above the N-th opposite Up Fractal (or Teeth if none in range)
         double oppFractalHigh = GetConfirmedFractalLevel(true, InpTrailFractalRank);
         double slPrice        = (oppFractalHigh > 0.0 && oppFractalHigh > entryPrice)
                                 ? oppFractalHigh + (InpSLBufferPips * ExtPipValue) + spread
                                 : teethAtCurrent + (InpSLBufferPips * ExtPipValue) + spread;
         slPrice = NormalizeDouble(slPrice, _Digits);

         double slDistPips = (slPrice - entryPrice) / ExtPipValue;

         if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
         {
            double tpPrice = 0.0;
            if(InpRewardRiskRatio > 0.0)
               tpPrice = NormalizeDouble(entryPrice - (slPrice - entryPrice) * InpRewardRiskRatio, _Digits);

            double lots = CalculateLotSize(slPrice - entryPrice);
            PlaceOrReplaceStopOrder(ORDER_TYPE_SELL_STOP, lots, entryPrice, slPrice, tpPrice);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Place a New Stop Order or Replace an Older Pending Stop Order    |
//+------------------------------------------------------------------+
void PlaceOrReplaceStopOrder(ENUM_ORDER_TYPE orderType, double lots, double price, double sl, double tp)
{
   //--- Per Bill Williams' rules: a newer valid fractal replaces any un-triggered stop order
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber && ExtOrder.OrderType() == orderType)
         {
            ExtTrade.OrderDelete(ExtOrder.Ticket());
         }
      }
   }

   ENUM_ORDER_TYPE_TIME timeType = ORDER_TIME_GTC;
   datetime expiration = 0;

   if(InpOrderExpirationHrs > 0)
   {
      timeType   = ORDER_TIME_SPECIFIED;
      expiration = TimeCurrent() + (InpOrderExpirationHrs * 3600);
   }

   if(orderType == ORDER_TYPE_BUY_STOP)
      ExtTrade.BuyStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Up Fractal Breakout");
   else if(orderType == ORDER_TYPE_SELL_STOP)
      ExtTrade.SellStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Down Fractal Breakout");
}

//+------------------------------------------------------------------+
//| Cancel Pending Orders if Price Closes Across Alligator Teeth     |
//+------------------------------------------------------------------+
void ValidatePendingOrders()
{
   double close1 = iClose(_Symbol, _Period, 1);
   double teeth1 = GetAlligatorTeethAtBar(1);
   if(teeth1 <= 0.0) return;

   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber)
         {
            // Cancel pending Buy Stop if market closes below Alligator Teeth
            if(ExtOrder.OrderType() == ORDER_TYPE_BUY_STOP && close1 < teeth1)
               ExtTrade.OrderDelete(ExtOrder.Ticket());

            // Cancel pending Sell Stop if market closes above Alligator Teeth
            if(ExtOrder.OrderType() == ORDER_TYPE_SELL_STOP && close1 > teeth1)
               ExtTrade.OrderDelete(ExtOrder.Ticket());
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Trail Open Positions Behind Confirmed Opposite Fractals          |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
   double ask    = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid    = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread = ask - bid;
   double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;

   double trailDownFractal = GetConfirmedFractalLevel(false, InpTrailFractalRank);
   double trailUpFractal   = GetConfirmedFractalLevel(true,  InpTrailFractalRank);

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber)
            continue;

         ulong  ticket    = ExtPosition.Ticket();
         double currentSL = ExtPosition.StopLoss();
         double currentTP = ExtPosition.TakeProfit();

         //--- Trail Long Position behind N-th Down Fractal
         if(ExtPosition.PositionType() == POSITION_TYPE_BUY && trailDownFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailDownFractal - (InpSLBufferPips * ExtPipValue), _Digits);
            if(newSL > currentSL + _Point && (bid - newSL) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
         //--- Trail Short Position behind N-th Up Fractal
         else if(ExtPosition.PositionType() == POSITION_TYPE_SELL && trailUpFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailUpFractal + (InpSLBufferPips * ExtPipValue) + spread, _Digits);
            if((currentSL == 0.0 || newSL < currentSL - _Point) && (newSL - ask) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check if a Position of Given Direction is Already Open           |
//+------------------------------------------------------------------+
bool HasOpenPosition(ENUM_POSITION_TYPE posType)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() == _Symbol &&
            ExtPosition.Magic() == InpMagicNumber &&
            ExtPosition.PositionType() == posType)
            return(true);
      }
   }
   return(false);
}

//+------------------------------------------------------------------+
//| Dynamic Position Sizing Based on Account Risk % and SL Distance  |
//+------------------------------------------------------------------+
double CalculateLotSize(double slPriceDistance)
{
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(InpRiskPercent <= 0.0 || slPriceDistance <= 0.0)
      return(MathMax(minLot, MathMin(maxLot, InpFixedLot)));

   double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);

   if(tickSize <= 0.0 || tickValue <= 0.0)
      return(minLot);

   double moneyRiskPerLot = (slPriceDistance / tickSize) * tickValue;
   if(moneyRiskPerLot <= 0.0)
      return(minLot);

   double rawLots = riskMoney / moneyRiskPerLot;
   double steppedLots = MathFloor(rawLots / lotStep) * lotStep;

   return(NormalizeDouble(MathMax(minLot, MathMin(maxLot, steppedLots)), 2));
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

How to Test It in MT5 Strategy Tester

Open MetaEditor (F4 in MetaTrader 5), create a new Expert Advisor named WilliamsFractalChaosEA.mq5, paste the code above, and press Compile (F7).

In MetaTrader 5, open the Strategy Tester (Ctrl+R) and select WilliamsFractalChaosEA.

Recommended Starting Settings:

Pairs: EURUSD, GBPUSD, USDJPY, or XAUUSD

Timeframe: H1 or H4 (avoids low-timeframe spread noise)

Modeling Mode: Every tick based on real ticks (important for accurate pending stop execution and spread simulation)

Take Profit vs. Pure Trend Riding: Leave InpRewardRiskRatio = 0.0 to let winners run purely on the mechanical Down/Up Fractal trailing stop, or set it to 2.0 to cap trades at a 2:1 Reward-to-Risk target.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

To prevent the Expert Advisor from placing breakout orders during tight sideways consolidation ("chop"), we can quantify whether the Alligator is Awake (Hungry) or Sleeping (Sated) at any bar index $b$ using four mathematical criteria across its three visually shifted lines—Jaw (Blue, 13/8), Teeth (Red, 8/5), and Lips (Green, 5/3):
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

Directional Fan Alignment:

Bullish Awake State: The Lips must sit above the Teeth, and the Teeth must sit above the Jaw.Bearish Awake State:

The Lips must sit below the Teeth, and the Teeth must sit below the Jaw.Minimum Total Mouth Width (Pips or ATR-Normalized):

The total vertical distance between the outer lines, $\left\vert{}\text{Lips}_b - \text{Jaw}_b\right\vert{}$, must exceed a minimum pip threshold (InpMinMouthWidthPips) and/or a dynamic multiple of the 14-period Average True Range (InpMinMouthATRMult). Using an ATR multiplier allows the filter to scale automatically between FX pairs and higher-volatility instruments like Gold (XAUUSD) across any timeframe.

Minimum Adjacent Line Separation: Both adjacent gaps—$\left\vert{}\text{Lips}_b - \text{Teeth}_b\right\vert{}$ and $\left\vert{}\text{Teeth}_b - \text{Jaw}_b\right\vert{}$—must exceed InpMinAdjacentGapPips so the EA rejects setups where two of the three lines are intertwined or touching.

Mouth Expansion Check (Optional): Compares the total spread $\left\vert{}\text{Lips}_1 - \text{Jaw}_1\right\vert{}$ on the just-closed bar against $\left\vert{}\text{Lips}_2 - \text{Jaw}_2\right\vert{}$ on the prior bar to verify that the Alligator's mouth is actively opening rather than closing.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

Updated MQL5 Source Code (WilliamsFractalChaosEA.mq5 v1.10)

Replace your existing code in MetaEditor with this updated version and press Compile (F7).

Code: Select all

//+------------------------------------------------------------------+
//|                                       WilliamsFractalChaosEA.mq5 |
//|           Bill Williams Fractal + Alligator Awake Filter (v1.10) |
//+------------------------------------------------------------------+
#property copyright "Forex Strategy Deep Dive"
#property version   "1.10"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>

//--- Enum for Alligator Evaluation Bar
enum ENUM_AWAKE_EVAL_MODE
{
   AWAKE_AT_SIGNAL_BAR  = 0, // Check at Bar 1 (When Fractal Confirms)
   AWAKE_AT_FRACTAL_BAR = 1, // Check at Bar 3 (At Fractal Peak/Trough)
   AWAKE_AT_BOTH_BARS   = 2  // Require Awake State at Both Bar 3 and Bar 1
};

//--- Input Parameters: Alligator Settings
input group "=== Alligator Core Settings ==="
input int                   InpJawPeriod            = 13;                // Jaw Period (Blue)
input int                   InpJawShift             = 8;                 // Jaw Future Shift
input int                   InpTeethPeriod          = 8;                 // Teeth Period (Red - Filter)
input int                   InpTeethShift           = 5;                 // Teeth Future Shift
input int                   InpLipsPeriod           = 5;                 // Lips Period (Green)
input int                   InpLipsShift            = 3;                 // Lips Future Shift
input ENUM_MA_METHOD        InpAlligatorMethod      = MODE_SMMA;         // Smoothing Method
input ENUM_APPLIED_PRICE    InpAlligatorPrice       = PRICE_MEDIAN;      // Applied Price ((H+L)/2)

//--- Input Parameters: Alligator 'Awake' Mouth-Width Filter
input group "=== Alligator 'Awake' Filter Settings ==="
input bool                  InpUseAwakeFilter       = true;              // Enable Alligator Awake Filter
input ENUM_AWAKE_EVAL_MODE  InpAwakeEvalMode        = AWAKE_AT_SIGNAL_BAR;// Bar to Check Awake Condition
input bool                  InpRequireStrictFan     = true;              // Require Lips/Teeth/Jaw Fan Alignment
input double                InpMinMouthWidthPips    = 3.0;               // Min Total Mouth Width |Lips - Jaw| (Pips)
input double                InpMinAdjacentGapPips   = 0.8;               // Min Gap Between Adjacent Lines (Pips)
input int                   InpATRPeriod            = 14;                // ATR Period for Dynamic Width Filter
input double                InpMinMouthATRMult      = 0.25;              // Min Mouth Width as ATR Multiple (0 = Off)
input bool                  InpRequireMouthExpanding= false;             // Require Mouth Opening (Width[1] > Width[2])
input bool                  InpDeleteOrderIfSleeping= true;              // Cancel Pending Orders if Alligator Sleeps

//--- Input Parameters: Fractal & Execution Rules
input group "=== Fractal Breakout & Stop Rules ==="
input double                InpEntryBufferPips      = 1.5;               // Breakout Buffer Above/Below Fractal (Pips)
input double                InpSLBufferPips         = 2.0;               // Stop-Loss Buffer Beyond Opposite Fractal (Pips)
input int                   InpTrailFractalRank     = 2;                 // Trail Behind N-th Opposite Fractal (1 or 2)
input int                   InpFractalLookback      = 100;               // Max Bars to Search for Opposite Fractals
input int                   InpOrderExpirationHrs   = 48;                // Pending Order Expiration (Hours, 0 = GTC)
input double                InpMinSLDistancePips    = 10.0;              // Minimum Stop-Loss Distance (Pips)
input double                InpMaxSLDistancePips    = 150.0;             // Maximum Stop-Loss Distance (Pips)

//--- Input Parameters: Risk & Trade Management
input group "=== Risk & Money Management ==="
input double                InpRiskPercent          = 1.0;               // Risk Per Trade (% of Balance, 0 = Fixed Lot)
input double                InpFixedLot             = 0.10;              // Fixed Lot Size (if Risk % is 0)
input double                InpRewardRiskRatio      = 0.0;               // Take Profit R:R Ratio (0 = Disable, Trail Only)
input ulong                 InpMagicNumber          = 199505;            // EA Magic Number
input ulong                 InpSlippagePoints       = 10;                // Max Slippage (Points)

//--- Global Variables & Handles
CTrade         ExtTrade;
CPositionInfo  ExtPosition;
COrderInfo     ExtOrder;

int            ExtFractalsHandle  = INVALID_HANDLE;
int            ExtAlligatorHandle = INVALID_HANDLE;
int            ExtATRHandle       = INVALID_HANDLE;
datetime       ExtLastBarTime     = 0;
double         ExtPipValue        = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Configure CTrade execution wrapper
   ExtTrade.SetExpertMagicNumber(InpMagicNumber);
   ExtTrade.SetDeviationInPoints(InpSlippagePoints);
   ExtTrade.SetTypeFillingBySymbol(_Symbol);

   //--- Calculate 1 Standard Pip in price terms (handles 3/5 and 2/4 digit symbols)
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   ExtPipValue = (digits == 3 || digits == 5) ? _Point * 10.0 : _Point;

   //--- Initialize Bill Williams Fractals
   ExtFractalsHandle = iFractals(_Symbol, _Period);
   if(ExtFractalsHandle == INVALID_HANDLE)
   {
      Print("Error creating iFractals handle: ", GetLastError());
      return(INIT_FAILED);
   }

   //--- Initialize Bill Williams Alligator
   ExtAlligatorHandle = iAlligator(_Symbol, _Period,
                                   InpJawPeriod, InpJawShift,
                                   InpTeethPeriod, InpTeethShift,
                                   InpLipsPeriod, InpLipsShift,
                                   InpAlligatorMethod, InpAlligatorPrice);
   if(ExtAlligatorHandle == INVALID_HANDLE)
   {
      Print("Error creating iAlligator handle: ", GetLastError());
      return(INIT_FAILED);
   }

   //--- Initialize ATR for dynamic mouth-width scaling
   ExtATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(ExtATRHandle == INVALID_HANDLE)
   {
      Print("Error creating iATR handle: ", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(ExtFractalsHandle  != INVALID_HANDLE) IndicatorRelease(ExtFractalsHandle);
   if(ExtAlligatorHandle != INVALID_HANDLE) IndicatorRelease(ExtAlligatorHandle);
   if(ExtATRHandle       != INVALID_HANDLE) IndicatorRelease(ExtATRHandle);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   //--- Process strategy logic strictly once per newly closed candle
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime == ExtLastBarTime)
      return;

   //--- Ensure sufficient historical bars exist
   int maxShift = MathMax(InpJawShift, MathMax(InpTeethShift, InpLipsShift));
   if(Bars(_Symbol, _Period) < InpFractalLookback + maxShift + 10)
      return;

   ExtLastBarTime = currentBarTime;

   //--- 1. Update mechanical fractal trailing stops on open positions
   ManageOpenPositions();

   //--- 2. Cancel invalidated pending orders if price crosses Teeth or Alligator sleeps
   ValidatePendingOrders();

   //--- 3. Check for newly confirmed 5-bar Fractals at Bar Index 3
   CheckForFractalSignals();
}

//+------------------------------------------------------------------+
//| Retrieve Visually Aligned Alligator Jaw, Teeth & Lips at 'barIdx'|
//+------------------------------------------------------------------+
bool GetAlligatorLinesAtBar(int barIdx, double &jaw, double &teeth, double &lips)
{
   // Buffer 0 = Jaw (Blue), Buffer 1 = Teeth (Red), Buffer 2 = Lips (Green)
   // Because PLOT_SHIFT shifts lines into the future on the chart, the value visually
   // plotted at 'barIdx' resides at buffer index (barIdx + respective Shift).
   double jawBuf[], teethBuf[], lipsBuf[];
   ArraySetAsSeries(jawBuf,   true);
   ArraySetAsSeries(teethBuf, true);
   ArraySetAsSeries(lipsBuf,  true);

   if(CopyBuffer(ExtAlligatorHandle, 0, barIdx + InpJawShift,   1, jawBuf)   <= 0) return(false);
   if(CopyBuffer(ExtAlligatorHandle, 1, barIdx + InpTeethShift, 1, teethBuf) <= 0) return(false);
   if(CopyBuffer(ExtAlligatorHandle, 2, barIdx + InpLipsShift,  1, lipsBuf)  <= 0) return(false);

   jaw   = jawBuf[0];
   teeth = teethBuf[0];
   lips  = lipsBuf[0];

   return(jaw > 0.0 && teeth > 0.0 && lips > 0.0);
}

//+------------------------------------------------------------------+
//| Check if Alligator is Awake at a Single Bar Index                |
//+------------------------------------------------------------------+
bool IsAlligatorAwakeAtBar(int barIdx, bool isBullish)
{
   double jaw = 0.0, teeth = 0.0, lips = 0.0;
   if(!GetAlligatorLinesAtBar(barIdx, jaw, teeth, lips))
      return(false);

   //--- 1. Check strict directional fan ordering (Lips > Teeth > Jaw or Lips < Teeth < Jaw)
   if(InpRequireStrictFan)
   {
      if(isBullish && !(lips > teeth && teeth > jaw))
         return(false);
      if(!isBullish && !(lips < teeth && teeth < jaw))
         return(false);
   }

   //--- 2. Check minimum total mouth width (|Lips - Jaw|) in Pips
   double totalMouthWidth = MathAbs(lips - jaw);
   double minMouthPrice   = InpMinMouthWidthPips * ExtPipValue;
   if(totalMouthWidth < minMouthPrice)
      return(false);

   //--- 3. Check minimum gap between adjacent lines (|Lips - Teeth| and |Teeth - Jaw|)
   double lipsTeethGap  = MathAbs(lips - teeth);
   double teethJawGap   = MathAbs(teeth - jaw);
   double minAdjPrice   = InpMinAdjacentGapPips * ExtPipValue;
   if(lipsTeethGap < minAdjPrice || teethJawGap < minAdjPrice)
      return(false);

   //--- 4. Check ATR-normalized mouth width threshold (if enabled)
   if(InpMinMouthATRMult > 0.0)
   {
      double atrBuf[];
      ArraySetAsSeries(atrBuf, true);
      if(CopyBuffer(ExtATRHandle, 0, barIdx, 1, atrBuf) <= 0)
         return(false);

      if(totalMouthWidth < (atrBuf[0] * InpMinMouthATRMult))
         return(false);
   }

   //--- 5. Check if mouth is actively expanding compared to prior bar (barIdx + 1)
   if(InpRequireMouthExpanding)
   {
      double prevJaw = 0.0, prevTeeth = 0.0, prevLips = 0.0;
      if(!GetAlligatorLinesAtBar(barIdx + 1, prevJaw, prevTeeth, prevLips))
         return(false);

      double prevMouthWidth = MathAbs(prevLips - prevJaw);
      if(totalMouthWidth <= prevMouthWidth)
         return(false);
   }

   return(true);
}

//+------------------------------------------------------------------+
//| Master Wrapper to Evaluate Alligator Awake State per Mode        |
//+------------------------------------------------------------------+
bool CheckAlligatorAwakeFilter(bool isBullish)
{
   if(!InpUseAwakeFilter)
      return(true);

   switch(InpAwakeEvalMode)
   {
      case AWAKE_AT_SIGNAL_BAR:
         return IsAlligatorAwakeAtBar(1, isBullish);

      case AWAKE_AT_FRACTAL_BAR:
         return IsAlligatorAwakeAtBar(3, isBullish);

      case AWAKE_AT_BOTH_BARS:
         return (IsAlligatorAwakeAtBar(3, isBullish) && IsAlligatorAwakeAtBar(1, isBullish));
   }

   return(false);
}

//+------------------------------------------------------------------+
//| Find the N-th Most Recent Confirmed Fractal (Rank 1 = Newest)    |
//+------------------------------------------------------------------+
double GetConfirmedFractalLevel(bool isUpFractal, int rank)
{
   double fracBuf[];
   ArraySetAsSeries(fracBuf, true);

   int bufferNum = isUpFractal ? 0 : 1; // 0 = UPPER_LINE, 1 = LOWER_LINE
   if(CopyBuffer(ExtFractalsHandle, bufferNum, 0, InpFractalLookback, fracBuf) <= 0)
      return(0.0);

   int foundCount = 0;
   // Start at index 3 (first bar with 2 closed candles to its right: bars 2 and 1)
   for(int i = 3; i < InpFractalLookback; i++)
   {
      if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
      {
         foundCount++;
         if(foundCount >= rank)
            return(fracBuf[i]);
      }
   }

   // Fallback: if 2nd fractal not found in lookback window, return the 1st found
   if(rank > 1 && foundCount == 1)
   {
      for(int i = 3; i < InpFractalLookback; i++)
         if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
            return(fracBuf[i]);
   }

   return(0.0);
}

//+------------------------------------------------------------------+
//| Evaluate Confirmed Fractals at Bar 3 and Place/Update Orders     |
//+------------------------------------------------------------------+
void CheckForFractalSignals()
{
   double upFracBuf[], dnFracBuf[];
   ArraySetAsSeries(upFracBuf, true);
   ArraySetAsSeries(dnFracBuf, true);

   if(CopyBuffer(ExtFractalsHandle, 0, 3, 1, upFracBuf) <= 0) return;
   if(CopyBuffer(ExtFractalsHandle, 1, 3, 1, dnFracBuf) <= 0) return;

   double jaw3 = 0.0, teethAtFractal = 0.0, lips3 = 0.0;
   double jaw1 = 0.0, teethAtCurrent = 0.0, lips1 = 0.0;

   if(!GetAlligatorLinesAtBar(3, jaw3, teethAtFractal, lips3)) return;
   if(!GetAlligatorLinesAtBar(1, jaw1, teethAtCurrent, lips1)) return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread = ask - bid;

   //================================================================
   // BULLISH SETUP: Confirmed Up Fractal at Bar 3 + Awake Alligator
   //================================================================
   if(upFracBuf[0] != EMPTY_VALUE && upFracBuf[0] > teethAtFractal)
   {
      // Verify Alligator is Awake for Bullish Breakout
      if(CheckAlligatorAwakeFilter(true))
      {
         double fractalHigh = upFracBuf[0];
         double entryPrice  = NormalizeDouble(fractalHigh + (InpEntryBufferPips * ExtPipValue) + spread, _Digits);

         // Ensure price hasn't already broken above the entry level and remains above Teeth
         if(entryPrice > ask && fractalHigh > teethAtCurrent && !HasOpenPosition(POSITION_TYPE_BUY))
         {
            double oppFractalLow = GetConfirmedFractalLevel(false, InpTrailFractalRank);
            double slPrice       = (oppFractalLow > 0.0 && oppFractalLow < entryPrice)
                                   ? oppFractalLow - (InpSLBufferPips * ExtPipValue)
                                   : teethAtCurrent - (InpSLBufferPips * ExtPipValue);
            slPrice = NormalizeDouble(slPrice, _Digits);

            double slDistPips = (entryPrice - slPrice) / ExtPipValue;

            if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
            {
               double tpPrice = 0.0;
               if(InpRewardRiskRatio > 0.0)
                  tpPrice = NormalizeDouble(entryPrice + (entryPrice - slPrice) * InpRewardRiskRatio, _Digits);

               double lots = CalculateLotSize(entryPrice - slPrice);
               PlaceOrReplaceStopOrder(ORDER_TYPE_BUY_STOP, lots, entryPrice, slPrice, tpPrice);
            }
         }
      }
   }

   //================================================================
   // BEARISH SETUP: Confirmed Down Fractal at Bar 3 + Awake Alligator
   //================================================================
   if(dnFracBuf[0] != EMPTY_VALUE && dnFracBuf[0] < teethAtFractal)
   {
      // Verify Alligator is Awake for Bearish Breakout
      if(CheckAlligatorAwakeFilter(false))
      {
         double fractalLow = dnFracBuf[0];
         double entryPrice = NormalizeDouble(fractalLow - (InpEntryBufferPips * ExtPipValue), _Digits);

         // Ensure price hasn't already broken below the entry level and remains below Teeth
         if(entryPrice < bid && fractalLow < teethAtCurrent && !HasOpenPosition(POSITION_TYPE_SELL))
         {
            double oppFractalHigh = GetConfirmedFractalLevel(true, InpTrailFractalRank);
            double slPrice        = (oppFractalHigh > 0.0 && oppFractalHigh > entryPrice)
                                    ? oppFractalHigh + (InpSLBufferPips * ExtPipValue) + spread
                                    : teethAtCurrent + (InpSLBufferPips * ExtPipValue) + spread;
            slPrice = NormalizeDouble(slPrice, _Digits);

            double slDistPips = (slPrice - entryPrice) / ExtPipValue;

            if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
            {
               double tpPrice = 0.0;
               if(InpRewardRiskRatio > 0.0)
                  tpPrice = NormalizeDouble(entryPrice - (slPrice - entryPrice) * InpRewardRiskRatio, _Digits);

               double lots = CalculateLotSize(slPrice - entryPrice);
               PlaceOrReplaceStopOrder(ORDER_TYPE_SELL_STOP, lots, entryPrice, slPrice, tpPrice);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Place a New Stop Order or Replace an Older Pending Stop Order    |
//+------------------------------------------------------------------+
void PlaceOrReplaceStopOrder(ENUM_ORDER_TYPE orderType, double lots, double price, double sl, double tp)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber && ExtOrder.OrderType() == orderType)
         {
            ExtTrade.OrderDelete(ExtOrder.Ticket());
         }
      }
   }

   ENUM_ORDER_TYPE_TIME timeType = ORDER_TIME_GTC;
   datetime expiration = 0;

   if(InpOrderExpirationHrs > 0)
   {
      timeType   = ORDER_TIME_SPECIFIED;
      expiration = TimeCurrent() + (InpOrderExpirationHrs * 3600);
   }

   if(orderType == ORDER_TYPE_BUY_STOP)
      ExtTrade.BuyStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Up Fractal [Awake]");
   else if(orderType == ORDER_TYPE_SELL_STOP)
      ExtTrade.SellStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Down Fractal [Awake]");
}

//+------------------------------------------------------------------+
//| Cancel Pending Orders if Price Crosses Teeth or Alligator Sleeps |
//+------------------------------------------------------------------+
void ValidatePendingOrders()
{
   double close1 = iClose(_Symbol, _Period, 1);
   double jaw1 = 0.0, teeth1 = 0.0, lips1 = 0.0;
   if(!GetAlligatorLinesAtBar(1, jaw1, teeth1, lips1))
      return;

   bool awakeBull = IsAlligatorAwakeAtBar(1, true);
   bool awakeBear = IsAlligatorAwakeAtBar(1, false);

   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber)
         {
            //--- Cancel pending Buy Stop if price closes below Teeth OR Alligator falls asleep
            if(ExtOrder.OrderType() == ORDER_TYPE_BUY_STOP)
            {
               if(close1 < teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBull))
                  ExtTrade.OrderDelete(ExtOrder.Ticket());
            }

            //--- Cancel pending Sell Stop if price closes above Teeth OR Alligator falls asleep
            if(ExtOrder.OrderType() == ORDER_TYPE_SELL_STOP)
            {
               if(close1 > teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBear))
                  ExtTrade.OrderDelete(ExtOrder.Ticket());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Trail Open Positions Behind Confirmed Opposite Fractals          |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
   double ask       = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid       = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread    = ask - bid;
   double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;

   double trailDownFractal = GetConfirmedFractalLevel(false, InpTrailFractalRank);
   double trailUpFractal   = GetConfirmedFractalLevel(true,  InpTrailFractalRank);

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber)
            continue;

         ulong  ticket    = ExtPosition.Ticket();
         double currentSL = ExtPosition.StopLoss();
         double currentTP = ExtPosition.TakeProfit();

         //--- Trail Long Position behind N-th Down Fractal
         if(ExtPosition.PositionType() == POSITION_TYPE_BUY && trailDownFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailDownFractal - (InpSLBufferPips * ExtPipValue), _Digits);
            if(newSL > currentSL + _Point && (bid - newSL) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
         //--- Trail Short Position behind N-th Up Fractal
         else if(ExtPosition.PositionType() == POSITION_TYPE_SELL && trailUpFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailUpFractal + (InpSLBufferPips * ExtPipValue) + spread, _Digits);
            if((currentSL == 0.0 || newSL < currentSL - _Point) && (newSL - ask) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check if a Position of Given Direction is Already Open           |
//+------------------------------------------------------------------+
bool HasOpenPosition(ENUM_POSITION_TYPE posType)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() == _Symbol &&
            ExtPosition.Magic() == InpMagicNumber &&
            ExtPosition.PositionType() == posType)
            return(true);
      }
   }
   return(false);
}

//+------------------------------------------------------------------+
//| Dynamic Position Sizing Based on Account Risk % and SL Distance  |
//+------------------------------------------------------------------+
double CalculateLotSize(double slPriceDistance)
{
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(InpRiskPercent <= 0.0 || slPriceDistance <= 0.0)
      return(MathMax(minLot, MathMin(maxLot, InpFixedLot)));

   double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);

   if(tickSize <= 0.0 || tickValue <= 0.0)
      return(minLot);

   double moneyRiskPerLot = (slPriceDistance / tickSize) * tickValue;
   if(moneyRiskPerLot <= 0.0)
      return(minLot);

   double rawLots = riskMoney / moneyRiskPerLot;
   double steppedLots = MathFloor(rawLots / lotStep) * lotStep;

   return(NormalizeDouble(MathMax(minLot, MathMin(maxLot, steppedLots)), 2));
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

To transform the Expert Advisor into a full Trading Chaos Multi-Indicator Pyramid System, this update integrates Bill Williams' Awesome Oscillator (AO).

In Trading Chaos, the Awesome Oscillator (a 34-period Simple Moving Average of the median price subtracted from a 5-period Simple Moving Average of the median price) serves as the primary momentum engine. This version adds three distinct AO scaling and entry filters, allowing you to scale into established trends or catch early momentum surges:

AO Zero-Line Cross (Trend Initiation): Requires the AO value at the signal bar to be strictly positive for a Buy (above 0.0) or negative for a Sell (below 0.0), ensuring trades only fire in the direction of the macro momentum cycle.

AO Saucer Signal (Continuation / Pyramiding): Bill Williams' famous "Saucer" setup requires a three-bar sequence where the AO dips and turns back in the trend direction (e.g., Red bar followed by two Green bars with higher values for a bullish saucer).

AO Twin Peaks / Peak Crossing: Requires the AO momentum histogram to print a higher peak or cross a previous momentum threshold in the direction of the breakout.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7644
Joined: Mon Jul 20, 2026 1:28 pm

Re: The Truth About Bill Williams Fractals

Post by PTScalper »

Updated MQL5 Source Code (WilliamsFractalChaosEA.mq5 v1.20)

Replace your existing code in MetaEditor with this updated version and press Compile (F7).

Code: Select all

//+------------------------------------------------------------------+
//|                                       WilliamsFractalChaosEA.mq5 |
//|    Bill Williams Fractal + Alligator Awake + AO Pyramiding (v1.20)|
//+------------------------------------------------------------------+
#property copyright "Forex Strategy Deep Dive"
#property version   "1.20"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>

//--- Enum for Alligator Evaluation Bar
enum ENUM_AWAKE_EVAL_MODE
{
   AWAKE_AT_SIGNAL_BAR  = 0, // Check at Bar 1 (When Fractal Confirms)
   AWAKE_AT_FRACTAL_BAR = 1, // Check at Bar 3 (At Fractal Peak/Trough)
   AWAKE_AT_BOTH_BARS   = 2  // Require Awake State at Both Bar 3 and Bar 1
};

//--- Enum for Awesome Oscillator Filter Modes
enum ENUM_AO_FILTER_MODE
{
   AO_FILTER_NONE          = 0, // Disabled (Fractals + Alligator Only)
   AO_FILTER_ZERO_LINE     = 1, // Require AO > 0 (Buy) or AO < 0 (Sell)
   AO_FILTER_SAUCER        = 2, // Require AO Saucer Pattern (Bullish/Bearish)
   AO_FILTER_ZERO_OR_SAUCER= 3  // Require either Zero-Line alignment OR Saucer pattern
};

//--- Input Parameters: Alligator Settings
input group "=== Alligator Core Settings ==="
input int                   InpJawPeriod            = 13;                // Jaw Period (Blue)
input int                   InpJawShift             = 8;                 // Jaw Future Shift
input int                   InpTeethPeriod          = 8;                 // Teeth Period (Red - Filter)
input int                   InpTeethShift           = 5;                 // Teeth Future Shift
input int                   InpLipsPeriod           = 5;                 // Lips Period (Green)
input int                   InpLipsShift            = 3;                 // Lips Future Shift
input ENUM_MA_METHOD        InpAlligatorMethod      = MODE_SMMA;         // Smoothing Method
input ENUM_APPLIED_PRICE    InpAlligatorPrice       = PRICE_MEDIAN;      // Applied Price ((H+L)/2)

//--- Input Parameters: Alligator 'Awake' Mouth-Width Filter
input group "=== Alligator 'Awake' Filter Settings ==="
input bool                  InpUseAwakeFilter       = true;              // Enable Alligator Awake Filter
input ENUM_AWAKE_EVAL_MODE  InpAwakeEvalMode        = AWAKE_AT_SIGNAL_BAR;// Bar to Check Awake Condition
input bool                  InpRequireStrictFan     = true;              // Require Lips/Teeth/Jaw Fan Alignment
input double                InpMinMouthWidthPips    = 3.0;               // Min Total Mouth Width |Lips - Jaw| (Pips)
input double                InpMinAdjacentGapPips   = 0.8;               // Min Gap Between Adjacent Lines (Pips)
input int                   InpATRPeriod            = 14;                // ATR Period for Dynamic Width Filter
input double                InpMinMouthATRMult      = 0.25;              // Min Mouth Width as ATR Multiple (0 = Off)
input bool                  InpDeleteOrderIfSleeping= true;              // Cancel Pending Orders if Alligator Sleeps

//--- Input Parameters: Awesome Oscillator (AO) Settings
input group "=== Awesome Oscillator (AO) Settings ==="
input ENUM_AO_FILTER_MODE   InpAOFilterMode         = AO_FILTER_ZERO_LINE;// AO Filter Mode
input int                   InpAOFastPeriod         = 5;                 // AO Fast MA Period
input int                   InpAOSlowPeriod         = 34;                // AO Slow MA Period
input bool                  InpAllowPyramiding      = true;              // Allow Multiple Positions (Pyramiding via New Fractals)
input int                   InpMaxPyramidTrades     = 3;                 // Max Concurrent Pyramiding Positions

//--- Input Parameters: Fractal & Execution Rules
input group "=== Fractal Breakout & Stop Rules ==="
input double                InpEntryBufferPips      = 1.5;               // Breakout Buffer Above/Below Fractal (Pips)
input double                InpSLBufferPips         = 2.0;               // Stop-Loss Buffer Beyond Opposite Fractal (Pips)
input int                   InpTrailFractalRank     = 2;                 // Trail Behind N-th Opposite Fractal (1 or 2)
input int                   InpFractalLookback      = 100;               // Max Bars to Search for Opposite Fractals
input int                   InpOrderExpirationHrs   = 48;                // Pending Order Expiration (Hours, 0 = GTC)
input double                InpMinSLDistancePips    = 10.0;              // Minimum Stop-Loss Distance (Pips)
input double                InpMaxSLDistancePips    = 150.0;             // Maximum Stop-Loss Distance (Pips)

//--- Input Parameters: Risk & Trade Management
input group "=== Risk & Money Management ==="
input double                InpRiskPercent          = 1.0;               // Risk Per Trade (% of Balance, 0 = Fixed Lot)
input double                InpFixedLot             = 0.10;              // Fixed Lot Size (if Risk % is 0)
input double                InpRewardRiskRatio      = 0.0;               // Take Profit R:R Ratio (0 = Disable, Trail Only)
input ulong                 InpMagicNumber          = 199505;            // EA Magic Number
input ulong                 InpSlippagePoints       = 10;                // Max Slippage (Points)

//--- Global Variables & Handles
CTrade         ExtTrade;
CPositionInfo  ExtPosition;
COrderInfo     ExtOrder;

int            ExtFractalsHandle  = INVALID_HANDLE;
int            ExtAlligatorHandle = INVALID_HANDLE;
int            ExtATRHandle       = INVALID_HANDLE;
int            ExtAOHandle        = INVALID_HANDLE;
datetime       ExtLastBarTime     = 0;
double         ExtPipValue        = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   ExtTrade.SetExpertMagicNumber(InpMagicNumber);
   ExtTrade.SetDeviationInPoints(InpSlippagePoints);
   ExtTrade.SetTypeFillingBySymbol(_Symbol);

   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   ExtPipValue = (digits == 3 || digits == 5) ? _Point * 10.0 : _Point;

   //--- Initialize Indicators
   ExtFractalsHandle = iFractals(_Symbol, _Period);
   if(ExtFractalsHandle == INVALID_HANDLE) { Print("Error iFractals"); return(INIT_FAILED); }

   ExtAlligatorHandle = iAlligator(_Symbol, _Period,
                                   InpJawPeriod, InpJawShift,
                                   InpTeethPeriod, InpTeethShift,
                                   InpLipsPeriod, InpLipsShift,
                                   InpAlligatorMethod, InpAlligatorPrice);
   if(ExtAlligatorHandle == INVALID_HANDLE) { Print("Error iAlligator"); return(INIT_FAILED); }

   ExtATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(ExtATRHandle == INVALID_HANDLE) { Print("Error iATR"); return(INIT_FAILED); }

   ExtAOHandle = iAO(_Symbol, _Period);
   if(ExtAOHandle == INVALID_HANDLE) { Print("Error iAO"); return(INIT_FAILED); }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(ExtFractalsHandle  != INVALID_HANDLE) IndicatorRelease(ExtFractalsHandle);
   if(ExtAlligatorHandle != INVALID_HANDLE) IndicatorRelease(ExtAlligatorHandle);
   if(ExtATRHandle       != INVALID_HANDLE) IndicatorRelease(ExtATRHandle);
   if(ExtAOHandle        != INVALID_HANDLE) IndicatorRelease(ExtAOHandle);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime == ExtLastBarTime)
      return;

   int maxShift = MathMax(InpJawShift, MathMax(InpTeethShift, InpLipsShift));
   if(Bars(_Symbol, _Period) < InpFractalLookback + maxShift + 10)
      return;

   ExtLastBarTime = currentBarTime;

   ManageOpenPositions();
   ValidatePendingOrders();
   CheckForFractalSignals();
}

//+------------------------------------------------------------------+
//| Retrieve Visually Aligned Alligator Lines                        |
//+------------------------------------------------------------------+
bool GetAlligatorLinesAtBar(int barIdx, double &jaw, double &teeth, double &lips)
{
   double jawBuf[], teethBuf[], lipsBuf[];
   ArraySetAsSeries(jawBuf,   true);
   ArraySetAsSeries(teethBuf, true);
   ArraySetAsSeries(lipsBuf,  true);

   if(CopyBuffer(ExtAlligatorHandle, 0, barIdx + InpJawShift,   1, jawBuf)   <= 0) return(false);
   if(CopyBuffer(ExtAlligatorHandle, 1, barIdx + InpTeethShift, 1, teethBuf) <= 0) return(false);
   if(CopyBuffer(ExtAlligatorHandle, 2, barIdx + InpLipsShift,  1, lipsBuf)  <= 0) return(false);

   jaw   = jawBuf[0];
   teeth = teethBuf[0];
   lips  = lipsBuf[0];

   return(jaw > 0.0 && teeth > 0.0 && lips > 0.0);
}

//+------------------------------------------------------------------+
//| Check Alligator Awake Condition                                  |
//+------------------------------------------------------------------+
bool IsAlligatorAwakeAtBar(int barIdx, bool isBullish)
{
   double jaw = 0.0, teeth = 0.0, lips = 0.0;
   if(!GetAlligatorLinesAtBar(barIdx, jaw, teeth, lips))
      return(false);

   if(InpRequireStrictFan)
   {
      if(isBullish && !(lips > teeth && teeth > jaw)) return(false);
      if(!isBullish && !(lips < teeth && teeth < jaw)) return(false);
   }

   double totalMouthWidth = MathAbs(lips - jaw);
   if(totalMouthWidth < (InpMinMouthWidthPips * ExtPipValue)) return(false);

   if(MathAbs(lips - teeth) < (InpMinAdjacentGapPips * ExtPipValue) ||
      MathAbs(teeth - jaw)  < (InpMinAdjacentGapPips * ExtPipValue)) return(false);

   if(InpMinMouthATRMult > 0.0)
   {
      double atrBuf[];
      ArraySetAsSeries(atrBuf, true);
      if(CopyBuffer(ExtATRHandle, 0, barIdx, 1, atrBuf) <= 0) return(false);
      if(totalMouthWidth < (atrBuf[0] * InpMinMouthATRMult)) return(false);
   }

   return(true);
}

bool CheckAlligatorAwakeFilter(bool isBullish)
{
   if(!InpUseAwakeFilter) return(true);

   switch(InpAwakeEvalMode)
   {
      case AWAKE_AT_SIGNAL_BAR:  return IsAlligatorAwakeAtBar(1, isBullish);
      case AWAKE_AT_FRACTAL_BAR: return IsAlligatorAwakeAtBar(3, isBullish);
      case AWAKE_AT_BOTH_BARS:   return (IsAlligatorAwakeAtBar(3, isBullish) && IsAlligatorAwakeAtBar(1, isBullish));
   }
   return(false);
}

//+------------------------------------------------------------------+
//| Awesome Oscillator (AO) Filter Validation                        |
//+------------------------------------------------------------------+
bool CheckAwesomeOscillatorFilter(bool isBullish)
{
   if(InpAOFilterMode == AO_FILTER_NONE)
      return(true);

   double aoBuf[];
   ArraySetAsSeries(aoBuf, true);
   // Copy 4 bars of AO (Index 1 = Bar 1 closed, Index 2, 3 for saucer detection)
   if(CopyBuffer(ExtAOHandle, 0, 1, 4, aoBuf) < 4)
      return(false);

   double ao1 = aoBuf[0]; // Bar 1
   double ao2 = aoBuf[1]; // Bar 2
   double ao3 = aoBuf[2]; // Bar 3

   bool zeroLinePass = false;
   bool saucerPass   = false;

   //--- 1. Zero-Line Check
   if(isBullish && ao1 > 0.0) zeroLinePass = true;
   if(!isBullish && ao1 < 0.0) zeroLinePass = true;

   //--- 2. Saucer Check (Bill Williams rules)
   // Bullish Saucer: AO is above zero (or dipping), Bar 3 > Bar 2, and Bar 1 > Bar 2 (Red bar surrounded by two green bars)
   if(isBullish)
   {
      if(ao2 < ao3 && ao1 > ao2 && ao1 > 0.0)
         saucerPass = true;
   }
   // Bearish Saucer: AO is below zero, Bar 3 < Bar 2, and Bar 1 < Bar 2 (Green bar surrounded by two red bars)
   else
   {
      if(ao2 > ao3 && ao1 < ao2 && ao1 < 0.0)
         saucerPass = true;
   }

   //--- 3. Apply Filter Mode Evaluation
   switch(InpAOFilterMode)
   {
      case AO_FILTER_ZERO_LINE:      return zeroLinePass;
      case AO_FILTER_SAUCER:         return saucerPass;
      case AO_FILTER_ZERO_OR_SAUCER: return (zeroLinePass || saucerPass);
   }

   return(true);
}

//+------------------------------------------------------------------+
//| Find N-th Most Recent Confirmed Fractal                          |
//+------------------------------------------------------------------+
double GetConfirmedFractalLevel(bool isUpFractal, int rank)
{
   double fracBuf[];
   ArraySetAsSeries(fracBuf, true);
   int bufferNum = isUpFractal ? 0 : 1;
   if(CopyBuffer(ExtFractalsHandle, bufferNum, 0, InpFractalLookback, fracBuf) <= 0)
      return(0.0);

   int foundCount = 0;
   for(int i = 3; i < InpFractalLookback; i++)
   {
      if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
      {
         foundCount++;
         if(foundCount >= rank)
            return(fracBuf[i]);
      }
   }

   if(rank > 1 && foundCount == 1)
   {
      for(int i = 3; i < InpFractalLookback; i++)
         if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
            return(fracBuf[i]);
   }

   return(0.0);
}

//+------------------------------------------------------------------+
//| Evaluate Fractals + Alligator + AO and Place Orders              |
//+------------------------------------------------------------------+
void CheckForFractalSignals()
{
   double upFracBuf[], dnFracBuf[];
   ArraySetAsSeries(upFracBuf, true);
   ArraySetAsSeries(dnFracBuf, true);

   if(CopyBuffer(ExtFractalsHandle, 0, 3, 1, upFracBuf) <= 0) return;
   if(CopyBuffer(ExtFractalsHandle, 1, 3, 1, dnFracBuf) <= 0) return;

   double jaw3 = 0.0, teethAtFractal = 0.0, lips3 = 0.0;
   double jaw1 = 0.0, teethAtCurrent = 0.0, lips1 = 0.0;

   if(!GetAlligatorLinesAtBar(3, jaw3, teethAtFractal, lips3)) return;
   if(!GetAlligatorLinesAtBar(1, jaw1, teethAtCurrent, lips1)) return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread = ask - bid;

   int openCount = CountOpenPositions();
   if(!InpAllowPyramiding && openCount > 0)
      return;
   if(openCount >= InpMaxPyramidTrades)
      return;

   //================================================================
   // BULLISH SETUP: Up Fractal + Awake Alligator + AO Filter
   //================================================================
   if(upFracBuf[0] != EMPTY_VALUE && upFracBuf[0] > teethAtFractal)
   {
      if(CheckAlligatorAwakeFilter(true) && CheckAwesomeOscillatorFilter(true))
      {
         double fractalHigh = upFracBuf[0];
         double entryPrice  = NormalizeDouble(fractalHigh + (InpEntryBufferPips * ExtPipValue) + spread, _Digits);

         if(entryPrice > ask && fractalHigh > teethAtCurrent && !HasOpenPositionDirection(POSITION_TYPE_BUY, fractalHigh))
         {
            double oppFractalLow = GetConfirmedFractalLevel(false, InpTrailFractalRank);
            double slPrice       = (oppFractalLow > 0.0 && oppFractalLow < entryPrice)
                                   ? oppFractalLow - (InpSLBufferPips * ExtPipValue)
                                   : teethAtCurrent - (InpSLBufferPips * ExtPipValue);
            slPrice = NormalizeDouble(slPrice, _Digits);

            double slDistPips = (entryPrice - slPrice) / ExtPipValue;

            if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
            {
               double tpPrice = 0.0;
               if(InpRewardRiskRatio > 0.0)
                  tpPrice = NormalizeDouble(entryPrice + (entryPrice - slPrice) * InpRewardRiskRatio, _Digits);

               double lots = CalculateLotSize(entryPrice - slPrice);
               PlaceOrReplaceStopOrder(ORDER_TYPE_BUY_STOP, lots, entryPrice, slPrice, tpPrice);
            }
         }
      }
   }

   //================================================================
   // BEARISH SETUP: Down Fractal + Awake Alligator + AO Filter
   //================================================================
   if(dnFracBuf[0] != EMPTY_VALUE && dnFracBuf[0] < teethAtFractal)
   {
      if(CheckAlligatorAwakeFilter(false) && CheckAwesomeOscillatorFilter(false))
      {
         double fractalLow = dnFracBuf[0];
         double entryPrice = NormalizeDouble(fractalLow - (InpEntryBufferPips * ExtPipValue), _Digits);

         if(entryPrice < bid && fractalLow < teethAtCurrent && !HasOpenPositionDirection(POSITION_TYPE_SELL, fractalLow))
         {
            double oppFractalHigh = GetConfirmedFractalLevel(true, InpTrailFractalRank);
            double slPrice        = (oppFractalHigh > 0.0 && oppFractalHigh > entryPrice)
                                    ? oppFractalHigh + (InpSLBufferPips * ExtPipValue) + spread
                                    : teethAtCurrent + (InpSLBufferPips * ExtPipValue) + spread;
            slPrice = NormalizeDouble(slPrice, _Digits);

            double slDistPips = (slPrice - entryPrice) / ExtPipValue;

            if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
            {
               double tpPrice = 0.0;
               if(InpRewardRiskRatio > 0.0)
                  tpPrice = NormalizeDouble(entryPrice - (slPrice - entryPrice) * InpRewardRiskRatio, _Digits);

               double lots = CalculateLotSize(slPrice - entryPrice);
               PlaceOrReplaceStopOrder(ORDER_TYPE_SELL_STOP, lots, entryPrice, slPrice, tpPrice);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Place or Replace Stop Orders                                     |
//+------------------------------------------------------------------+
void PlaceOrReplaceStopOrder(ENUM_ORDER_TYPE orderType, double lots, double price, double sl, double tp)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber && ExtOrder.OrderType() == orderType)
         {
            ExtTrade.OrderDelete(ExtOrder.Ticket());
         }
      }
   }

   ENUM_ORDER_TYPE_TIME timeType = ORDER_TIME_GTC;
   datetime expiration = 0;
   if(InpOrderExpirationHrs > 0)
   {
      timeType   = ORDER_TIME_SPECIFIED;
      expiration = TimeCurrent() + (InpOrderExpirationHrs * 3600);
   }

   if(orderType == ORDER_TYPE_BUY_STOP)
      ExtTrade.BuyStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Up Pyramid");
   else if(orderType == ORDER_TYPE_SELL_STOP)
      ExtTrade.SellStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Down Pyramid");
}

//+------------------------------------------------------------------+
//| Validate Pending Orders                                          |
//+------------------------------------------------------------------+
void ValidatePendingOrders()
{
   double close1 = iClose(_Symbol, _Period, 1);
   double jaw1 = 0.0, teeth1 = 0.0, lips1 = 0.0;
   if(!GetAlligatorLinesAtBar(1, jaw1, teeth1, lips1)) return;

   bool awakeBull = IsAlligatorAwakeAtBar(1, true);
   bool awakeBear = IsAlligatorAwakeAtBar(1, false);

   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(ExtOrder.SelectByIndex(i))
      {
         if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber)
         {
            if(ExtOrder.OrderType() == ORDER_TYPE_BUY_STOP)
            {
               if(close1 < teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBull))
                  ExtTrade.OrderDelete(ExtOrder.Ticket());
            }
            if(ExtOrder.OrderType() == ORDER_TYPE_SELL_STOP)
            {
               if(close1 > teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBear))
                  ExtTrade.OrderDelete(ExtOrder.Ticket());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Manage Open Positions & Trailing Stops                           |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
   double ask       = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid       = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread    = ask - bid;
   double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;

   double trailDownFractal = GetConfirmedFractalLevel(false, InpTrailFractalRank);
   double trailUpFractal   = GetConfirmedFractalLevel(true,  InpTrailFractalRank);

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber)
            continue;

         ulong  ticket    = ExtPosition.Ticket();
         double currentSL = ExtPosition.StopLoss();
         double currentTP = ExtPosition.TakeProfit();

         if(ExtPosition.PositionType() == POSITION_TYPE_BUY && trailDownFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailDownFractal - (InpSLBufferPips * ExtPipValue), _Digits);
            if(newSL > currentSL + _Point && (bid - newSL) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
         else if(ExtPosition.PositionType() == POSITION_TYPE_SELL && trailUpFractal > 0.0)
         {
            double newSL = NormalizeDouble(trailUpFractal + (InpSLBufferPips * ExtPipValue) + spread, _Digits);
            if((currentSL == 0.0 || newSL < currentSL - _Point) && (newSL - ask) > stopLevel)
            {
               ExtTrade.PositionModify(ticket, newSL, currentTP);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper Counts & Position Checks                                  |
//+------------------------------------------------------------------+
int CountOpenPositions()
{
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() == _Symbol && ExtPosition.Magic() == InpMagicNumber)
            count++;
      }
   }
   return(count);
}

bool HasOpenPositionDirection(ENUM_POSITION_TYPE posType, double fractalPrice)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(ExtPosition.SelectByIndex(i))
      {
         if(ExtPosition.Symbol() == _Symbol &&
            ExtPosition.Magic() == InpMagicNumber &&
            ExtPosition.PositionType() == posType)
         {
            // Prevent duplicate entries for the exact same fractal level
            if(MathAbs(ExtPosition.PriceOpen() - fractalPrice) < (_Point * 2.0))
               return(true);
         }
      }
   }
   return(false);
}

//+------------------------------------------------------------------+
//| Dynamic Position Sizing                                          |
//+------------------------------------------------------------------+
double CalculateLotSize(double slPriceDistance)
{
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(InpRiskPercent <= 0.0 || slPriceDistance <= 0.0)
      return(MathMax(minLot, MathMin(maxLot, InpFixedLot)));

   double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);

   if(tickSize <= 0.0 || tickValue <= 0.0) return(minLot);

   double moneyRiskPerLot = (slPriceDistance / tickSize) * tickValue;
   if(moneyRiskPerLot <= 0.0) return(minLot);

   double rawLots = riskMoney / moneyRiskPerLot;
   double steppedLots = MathFloor(rawLots / lotStep) * lotStep;

   return(NormalizeDouble(MathMax(minLot, MathMin(maxLot, steppedLots)), 2));
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply