#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

// DonchianMidlineReversion
// -------------------------
// A range mean-reversion system that FADES failed breakouts of a Donchian channel.
// The channel (highest-high / lowest-low over ChannelPeriod bars) defines the range,
// and its midpoint is the reversion target. When the newest closed bar pierces the
// outer band but CLOSES BACK INSIDE the channel (a rejection / false break) and RSI
// sits at an extreme, price is likely to snap back toward the middle of the range.
// A minimum channel-width (in ATR units) filter skips dead, compressed conditions.
// Long and short are fully symmetric. Stops sit just beyond the rejected extreme
// (ATR-buffered); the target is the channel midline.

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

//--- inputs -----------------------------------------------------------------
input int    ChannelPeriod = 20;    // Donchian channel period
input int    RsiPeriod     = 14;    // RSI period
input int    RsiOversold   = 30;    // RSI oversold level (overbought = 100 - this)
input int    AtrPeriod     = 14;    // ATR period
input double MinWidthAtr   = 2.0;   // Minimum channel width in ATR units
input double SlAtrMult     = 1.0;   // Stop-loss ATR buffer beyond the extreme
input double Lots          = 0.10;  // Trade volume
input long   Magic         = 1001;  // Magic number

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

//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(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("Failed to create indicator handles");
        return INIT_FAILED;
    }
    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()
{
    // Act once per completed bar.
    if(!IsNewBar()) return;

    // Enough history for the channel (shifts 2..ChannelPeriod+1), RSI and ATR.
    int need = MathMax(ChannelPeriod + 2, MathMax(RsiPeriod + 1, AtrPeriod + 1));
    if(Bars(_Symbol, _Period) < need) return;

    // One position per magic at a time.
    if(HasPosition(Magic)) return;

    // --- pull RSI and ATR at the signal bar (the just-closed bar, shift 1) ---
    double rsiBuf[], atrBuf[];
    ArraySetAsSeries(rsiBuf, true);
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_rsiHandle, 0, 0, 3, rsiBuf) < 3) return;
    if(CopyBuffer(g_atrHandle, 0, 0, 3, atrBuf) < 3) return;

    double rsi = rsiBuf[1];
    double atr = atrBuf[1];
    if(atr <= 0.0) return;

    // --- signal bar = newest closed bar (shift 1) ---
    double sigHigh  = iHigh(_Symbol,  _Period, 1);
    double sigLow   = iLow(_Symbol,   _Period, 1);
    double sigClose = iClose(_Symbol, _Period, 1);

    // --- Donchian channel from the ChannelPeriod bars BEFORE the signal bar,
    //     i.e. shifts 2 .. ChannelPeriod+1, so the signal bar's own pierce does
    //     not shift the band. ---
    double upper = -DBL_MAX, lower = DBL_MAX;
    for(int shift = 2; shift <= ChannelPeriod + 1; shift++)
    {
        double h = iHigh(_Symbol, _Period, shift);
        double l = iLow(_Symbol,  _Period, shift);
        if(h > upper) upper = h;
        if(l < lower) lower = l;
    }
    double mid = (upper + lower) * 0.5;

    // Width filter: skip flat, compressed ranges that whipsaw.
    if((upper - lower) < MinWidthAtr * atr) return;

    double overbought = 100.0 - RsiOversold;

    // Long: pierced below the lower band, closed back inside, RSI oversold.
    bool longSig  = (sigLow  < lower && sigClose > lower && rsi < RsiOversold);
    // Short: pierced above the upper band, closed back inside, RSI overbought.
    bool shortSig = (sigHigh > upper && sigClose < upper && rsi > overbought);

    if(longSig)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = sigLow - atr * SlAtrMult;
        double tp    = mid;
        if(tp <= entry || sl >= entry) return;   // need room toward the midline
        sl = NormalizeDouble(sl, _Digits);
        tp = NormalizeDouble(tp, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "DonchianMidlineReversion-L");
    }
    else if(shortSig)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = sigHigh + atr * SlAtrMult;
        double tp    = mid;
        if(tp >= entry || sl <= entry) return;
        sl = NormalizeDouble(sl, _Digits);
        tp = NormalizeDouble(tp, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "DonchianMidlineReversion-S");
    }
}

//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
