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

// GapAnchorContinuation
// -----------------------------------------------------------------------------
// Pure price-action + gap strategy. No indicators (no MA/RSI/ATR/etc.).
//
// "Gap and go": when a bar opens with a meaningful GAP away from the prior bar's
// close AND closes further in the gap direction without dipping back to fill it,
// ARM the setup. Then wait for the FIRST defended retest back into the gap void
// (a close that holds the gap origin while dipping into the void) and enter the
// continuation with the gap origin as a hard invalidation line.
//
// Two-bar gap geometry (P = prior bar = shift 2, G = just-closed gap bar = shift 1):
//   Gap UP   : G.Open >= P.Close + thr, G.Close > G.Open, G.Low  > P.Close
//              void = [P.Close .. G.Open]   (origin .. far edge)
//   Gap DOWN : G.Open <= P.Close - thr, G.Close < G.Open, G.High < P.Close
//              void = [G.Open .. P.Close]
//   thr = GapMultiple * average bar range over RangeLookback (computed inline).
// -----------------------------------------------------------------------------

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

// --- inputs ---
input double Lots          = 0.10;   // Volume per trade
input double GapMultiple   = 0.80;   // Gap threshold as multiple of avg range
input int    RangeLookback = 20;     // Bars for avg-range yardstick
input double RetestDepth   = 0.50;   // Required dip into the void (fraction)
input double StopBuffer    = 0.50;   // SL buffer beyond gap origin (avg-range mult)
input double RewardRisk    = 2.00;   // Take-profit reward:risk multiple
input int    ExpiryBars    = 8;      // Arm expires after this many bars
input long   Magic         = 1001;   // Magic number

// --- one armed setup at a time ---
bool   g_armed      = false;
int    g_armDir     = 0;       // +1 gap up (long), -1 gap down (short)
int    g_armedBar   = 0;
double g_voidTop    = 0.0;     // gap void edges (origin..far)
double g_voidBottom = 0.0;
int    g_barIndex   = 0;       // completed-bar counter (for expiry)

int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    g_armed    = false;
    g_armDir   = 0;
    g_armedBar = 0;
    g_barIndex = 0;
    return INIT_SUCCEEDED;
}

void OnTick()
{
    // Act once per completed bar.
    if(!IsNewBar()) return;
    g_barIndex++;

    // Need shifts 1..RangeLookback+2 available (mirrors C# n < RangeLookback+2 guard).
    if(Bars(_Symbol, _Period) < RangeLookback + 3) return;

    // One position at a time; let SL/TP resolve it and clear any arm.
    if(HasPosition(Magic)) { g_armed = false; return; }

    // Price-action volatility yardstick: average bar range over the window
    // preceding the prior bar (shifts 3 .. RangeLookback+2). Computed inline.
    double avgRange = 0.0; int cnt = 0;
    for(int shift = 3; shift <= RangeLookback + 2; shift++)
    {
        avgRange += iHigh(_Symbol, _Period, shift) - iLow(_Symbol, _Period, shift);
        cnt++;
    }
    if(cnt == 0) return;
    avgRange /= cnt;
    if(avgRange <= 0.0) return;

    // P = prior bar (gap measured from its close) = shift 2
    // G = just-closed bar (gap bar OR retest bar)   = shift 1
    double P_close = iClose(_Symbol, _Period, 2);
    double G_open  = iOpen (_Symbol, _Period, 1);
    double G_close = iClose(_Symbol, _Period, 1);
    double G_high  = iHigh (_Symbol, _Period, 1);
    double G_low   = iLow  (_Symbol, _Period, 1);

    // 1) If armed, first try to act on this bar as the defended retest / manage the arm.
    if(g_armed)
    {
        // Voided if the gap origin has been filled by a close, or the arm is stale.
        bool filled = (g_armDir > 0) ? (G_close <= g_voidBottom) : (G_close >= g_voidTop);
        bool stale  = (g_barIndex - g_armedBar) > ExpiryBars;
        if(filled || stale)
        {
            g_armed = false;
            // fall through: this same bar may itself be a brand-new qualifying gap.
        }
        else
        {
            double voidSize = g_voidTop - g_voidBottom;
            if(voidSize > 0.0 && TryDefendedRetest(G_open, G_high, G_low, G_close, voidSize, avgRange))
                return;   // entered (or geometry rejected) for this bar
            // still armed, waiting for a clean defended retest
            if(!g_armed) { /* re-arm path handled below */ }
            else return;
        }
    }

    // 2) Look for a fresh qualifying gap on the just-closed bar to arm onto.
    double threshold = GapMultiple * avgRange;
    double gap       = G_open - P_close;          // >0 gap up, <0 gap down

    bool gapUp   = (gap >=  threshold) && (G_close > G_open) && (G_low  > P_close);
    bool gapDown = (gap <= -threshold) && (G_close < G_open) && (G_high < P_close);

    if(gapUp)
    {
        g_armed      = true;
        g_armDir     = +1;
        g_armedBar   = g_barIndex;
        g_voidBottom = P_close;   // gap origin (fill = invalidation)
        g_voidTop    = G_open;    // far edge of the void
        PrintFormat("GapAnchor ARM UP void [%.5f..%.5f]", g_voidBottom, g_voidTop);
    }
    else if(gapDown)
    {
        g_armed      = true;
        g_armDir     = -1;
        g_armedBar   = g_barIndex;
        g_voidBottom = G_open;    // far edge of the void
        g_voidTop    = P_close;   // gap origin (fill = invalidation)
        PrintFormat("GapAnchor ARM DOWN void [%.5f..%.5f]", g_voidBottom, g_voidTop);
    }
}

