//+------------------------------------------------------------------+
//|                                       BrokenLegTrapReversal.mq5   |
//|  Trend-continuation "broken leg" trap reversal EA.               |
//|  Converted from the Algobot C# strategy of the same name.        |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>
CTrade trade;

//--- inputs (mirror DescribeParameters / OnInit) ---------------------
input int    SwingLookback  = 3;      // fractal swing lookback (bars each side)
input int    TrendEmaPeriod = 50;     // EMA trend filter period
input int    ReclaimBars    = 3;      // reclaim window after a leg break
input int    AtrPeriod      = 14;     // ATR period
input double AtrSlMult      = 1.0;    // ATR multiple parked beyond trap extreme
input double RewardMult     = 2.0;    // reward as R-multiple of risk
input double Lots           = 0.10;   // fixed volume
input long   Magic          = 720104; // magic number

//--- indicator handles ----------------------------------------------
int g_emaHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;

//--- decision base shift: the just-closed bar (one decision per closed bar)
#define BASE_SHIFT 1

//--- monotonic bar counter (mirrors C# _barCount) --------------------
long g_barCount = 0;

//--- most recently confirmed fractal swings --------------------------
double g_swingLow = 0.0, g_swingHigh = 0.0;
long   g_swingLowIdx = -1, g_swingHighIdx = -1;
bool   g_haveSwingLow = false, g_haveSwingHigh = false;

//--- long trap: leg broken BELOW a swing low in an uptrend, awaiting reclaim
bool   g_longActive = false;
long   g_longBreakIdx = 0;
double g_longTrapLow = 0.0, g_longBrokenLevel = 0.0;
long   g_longArmedSwingIdx = -1;

//--- short trap: leg broken ABOVE a swing high in a downtrend, awaiting reclaim
bool   g_shortActive = false;
long   g_shortBreakIdx = 0;
double g_shortTrapHigh = 0.0, g_shortBrokenLevel = 0.0;
long   g_shortArmedSwingIdx = -1;

