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

// StairStepTrendContinuation
// ---------------------------------------------------------------------------
// Pure price-action, trend-following system. NO indicators are used at all.
//
// Healthy trends "stair-step": a sequence of higher swing highs AND higher
// swing lows (uptrend) or lower swing highs AND lower swing lows (downtrend).
// Inside a confirmed stair-step structure we wait for a shallow counter-trend
// pullback (a run of consecutive lower closes in an uptrend / higher closes in
// a downtrend) and re-enter the moment price resumes by closing back beyond the
// prior bar. The protective stop sits just past the pullback extreme; the
// target is a fixed R-multiple of that risk.
//
// Swing structure is detected with a symmetric pivot: a closed bar is a swing
// high if its High strictly exceeds the High of PivotLength bars on each side
// (mirror for swing lows). Everything is derived from raw OHLC only.

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

//--- inputs (mirror DescribeParameters / OnInit GetInput defaults) -----------
input int    PivotLength   = 3;     // symmetric pivot half-width
input int    PullbackBars  = 2;     // min consecutive counter-trend closes
input double RewardRatio    = 2.0;  // target as multiple of risk
input double StopBufferPct  = 0.25; // stop buffer as fraction of current range
input double Lots           = 0.10; // order volume
input long   Magic          = 7301; // magic number

//--- closed-bar history, newest-last (mirrors C# List<Bar> _bars) ------------
double g_high[];
double g_low[];
double g_close[];

//--- swing structure state (mirrors C# fields; NaN -> "has" flags) -----------
double _lastSwingHigh = 0.0; bool _hasLastSwingHigh = false;
double _prevSwingHigh = 0.0; bool _hasPrevSwingHigh = false;
double _lastSwingLow  = 0.0; bool _hasLastSwingLow  = false;
double _prevSwingLow  = 0.0; bool _hasPrevSwingLow  = false;

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

    ArrayResize(g_high, 0);
    ArrayResize(g_low,  0);
    ArrayResize(g_close,0);

    _hasLastSwingHigh = _hasPrevSwingHigh = false;
    _hasLastSwingLow  = _hasPrevSwingLow  = false;
    _lastSwingHigh = _prevSwingHigh = _lastSwingLow = _prevSwingLow = 0.0;

    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnTick()
{
    // Act once per newly-formed bar; trade on the bar that just closed.
    if(!IsNewBar()) return;

    if(Bars(_Symbol, _Period) < 2) return;

    // Append the bar that just closed (shift 1). Newest-last, cap at 600.
    AppendBar(iHigh(_Symbol, _Period, 1),
              iLow (_Symbol, _Period, 1),
              iClose(_Symbol, _Period, 1));

    UpdateSwings();

    int n = ArraySize(g_close);
    if(n < 2 * PivotLength + 1 || n < PullbackBars + 3) return;

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

    bool haveStruct = _hasPrevSwingHigh && _hasPrevSwingLow;
    bool up   = haveStruct && _lastSwingHigh > _prevSwingHigh && _lastSwingLow > _prevSwingLow;
    bool down = haveStruct && _lastSwingHigh < _prevSwingHigh && _lastSwingLow < _prevSwingLow;

    double curHigh  = g_high[n - 1];
    double curLow   = g_low [n - 1];
    double curClose = g_close[n - 1];
    double prevHigh = g_high[n - 2];
    double prevLow  = g_low [n - 2];

    double range  = curHigh - curLow;
    double buffer = StopBufferPct * range;

    if(up)
    {
        // Pullback = run of lower closes ending on the prior bar; resumption
        // = current bar closes back above the prior bar's high.
        int k = CountFalling(n - 2);
        if(k >= PullbackBars && curClose > prevHigh)
        {
            double lo    = LowestLow(n - 1 - k, n - 1);   // pullback extreme
            double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double sl    = lo - buffer;
            double risk  = entry - sl;
            if(risk > 0)
            {
                double tp = entry + risk * RewardRatio;
                trade.Buy(Lots, _Symbol, 0.0,
                          NormalizeDouble(sl, _Digits),
                          NormalizeDouble(tp, _Digits),
                          "StairStepTrendContinuation");
            }
        }
    }
    else if(down)
    {
        int k = CountRising(n - 2);
        if(k >= PullbackBars && curClose < prevLow)
        {
            double hi    = HighestHigh(n - 1 - k, n - 1);
            double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double sl    = hi + buffer;
            double risk  = sl - entry;
            if(risk > 0)
            {
                double tp = entry - risk * RewardRatio;
                trade.Sell(Lots, _Symbol, 0.0,
                           NormalizeDouble(sl, _Digits),
                           NormalizeDouble(tp, _Digits),
                           "StairStepTrendContinuation");
            }
        }
    }
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // No indicator handles to release (pure price-action strategy).
}