// Returns true if this bar resolved the armed setup (entered or disarmed).
bool TryDefendedRetest(double R_open, double R_high, double R_low, double R_close,
                       double voidSize, double avgRange)
{
    if(g_armDir > 0)
    {
        // Long: dip into the void from above, but defend the gap origin.
        double retestLevel = g_voidTop - RetestDepth * voidSize;   // how deep into the void
        bool dipped   = R_low   <= retestLevel;
        bool held     = R_close > g_voidBottom;   // gap origin not filled
        bool defended = R_close > R_open;         // bullish hold candle
        if(!(dipped && held && defended)) return false;

        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = g_voidBottom - StopBuffer * avgRange;
        double risk  = entry - sl;
        if(risk <= 0.0) { g_armed = false; return true; }
        double tp = entry + RewardRisk * risk;
        if(tp <= entry) { g_armed = false; return true; }

        Fire(ORDER_TYPE_BUY, entry, sl, tp);
        g_armed = false;
        return true;
    }
    else
    {
        // Short: rally into the void from below, but defend the gap origin.
        double retestLevel = g_voidBottom + RetestDepth * voidSize;
        bool popped   = R_high  >= retestLevel;
        bool held     = R_close < g_voidTop;      // gap origin not filled
        bool defended = R_close < R_open;         // bearish hold candle
        if(!(popped && held && defended)) return false;

        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = g_voidTop + StopBuffer * avgRange;
        double risk  = sl - entry;
        if(risk <= 0.0) { g_armed = false; return true; }
        double tp = entry - RewardRisk * risk;
        if(tp >= entry) { g_armed = false; return true; }

        Fire(ORDER_TYPE_SELL, entry, sl, tp);
        g_armed = false;
        return true;
    }
}

void Fire(ENUM_ORDER_TYPE type, double price, double sl, double tp)
{
    sl = NormalizeDouble(sl, _Digits);
    tp = NormalizeDouble(tp, _Digits);
    double vol = NormalizeLots(Lots);

    if(type == ORDER_TYPE_BUY)
        trade.Buy(vol, _Symbol, 0.0, sl, tp, "GapAnchorContinuation");
    else
        trade.Sell(vol, _Symbol, 0.0, sl, tp, "GapAnchorContinuation");

    PrintFormat("GapAnchor %s @ %.5f  sl %.5f  tp %.5f",
                (type == ORDER_TYPE_BUY ? "BUY" : "SELL"), price, sl, tp);
}

// --- helpers ---
double NormalizeLots(double lots)
{
    double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(lotStep > 0.0) lots = MathRound(lots / lotStep) * lotStep;
    if(lots < minLot) lots = minLot;
    if(lots > maxLot) lots = maxLot;
    return lots;
}

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

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