//+------------------------------------------------------------------+
//|                                          SwingLevelReclaim.mq5    |
//|                                                          Algobot  |
//|                                       https://www.algobot.live    |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

//  ===========================================================================
//  SwingLevelReclaim  (C# -> MQL5 native EA)
//  ---------------------------------------------------------------------------
//  Support/Resistance (fractal swing pivots) + RSI momentum confirmation.
//  Trades the FALSE break ("sweep & reclaim"):
//    LONG  - price wicks below a confirmed swing-low support (sweep), then a
//            candle CLOSES back above it with RSI turning up. Penetration must
//            be a controlled wick (<= MaxPenetrationAtr * ATR).
//    SHORT - mirror image around a confirmed swing-high resistance.
//  A decisive close beyond the level invalidates (expires) that level instead
//  of triggering a trade. Stops sit just past the swept extreme buffered by
//  ATR; targets use a fixed reward:risk multiple. One position per Magic.
//  ===========================================================================

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

//--- inputs (mirror DescribeParameters) ---
input int    SwingLookback     = 3;      // fractal half-window (bars each side)
input int    ReclaimBars       = 3;      // sweep/reclaim lookback window
input double MaxPenetrationAtr = 0.80;   // max wick beyond level (in ATR)
input int    LevelExpiry       = 60;     // bars a level stays valid
input int    RsiPeriod         = 14;     // RSI period
input double RsiUpper          = 65;     // long momentum ceiling
input double RsiLower          = 35;     // short momentum floor
input int    AtrPeriod         = 14;     // ATR period
input double AtrSlMult         = 0.50;   // ATR buffer added past the extreme
input double RewardRisk        = 1.80;   // TP = RR * risk
input double Lots              = 0.10;   // fixed lot size
input long   Magic             = 7701;   // EA magic number

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

//--- active structure (persisted state, mirrors C# fields) ---
double g_support       = 0.0;
double g_resistance    = 0.0;
bool   g_supportFresh  = false;
bool   g_resistFresh   = false;
long   g_supportBar    = 0;
long   g_resistBar     = 0;
long   g_barCount      = 0;