//+------------------------------------------------------------------+
//| Append a closed bar; keep newest-last and cap history at 600.    |
//+------------------------------------------------------------------+
void AppendBar(double h, double l, double c)
{
    int n = ArraySize(g_high);
    ArrayResize(g_high,  n + 1);
    ArrayResize(g_low,   n + 1);
    ArrayResize(g_close, n + 1);
    g_high[n]  = h;
    g_low[n]   = l;
    g_close[n] = c;

    int sz = ArraySize(g_high);
    if(sz > 600)
    {
        int rem = sz - 600;
        ArrayRemove(g_high,  0, rem);
        ArrayRemove(g_low,   0, rem);
        ArrayRemove(g_close, 0, rem);
    }
}

//+------------------------------------------------------------------+
//| Confirm the newest fully-evaluable pivot center as swing hi/lo.  |
//+------------------------------------------------------------------+
void UpdateSwings()
{
    int n = ArraySize(g_close);
    int c = n - 1 - PivotLength;   // newest center we can fully evaluate
    if(c < PivotLength) return;    // not enough bars to the left yet

    double hc = g_high[c], lc = g_low[c];
    bool isHigh = true, isLow = true;
    for(int j = c - PivotLength; j <= c + PivotLength; j++)
    {
        if(j == c) continue;
        if(g_high[j] >= hc) isHigh = false;
        if(g_low[j]  <= lc) isLow  = false;
    }
    if(isHigh)
    {
        _prevSwingHigh = _lastSwingHigh; _hasPrevSwingHigh = _hasLastSwingHigh;
        _lastSwingHigh = hc;             _hasLastSwingHigh = true;
    }
    if(isLow)
    {
        _prevSwingLow = _lastSwingLow;   _hasPrevSwingLow = _hasLastSwingLow;
        _lastSwingLow = lc;              _hasLastSwingLow = true;
    }
}

//+------------------------------------------------------------------+
//| Consecutive falling closes ending at endIdx (going backwards).   |
//+------------------------------------------------------------------+
int CountFalling(int endIdx)
{
    int k = 0;
    for(int i = endIdx; i >= 1; i--)
    {
        if(g_close[i] < g_close[i - 1]) k++;
        else break;
    }
    return k;
}

//+------------------------------------------------------------------+
//| Consecutive rising closes ending at endIdx (going backwards).    |
//+------------------------------------------------------------------+
int CountRising(int endIdx)
{
    int k = 0;
    for(int i = endIdx; i >= 1; i--)
    {
        if(g_close[i] > g_close[i - 1]) k++;
        else break;
    }
    return k;
}

//+------------------------------------------------------------------+
double LowestLow(int from, int to)
{
    if(from < 0) from = 0;
    double lo = DBL_MAX;
    for(int i = from; i <= to; i++) if(g_low[i] < lo) lo = g_low[i];
    return lo;
}

//+------------------------------------------------------------------+
double HighestHigh(int from, int to)
{
    if(from < 0) from = 0;
    double hi = -DBL_MAX;
    for(int i = from; i <= to; i++) if(g_high[i] > hi) hi = g_high[i];
    return hi;
}

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