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

// ThrustImbalanceReboundHedge
// --------------------------------------------------------------------------------------
// A PURE price-action system that pairs TWO ideas: (1) a Fair Value Gap / 3-candle
// imbalance carved out by a momentum THRUST candle, traded as a continuation REBOUND, and
// (2) HEDGING via gap inversion. No technical indicators are used anywhere - every value
// (thrust strength, gap size, stops, targets) is measured directly from candle highs,
// lows, opens and closes.

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

//--- inputs (from DescribeParameters) ------------------------------
input double Lots               = 0.10;   // Lots
input double ThrustBodyFraction = 0.55;   // ThrustBodyFraction
input double MinGapFactor       = 0.25;   // MinGapFactor
input double TakeProfitRR       = 2.00;   // TakeProfitRR
input double StopBufferMult     = 0.30;   // StopBufferMult
input int    ExpiryBars         = 15;     // ExpiryBars
input int    RangeLookback      = 14;     // RangeLookback
input int    EnableHedge        = 1;      // EnableHedge (1=on,0=off)
input long   Magic              = 9200;   // Magic

//--- magic numbers -------------------------------------------------
long g_magic      = 0;   // primary rebound leg
long g_hedgeMagic = 0;   // inversion hedge leg

//--- bar counter (mirrors C# _bars.Count == n) ---------------------
// In the C# source, bars are accumulated live (newest-last) and indices into that list
// drive freshness/age. We mirror that with a live counter so age semantics match exactly.
long g_n = 0;

//--- the currently-tracked imbalance (mirrors C# state) ------------
bool   g_hasFvg     = false;
bool   g_fvgBullish = false;
bool   g_fvgTraded  = false;   // primary rebound leg already entered for this gap
double g_zLow       = 0.0;     // gap edges, g_zLow < g_zHigh
double g_zHigh      = 0.0;
long   g_fvgBarIndex= 0;       // g_n at formation, for age / freshness

//+------------------------------------------------------------------+
int OnInit()
{
    g_magic      = Magic;
    g_hedgeMagic = Magic + 1;
    trade.SetExpertMagicNumber(g_magic);

    g_n          = 0;
    g_hasFvg     = false;
    g_fvgBullish = false;
    g_fvgTraded  = false;
    g_zLow       = 0.0;
    g_zHigh      = 0.0;
    g_fvgBarIndex= 0;

    return(INIT_SUCCEEDED);
}

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