//+------------------------------------------------------------------+
//| Completed-bar accessors.                                          |
//| C# B(shift): shift 0 = newest COMPLETED bar. In MQL5 IsNewBar()   |
//| fires at bar open, so the last completed bar is index 1 -> we add  |
//| +1 to every C# shift.                                              |
//+------------------------------------------------------------------+
double Cl(int shift){ return iClose(_Symbol, _Period, shift + 1); }
double Op(int shift){ return iOpen (_Symbol, _Period, shift + 1); }
double Hi(int shift){ return iHigh (_Symbol, _Period, shift + 1); }
double Lo(int shift){ return iLow  (_Symbol, _Period, shift + 1); }

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

    g_rsiHandle = iRSI(_Symbol, _Period, RsiPeriod, PRICE_CLOSE);
    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);
    if(g_rsiHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
    {
        Print("SwingLevelReclaim: failed to create indicator handles");
        return INIT_FAILED;
    }

    // reset persisted structure (mirror C# OnInit)
    g_support = g_resistance = 0.0;
    g_supportFresh = g_resistFresh = false;
    g_supportBar = g_resistBar = 0;
    g_barCount = 0;

    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;        // one evaluation per completed bar

    // _barCount++ happens for every new bar (before the "enough data" gate)
    g_barCount++;

    int L = SwingLookback;
    int needed = MathMax(2 * L + 1,
                 MathMax(RsiPeriod + 2,
                 MathMax(AtrPeriod + 2, ReclaimBars + 1)));

    // need enough history; +2 covers the completed-bar (+1) offset
    if(Bars(_Symbol, _Period) < needed + 2) return;

    //--- indicators (RSI for momentum confirmation, ATR for sizing) ---
    double atrBuf[]; ArraySetAsSeries(atrBuf, true);
    double rsiBuf[]; ArraySetAsSeries(rsiBuf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, 3, atrBuf) < 3) return;
    if(CopyBuffer(g_rsiHandle, 0, 0, 3, rsiBuf) < 3) return;

    double atr     = atrBuf[1];    // ATR at last completed bar = C# B(0)
    double rsiNow  = rsiBuf[1];    // RSI at last completed bar  = C# Rsi(closes)
    double rsiPrev = rsiBuf[2];    // RSI one bar earlier        = C# Rsi(closes[..^1])
    if(atr <= 0) return;

    //--- 1) confirm a new fractal swing pivot at shift L ---
    double pivLow = Lo(L), pivHigh = Hi(L);
    bool isSwingLow = true, isSwingHigh = true;
    for(int s = 0; s <= 2 * L; s++)
    {
        if(Lo(s) < pivLow)  isSwingLow  = false;
        if(Hi(s) > pivHigh) isSwingHigh = false;
    }
    if(isSwingLow)  { g_support    = pivLow;  g_supportFresh = true; g_supportBar = g_barCount; }
    if(isSwingHigh) { g_resistance = pivHigh; g_resistFresh  = true; g_resistBar  = g_barCount; }

    //--- 2) expire stale or decisively-broken levels ---
    if(g_supportFresh)
    {
        if(g_barCount - g_supportBar > LevelExpiry)               g_supportFresh = false;
        else if(Cl(0) < g_support - MaxPenetrationAtr * atr)      g_supportFresh = false; // real breakdown
    }
    if(g_resistFresh)
    {
        if(g_barCount - g_resistBar > LevelExpiry)                g_resistFresh = false;
        else if(Cl(0) > g_resistance + MaxPenetrationAtr * atr)   g_resistFresh = false;  // real breakout
    }

    //--- 3) one position per Magic ---
    if(HasPosition(Magic)) return;

    //--- sweep window extremes over the last ReclaimBars bars ---
    double minLow = DBL_MAX, maxHigh = -DBL_MAX;
    for(int s = 0; s < ReclaimBars; s++)
    {
        minLow  = MathMin(minLow,  Lo(s));
        maxHigh = MathMax(maxHigh, Hi(s));
    }

    double curClose = Cl(0);
    double curOpen  = Op(0);

    //--- 4) LONG: sweep below support, reclaim with bullish close, RSI turning up ---
    if(g_supportFresh &&
       minLow < g_support &&
       (g_support - minLow) <= MaxPenetrationAtr * atr &&
       curClose > g_support &&
       curClose > curOpen &&
       rsiNow > rsiPrev && rsiNow < RsiUpper)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = minLow - AtrSlMult * atr;
        double risk  = entry - sl;
        if(risk > 0)
        {
            double tp = entry + RewardRisk * risk;
            g_supportFresh = false;  // consume the level
            double vol = NormalizeLots(Lots);
            if(trade.Buy(vol, _Symbol, 0.0, NormPrice(sl), NormPrice(tp), "SwingLevelReclaim long"))
                PrintFormat("LONG reclaim @ %.5f support=%.5f sweepLow=%.5f sl=%.5f tp=%.5f",
                            entry, g_support, minLow, sl, tp);
        }
        return;
    }

    //--- 5) SHORT: sweep above resistance, reclaim with bearish close, RSI turning down ---
    if(g_resistFresh &&
       maxHigh > g_resistance &&
       (maxHigh - g_resistance) <= MaxPenetrationAtr * atr &&
       curClose < g_resistance &&
       curClose < curOpen &&
       rsiNow < rsiPrev && rsiNow > RsiLower)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = maxHigh + AtrSlMult * atr;
        double risk  = sl - entry;
        if(risk > 0)
        {
            double tp = entry - RewardRisk * risk;
            g_resistFresh = false;  // consume the level
            double vol = NormalizeLots(Lots);
            if(trade.Sell(vol, _Symbol, 0.0, NormPrice(sl), NormPrice(tp), "SwingLevelReclaim short"))
                PrintFormat("SHORT reclaim @ %.5f resistance=%.5f sweepHigh=%.5f sl=%.5f tp=%.5f",
                            entry, g_resistance, maxHigh, sl, tp);
        }
    }
}

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

//+------------------------------------------------------------------+
//| One open position for this symbol + magic?                        |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
//| Lot normalization (C#: round to 2dp, floor 0.01) + broker clamp   |
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
    double v = NormalizeDouble(lots, 2);
    if(v < 0.01) v = 0.01;

    double minv = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxv = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(step > 0) v = MathRound(v / step) * step;
    if(minv > 0 && v < minv) v = minv;
    if(maxv > 0 && v > maxv) v = maxv;
    return v;
}

//+------------------------------------------------------------------+
//| Price normalization to tick size / digits                         |
//+------------------------------------------------------------------+
double NormPrice(double p)
{
    double tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    if(tick > 0) p = MathRound(p / tick) * tick;
    return NormalizeDouble(p, _Digits);
}
//+------------------------------------------------------------------+
