//+------------------------------------------------------------------+
//|                                        ChandelierTrendRider.mq5   |
//|  Trend-following Donchian breakout, EMA regime filter,           |
//|  managed with a ratcheting ATR Chandelier Exit.                 |
//|  Converted from the Algobot C# strategy ChandelierTrendRider.cs  |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror StrategyParameter defaults from the C# source) ---
input int    ChannelPeriod        = 20;    // Donchian lookback (also Chandelier trail)
input int    TrendEmaPeriod       = 50;    // EMA regime length
input int    AtrPeriod            = 14;    // ATR length (initial stop + Chandelier)
input double InitialStopAtrMult   = 2.0;   // initial protective stop, in ATR
input double ChandelierAtrMult    = 3.0;   // Chandelier trail distance, in ATR
input double TakeProfitRewardMult = 4.0;   // backstop TP as R-multiple (0 = none)
input double Lots                 = 0.10;  // fixed volume
input long   Magic                = 8801;  // magic number

//--- indicator handles ---
int g_ema = INVALID_HANDLE;
int g_atr = INVALID_HANDLE;

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

    g_ema = iMA(_Symbol, _Period, TrendEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
    g_atr = iATR(_Symbol, _Period, AtrPeriod);

    if(g_ema == INVALID_HANDLE || g_atr == INVALID_HANDLE)
    {
        Print("ChandelierTrendRider: failed to create indicator handles");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Act once per newly-closed bar.                                   |
//| In the C# source, the just-closed bar (shift 1, "c") is          |
//| APPENDED only AFTER evaluation, so every indicator / channel is  |
//| built from PRIOR bars only -> they are read here as-of shift 2,  |
//| while the breakout close is the just-closed bar at shift 1.      |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // Warmup gate: C# requires _bars.Count >= max(channel, ema, atr).
    // Indicators / channel are read from shift 2 upward, hence +3 margin.
    int need = (int)MathMax(ChannelPeriod, MathMax(TrendEmaPeriod, AtrPeriod));
    if(Bars(_Symbol, _Period) < need + 3) return;

    // EMA and ATR of the PRIOR bars (as-of shift 2).
    double emaBuf[], atrBuf[];
    ArraySetAsSeries(emaBuf, true);
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_ema, 0, 2, 1, emaBuf) < 1) return;
    if(CopyBuffer(g_atr, 0, 2, 1, atrBuf) < 1) return;
    double ema = emaBuf[0];
    double atr = atrBuf[0];
    if(atr <= 0.0) return;

    // Donchian extremes of PRIOR bars: highest high / lowest low over the
    // last ChannelPeriod completed bars, starting at shift 2 (excludes "c").
    int hiIdx = iHighest(_Symbol, _Period, MODE_HIGH, ChannelPeriod, 2);
    int loIdx = iLowest (_Symbol, _Period, MODE_LOW,  ChannelPeriod, 2);
    if(hiIdx < 0 || loIdx < 0) return;
    double chHigh = iHigh(_Symbol, _Period, hiIdx);
    double chLow  = iLow (_Symbol, _Period, loIdx);

    if(HasPosition(Magic))
        ManageOpen(chHigh, chLow, atr);
    else
        TryEnter(ema, atr, chHigh, chLow);
}

//+------------------------------------------------------------------+
//| Flat: fresh breakout that AGREES with the EMA regime.            |
//| Breakout is judged on the just-closed bar (shift 1) close.       |
//+------------------------------------------------------------------+
void TryEnter(double ema, double atr, double chHigh, double chLow)
{
    double close1 = iClose(_Symbol, _Period, 1);   // "c" = just-closed bar

    // LONG - decisive close above the prior-N high AND above the EMA regime.
    if(close1 > chHigh && close1 > ema)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = entry - InitialStopAtrMult * atr;
        double risk  = entry - sl;
        if(risk > 0.0)
        {
            double tp = (TakeProfitRewardMult > 0.0) ? entry + TakeProfitRewardMult * risk : 0.0;
            sl = NormalizeDouble(sl, _Digits);
            tp = (tp > 0.0) ? NormalizeDouble(tp, _Digits) : 0.0;
            bool res = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "CTR long");
            PrintFormat("CTR LONG  break>%.5f close=%.5f ema=%.5f atr=%.5f sl=%.5f tp=%.5f -> %s",
                        chHigh, close1, ema, atr, sl, tp, (res ? "ok" : "fail"));
        }
    }
    // SHORT - decisive close below the prior-N low AND below the EMA regime.
    else if(close1 < chLow && close1 < ema)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = entry + InitialStopAtrMult * atr;
        double risk  = sl - entry;
        if(risk > 0.0)
        {
            double tp = (TakeProfitRewardMult > 0.0) ? entry - TakeProfitRewardMult * risk : 0.0;
            sl = NormalizeDouble(sl, _Digits);
            tp = (tp > 0.0) ? NormalizeDouble(tp, _Digits) : 0.0;
            bool res = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "CTR short");
            PrintFormat("CTR SHORT break<%.5f close=%.5f ema=%.5f atr=%.5f sl=%.5f tp=%.5f -> %s",
                        chLow, close1, ema, atr, sl, tp, (res ? "ok" : "fail"));
        }
    }
}

//+------------------------------------------------------------------+
//| In a trade: ratchet the Chandelier Exit; the stop only tightens. |
//+------------------------------------------------------------------+
void ManageOpen(double chHigh, double chLow, double atr)
{
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(!PositionSelectByTicket(t)) continue;
        if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
        if(PositionGetInteger(POSITION_MAGIC) != Magic)   continue;

        long   type  = PositionGetInteger(POSITION_TYPE);
        double curSl = PositionGetDouble(POSITION_SL);
        double curTp = PositionGetDouble(POSITION_TP);

        if(type == POSITION_TYPE_BUY)
        {
            // Trail under the running high; keep the stop below current price,
            // and only ever move it UP.
            double chand = chHigh - ChandelierAtrMult * atr;
            if(chand > curSl && chand < bid)
            {
                double nsl = NormalizeDouble(chand, _Digits);
                bool res = trade.PositionModify(t, nsl, curTp);
                PrintFormat("CTR trail LONG  sl %.5f -> %.5f -> %s", curSl, nsl, (res ? "ok" : "fail"));
            }
        }
        else if(type == POSITION_TYPE_SELL)
        {
            // Trail above the running low; keep the stop above current price,
            // and only ever move it DOWN.
            double chand = chLow + ChandelierAtrMult * atr;
            if((curSl <= 0.0 || chand < curSl) && chand > ask)
            {
                double nsl = NormalizeDouble(chand, _Digits);
                bool res = trade.PositionModify(t, nsl, curTp);
                PrintFormat("CTR trail SHORT sl %.5f -> %.5f -> %s", curSl, nsl, (res ? "ok" : "fail"));
            }
        }
    }
}

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