//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);

    g_emaHandle = iMA(_Symbol, _Period, TrendEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);
    if(g_emaHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
        return INIT_FAILED;

    g_barCount = 0;
    g_haveSwingLow = false; g_haveSwingHigh = false;
    g_longActive   = false; g_shortActive  = false;
    g_longArmedSwingIdx = -1; g_shortArmedSwingIdx = -1;

    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;                 // one decision per newly closed bar
    g_barCount++;                           // mirrors C# _barCount++ per new bar

    int need = MathMax(TrendEmaPeriod + 2, 2 * SwingLookback + 2);
    if(Bars(_Symbol, _Period) < need + BASE_SHIFT) return;

    //--- indicators (newest-last -> series indexing) ---
    double emaBuf[]; double atrBuf[];
    ArraySetAsSeries(emaBuf, true);
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_emaHandle, 0, 0, BASE_SHIFT + 2, emaBuf) < BASE_SHIFT + 2) return;
    if(CopyBuffer(g_atrHandle, 0, 0, BASE_SHIFT + 1, atrBuf) < BASE_SHIFT + 1) return;

    double ema     = emaBuf[BASE_SHIFT];        // EMA at the just-closed bar
    double emaPrev = emaBuf[BASE_SHIFT + 1];     // EMA at the prior bar (closeSpan[..^1])
    double atr     = atrBuf[BASE_SHIFT];
    if(atr <= 0.0) return;

    double close   = iClose(_Symbol, _Period, BASE_SHIFT);
    double barHigh = iHigh(_Symbol,  _Period, BASE_SHIFT);
    double barLow  = iLow(_Symbol,   _Period, BASE_SHIFT);

    //--- confirm a fractal pivot 'SwingLookback' bars back (equal bars each side) ---
    // pivot sits at shift BASE_SHIFT + SwingLookback; the window spans
    // [BASE_SHIFT .. BASE_SHIFT + 2*SwingLookback], pivot compared to all others.
    int pivotShift = BASE_SHIFT + SwingLookback;
    if(Bars(_Symbol, _Period) >= BASE_SHIFT + 2 * SwingLookback + 1)
    {
        double pl = iLow(_Symbol,  _Period, pivotShift);
        double ph = iHigh(_Symbol, _Period, pivotShift);
        bool isLow = true, isHigh = true;
        for(int s = BASE_SHIFT; s <= BASE_SHIFT + 2 * SwingLookback; s++)
        {
            if(s == pivotShift) continue;
            if(iLow(_Symbol,  _Period, s) <= pl) isLow  = false;
            if(iHigh(_Symbol, _Period, s) >= ph) isHigh = false;
        }
        long pivotIdx = g_barCount - 1 - SwingLookback;
        if(isLow)  { g_swingLow  = pl; g_swingLowIdx  = pivotIdx; g_haveSwingLow  = true; }
        if(isHigh) { g_swingHigh = ph; g_swingHighIdx = pivotIdx; g_haveSwingHigh = true; }
    }

    bool upTrend   = (close > ema && ema > emaPrev);
    bool downTrend = (close < ema && ema < emaPrev);

    //=========== LONG: leg broken below a swing low, then reclaimed ===========
    if(!g_longActive && g_haveSwingLow && upTrend
       && g_swingLowIdx != g_longArmedSwingIdx
       && barLow < g_swingLow && close < g_swingLow)   // pierced and still below at close => broken leg
    {
        g_longActive       = true;
        g_longBreakIdx     = g_barCount;
        g_longTrapLow      = barLow;
        g_longBrokenLevel  = g_swingLow;
        g_longArmedSwingIdx = g_swingLowIdx;             // don't re-arm on the same swing
    }
    else if(g_longActive)
    {
        g_longTrapLow = MathMin(g_longTrapLow, barLow);
        if(close > g_longBrokenLevel)                    // reclaimed => bull trap sprung, join uptrend
        {
            TryEnterLong(close, g_longTrapLow, atr);
            g_longActive = false;
        }
        else if(g_barCount - g_longBreakIdx >= ReclaimBars)
        {
            g_longActive = false;                        // reclaim window expired -> genuine breakdown
        }
    }

    //=========== SHORT: leg broken above a swing high, then reclaimed ===========
    if(!g_shortActive && g_haveSwingHigh && downTrend
       && g_swingHighIdx != g_shortArmedSwingIdx
       && barHigh > g_swingHigh && close > g_swingHigh)
    {
        g_shortActive       = true;
        g_shortBreakIdx     = g_barCount;
        g_shortTrapHigh     = barHigh;
        g_shortBrokenLevel  = g_swingHigh;
        g_shortArmedSwingIdx = g_swingHighIdx;
    }
    else if(g_shortActive)
    {
        g_shortTrapHigh = MathMax(g_shortTrapHigh, barHigh);
        if(close < g_shortBrokenLevel)                   // reclaimed => bear trap sprung, join downtrend
        {
            TryEnterShort(close, g_shortTrapHigh, atr);
            g_shortActive = false;
        }
        else if(g_barCount - g_shortBreakIdx >= ReclaimBars)
        {
            g_shortActive = false;
        }
    }
}

//+------------------------------------------------------------------+
void TryEnterLong(double entry, double trapLow, double atr)
{
    if(HasPosition(Magic)) return;
    double sl = trapLow - AtrSlMult * atr;
    double risk = entry - sl;
    if(risk <= 0.0) return;
    double tp = entry + RewardMult * risk;

    sl = NormalizeDouble(sl, _Digits);
    tp = NormalizeDouble(tp, _Digits);
    trade.Buy(Lots, _Symbol, 0.0, sl, tp, "BrokenLegTrap-L");   // price 0 => market at Ask
}

//+------------------------------------------------------------------+
void TryEnterShort(double entry, double trapHigh, double atr)
{
    if(HasPosition(Magic)) return;
    double sl = trapHigh + AtrSlMult * atr;
    double risk = sl - entry;
    if(risk <= 0.0) return;
    double tp = entry - RewardMult * risk;

    sl = NormalizeDouble(sl, _Digits);
    tp = NormalizeDouble(tp, _Digits);
    trade.Sell(Lots, _Symbol, 0.0, sl, tp, "BrokenLegTrap-S");  // price 0 => market at Bid
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    if(g_emaHandle != INVALID_HANDLE) IndicatorRelease(g_emaHandle);
    if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
}

//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
bool HasPosition(long magic)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic) return true;
    }
    return false;
}
//+------------------------------------------------------------------+
