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

//
// CounterThrustReversal - a pure price-action, two-candle swing-reversal
// system (no indicators).
//
//   1. THRUST candle (shift 2): a strong-bodied candle that drives price to a
//      FRESH local extreme (fresh low for a down-thrust, fresh high for an
//      up-thrust) and whose range is wider than the recent average.
//   2. COUNTER-THRUST candle (shift 1): the next candle reverses hard and
//      closes back deep through the thrust candle's body (>= ReclaimRatio of
//      that body).
//
//   * Down-thrust to a fresh low, bullish reclaim  -> BUY
//   * Up-thrust to a fresh high, bearish reclaim    -> SELL
//
// Stops sit just beyond the two-bar swing extreme; the target is a configurable
// reward:risk multiple of that distance. All from raw bar geometry.
//

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

//--- inputs ---------------------------------------------------------
input int    SwingLookback   = 10;     // Swing lookback window (bars)
input double ThrustBodyFrac  = 0.55;   // Min thrust body / range
input double ReclaimBodyFrac = 0.50;   // Min counter body / range
input double ReclaimRatio    = 0.55;   // Reclaim depth into thrust body
input double ThrustRangeMult = 1.30;   // Thrust range vs avg range
input double RewardRisk      = 1.80;   // Reward : risk multiple
input double StopBufferPct   = 12.0;   // Stop buffer (% of thrust range)
input double Lots            = 0.10;   // Order volume
input long   Magic           = 5273;   // Magic number

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Fire once per freshly closed bar                                 |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| One position at a time for this magic                            |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
//| Main logic                                                       |
//+------------------------------------------------------------------+
void OnTick()
{
    // Act once per freshly closed bar (shift 0 is the still-forming bar).
    if(!IsNewBar()) return;

    // Need: counter(1) + thrust(2) + a lookback window beneath them (3 .. 2+lookback).
    int need = SwingLookback + 4;
    if(Bars(_Symbol, _Period) < need) return;

    // One position at a time for this magic - SL/TP manage the exit.
    if(HasPosition(Magic)) return;

    // counter = the reclaim candle (just closed); thrust = the breakout candle.
    double counterOpen  = iOpen (_Symbol, _Period, 1);
    double counterHigh  = iHigh (_Symbol, _Period, 1);
    double counterLow   = iLow  (_Symbol, _Period, 1);
    double counterClose = iClose(_Symbol, _Period, 1);

    double thrustOpen   = iOpen (_Symbol, _Period, 2);
    double thrustHigh   = iHigh (_Symbol, _Period, 2);
    double thrustLow    = iLow  (_Symbol, _Period, 2);
    double thrustClose  = iClose(_Symbol, _Period, 2);

    double thrustRange  = thrustHigh  - thrustLow;
    double counterRange = counterHigh - counterLow;
    if(thrustRange <= 0.0 || counterRange <= 0.0) return;

    double thrustBody  = MathAbs(thrustClose  - thrustOpen);
    double counterBody = MathAbs(counterClose - counterOpen);

    // Both candles must be conviction candles, not dojis.
    if(thrustBody  < ThrustBodyFrac  * thrustRange)  return;
    if(counterBody < ReclaimBodyFrac * counterRange) return;

    // Average range of the window just beneath the pattern (shifts 3 .. 2+lookback).
    double sumRange     = 0.0;
    double priorHighest = -DBL_MAX;
    double priorLowest  =  DBL_MAX;
    for(int s = 3; s <= SwingLookback + 2; s++)
    {
        double bHigh = iHigh(_Symbol, _Period, s);
        double bLow  = iLow (_Symbol, _Period, s);
        sumRange += bHigh - bLow;
        if(bHigh > priorHighest) priorHighest = bHigh;
        if(bLow  < priorLowest)  priorLowest  = bLow;
    }
    double avgRange = sumRange / SwingLookback;
    if(avgRange <= 0.0) return;

    // The thrust must be a genuine swing leg, wider than the recent average.
    if(thrustRange < ThrustRangeMult * avgRange) return;

    double buffer = (StopBufferPct / 100.0) * thrustRange;
    int    digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

    // ---- Down-thrust to a fresh low, bullish body reclaim -> BUY ----
    bool downThrust     = thrustClose  < thrustOpen;
    bool freshLow       = thrustLow    < priorLowest;      // breakout to a new local low
    bool bullishCounter = counterClose > counterOpen;
    // Counter opens within/below the thrust's close, then closes back up through the body.
    double bullReclaimTarget = thrustClose + ReclaimRatio * (thrustOpen - thrustClose);
    bool bullReclaim = (counterOpen <= thrustClose) && (counterClose >= bullReclaimTarget);

    if(downThrust && freshLow && bullishCounter && bullReclaim)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = MathMin(counterLow, thrustLow) - buffer;
        double risk  = entry - sl;
        if(risk > 0.0)
        {
            double tp = entry + RewardRisk * risk;
            sl = NormalizeDouble(sl, digits);
            tp = NormalizeDouble(tp, digits);
            if(trade.Buy(Lots, _Symbol, 0.0, sl, tp, "BullCounterThrust"))
                PrintFormat("Counter-thrust long @ %.5f SL %.5f TP %.5f (thrust x%.1f)",
                            entry, sl, tp, thrustRange / avgRange);
        }
        return;
    }

    // ---- Up-thrust to a fresh high, bearish body reclaim -> SELL ----
    bool upThrust       = thrustClose  > thrustOpen;
    bool freshHigh      = thrustHigh   > priorHighest;     // breakout to a new local high
    bool bearishCounter = counterClose < counterOpen;
    double bearReclaimTarget = thrustClose - ReclaimRatio * (thrustClose - thrustOpen);
    bool bearReclaim = (counterOpen >= thrustClose) && (counterClose <= bearReclaimTarget);

    if(upThrust && freshHigh && bearishCounter && bearReclaim)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = MathMax(counterHigh, thrustHigh) + buffer;
        double risk  = sl - entry;
        if(risk > 0.0)
        {
            double tp = entry - RewardRisk * risk;
            sl = NormalizeDouble(sl, digits);
            tp = NormalizeDouble(tp, digits);
            if(trade.Sell(Lots, _Symbol, 0.0, sl, tp, "BearCounterThrust"))
                PrintFormat("Counter-thrust short @ %.5f SL %.5f TP %.5f (thrust x%.1f)",
                            entry, sl, tp, thrustRange / avgRange);
        }
    }
}
//+------------------------------------------------------------------+