//+------------------------------------------------------------------+
void OnTick()
{
    // Act exactly once per fully-closed bar.
    if(!IsNewBar()) return;

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

    // Mirror C#: _bars.Add(ctx.Bar(sym,tf,1)) -- the bar that just closed.
    g_n++;

    // Need rangeLookback + 4 accumulated bars before doing anything.
    if(g_n < (long)RangeLookback + 4) return;

    double avgRange = AverageRange(g_n);
    if(avgRange <= 0.0) return;

    // Trio of closed bars (newest-last in the C# list):
    //   c1 = _bars[n-3] -> shift 3 (oldest of the trio)
    //   c2 = _bars[n-2] -> shift 2 (middle / thrust candle)
    //   c3 = _bars[n-1] -> shift 1 (newest closed bar)
    double c1High  = iHigh (_Symbol,_Period,3);
    double c1Low   = iLow  (_Symbol,_Period,3);

    double c2Open  = iOpen (_Symbol,_Period,2);
    double c2Close = iClose(_Symbol,_Period,2);
    double c2High  = iHigh (_Symbol,_Period,2);
    double c2Low   = iLow  (_Symbol,_Period,2);

    double c3Open  = iOpen (_Symbol,_Period,1);
    double c3Close = iClose(_Symbol,_Period,1);
    double c3High  = iHigh (_Symbol,_Period,1);
    double c3Low   = iLow  (_Symbol,_Period,1);

    double buffer  = StopBufferMult * avgRange;

    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    //---- 1) Manage the existing imbalance BEFORE looking for a new one ----------------
    if(g_hasFvg)
    {
        long age = g_n - g_fvgBarIndex;

        // (a) Inversion: price closed fully through the gap -> hedge in the new direction.
        bool violated = g_fvgBullish ? (c3Close < g_zLow) : (c3Close > g_zHigh);
        if(violated)
        {
            if(EnableHedge == 1 && !HasPosition(g_hedgeMagic))
            {
                if(g_fvgBullish)
                {
                    // Bullish gap failed -> becomes resistance -> sell the inversion.
                    double entry = bid;
                    double sl    = g_zHigh + buffer;
                    double risk  = sl - entry;
                    if(risk > 0.0)
                    {
                        trade.SetExpertMagicNumber(g_hedgeMagic);
                        trade.Sell(Lots, _Symbol, 0, sl, entry - TakeProfitRR * risk, "ThrustImbReb-HedgeS");
                    }
                }
                else
                {
                    // Bearish gap failed -> becomes support -> buy the inversion.
                    double entry = ask;
                    double sl    = g_zLow - buffer;
                    double risk  = entry - sl;
                    if(risk > 0.0)
                    {
                        trade.SetExpertMagicNumber(g_hedgeMagic);
                        trade.Buy(Lots, _Symbol, 0, sl, entry + TakeProfitRR * risk, "ThrustImbReb-HedgeB");
                    }
                }
            }
            g_hasFvg = false; g_fvgTraded = false;
            return;
        }

        // (b) Housekeeping: drop the gap once its primary leg has fully closed, or once
        //     it has gone stale untouched.
        if(g_fvgTraded)
        {
            if(!HasPosition(g_magic)) { g_hasFvg = false; g_fvgTraded = false; }
        }
        else if(age > ExpiryBars)
        {
            g_hasFvg = false;
        }

        // (c) Rebound continuation entry (first untraded tap that closes with momentum).
        if(g_hasFvg && !g_fvgTraded && age >= 1 && !HasPosition(g_magic))
        {
            if(g_fvgBullish)
            {
                bool tagged   = c3Low   <= g_zHigh;   // dipped back into the gap
                bool held     = c3Close >  g_zLow;    // stayed above the gap floor
                bool momentum = c3Close >  c3Open;    // closed bullish (thrust resuming)
                if(tagged && held && momentum)
                {
                    double entry = ask;
                    double sl    = MathMin(c3Low, g_zLow) - buffer;
                    double risk  = entry - sl;
                    if(risk > 0.0)
                    {
                        trade.SetExpertMagicNumber(g_magic);
                        trade.Buy(Lots, _Symbol, 0, sl, entry + TakeProfitRR * risk, "ThrustImbReb-L");
                        g_fvgTraded = true;
                    }
                }
            }
            else
            {
                bool tagged   = c3High  >= g_zLow;    // poked back into the gap
                bool held     = c3Close <  g_zHigh;   // stayed below the gap ceiling
                bool momentum = c3Close <  c3Open;    // closed bearish (thrust resuming)
                if(tagged && held && momentum)
                {
                    double entry = bid;
                    double sl    = MathMax(c3High, g_zHigh) + buffer;
                    double risk  = sl - entry;
                    if(risk > 0.0)
                    {
                        trade.SetExpertMagicNumber(g_magic);
                        trade.Sell(Lots, _Symbol, 0, sl, entry - TakeProfitRR * risk, "ThrustImbReb-S");
                        g_fvgTraded = true;
                    }
                }
            }
        }

        return;   // never detect a brand-new gap on a bar still managing one
    }

    //---- 2) Detect a fresh thrust-driven imbalance ----------------------------------
    double body   = MathAbs(c2Close - c2Open);
    double range2 = c2High - c2Low;
    if(range2 <= 0.0) return;
    bool strongThrust = body >= ThrustBodyFraction * range2;

    if(strongThrust && c2Close > c2Open && c1High < c3Low)
    {
        double gap = c3Low - c1High;
        if(gap >= MinGapFactor * avgRange)
        {
            g_hasFvg = true; g_fvgBullish = true; g_fvgTraded = false;
            g_zLow = c1High; g_zHigh = c3Low; g_fvgBarIndex = g_n;
        }
    }
    else if(strongThrust && c2Close < c2Open && c1Low > c3High)
    {
        double gap = c1Low - c3High;
        if(gap >= MinGapFactor * avgRange)
        {
            g_hasFvg = true; g_fvgBullish = false; g_fvgTraded = false;
            g_zLow = c3High; g_zHigh = c1Low; g_fvgBarIndex = g_n;
        }
    }
}

//+------------------------------------------------------------------+
//| Mean high-low range over the lookback window (no indicators).    |
//| count = min(RangeLookback, n); averages shifts 1..count, i.e.    |
//| the most recently closed bars (matches C# _bars window).         |
//+------------------------------------------------------------------+
double AverageRange(long n)
{
    int count = (int)MathMin((long)RangeLookback, n);
    if(count <= 0) return(0.0);
    double sum = 0.0;
    for(int shift = 1; shift <= count; shift++)
        sum += iHigh(_Symbol,_Period,shift) - iLow(_Symbol,_Period,shift);
    return(sum / count);
}

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

//+------------------------------------------------------------------+
//| True if an open position on this symbol carries the given magic. |
//| Mirrors ctx.OpenPositions(sym, magic).Count != 0.               |
//+------------------------------------------------------------------+
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);
}
//+------------------------------------------------------------------+
