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

//
// TailPivotReversal - a pure price-action reversal system (no indicators of any kind).
//
// When price thrusts to a FRESH local extreme and is immediately rejected, it prints a
// candle with a long "tail" (rejection wick) on the extreme side and a small body that
// closes back away from the extreme - a pin / rejection bar. Rather than fade the tail
// on its close, we demand CONFIRMATION: arm a pending stop order through the OPPOSITE
// (body) end of the rejection bar and only fill if the next bar trades back through it:
//   - Long tail at a fresh LOW  (lower wick dominant) -> BUY STOP above the bar's high
//   - Long tail at a fresh HIGH (upper wick dominant) -> SELL STOP below the bar's low
// The pending order is valid for exactly one bar; if it does not trigger it is cancelled.
// Stops sit just beyond the rejected tail extreme; target is a reward:risk multiple.
//

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

//--- inputs -------------------------------------------------------------------
input int    Lookback         = 10;     // bars behind the rejection bar to confirm a fresh extreme
input double TailRatio        = 0.55;   // wick must be >= this fraction of the bar range
input double MaxBodyPct       = 40.0;   // body must be <= this % of the bar range
input double RewardRisk       = 2.0;    // target distance as a multiple of risk
input double TriggerBufferPct = 5.0;    // entry buffer beyond the bar, as % of range
input double StopBufferPct    = 10.0;   // stop buffer beyond the tail, as % of range
input double Lots             = 0.10;   // order volume
input long   Magic            = 5207;   // EA magic number

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

//+------------------------------------------------------------------+
//| Deinit                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // no indicator handles to release (pure price action)
}

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

//+------------------------------------------------------------------+
//| Count open positions on this symbol/magic                        |
//+------------------------------------------------------------------+
int CountPositions(long magic)
{
    int n = 0;
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic) n++;
    }
    return n;
}

//+------------------------------------------------------------------+
//| Cancel every pending order on this symbol/magic                  |
//+------------------------------------------------------------------+
void CancelPendings(long magic)
{
    for(int i = OrdersTotal()-1; i >= 0; i--)
    {
        ulong t = OrderGetTicket(i);
        if(OrderSelect(t) &&
           OrderGetString(ORDER_SYMBOL) == _Symbol &&
           OrderGetInteger(ORDER_MAGIC) == magic)
            trade.OrderDelete(t);
    }
}

//+------------------------------------------------------------------+
//| Tick                                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Act only when a brand-new bar opens (shift 0 is the forming bar).
    if(!IsNewBar()) return;

    // A signal arm is valid for exactly one bar: any pending that survived the previous
    // bar without triggering is now stale - cancel it.
    CancelPendings(Magic);

    // SL/TP manage open trades; never stack a second position on the same magic.
    if(CountPositions(Magic) > 0) return;

    // Need the rejection bar (shift 1) plus a full lookback window behind it.
    if(Bars(_Symbol, _Period) < Lookback + 2) return;

    // The just-closed bar is our candidate rejection candle.
    double sigOpen  = iOpen (_Symbol, _Period, 1);
    double sigHigh  = iHigh (_Symbol, _Period, 1);
    double sigLow   = iLow  (_Symbol, _Period, 1);
    double sigClose = iClose(_Symbol, _Period, 1);

    double range = sigHigh - sigLow;
    if(range <= 0.0) return;

    double body      = MathAbs(sigClose - sigOpen);
    double upperWick = sigHigh - MathMax(sigOpen, sigClose);
    double lowerWick = MathMin(sigOpen, sigClose) - sigLow;
    double mid       = (sigHigh + sigLow) / 2.0;
    double maxBody    = (MaxBodyPct       / 100.0) * range;
    double trigBuffer = (TriggerBufferPct / 100.0) * range;
    double stopBuffer = (StopBufferPct    / 100.0) * range;

    // Body must be small - a dominant tail, not a strong directional candle.
    if(body > maxBody) return;

    // ---- Long tail at a FRESH LOW -> reversal up -> arm a BUY STOP ----
    bool freshLow = true;
    for(int s = 2; s <= Lookback + 1; s++)
    {
        if(iLow(_Symbol, _Period, s) <= sigLow){ freshLow = false; break; }
    }
    bool bullishTail =
        freshLow &&
        lowerWick >= TailRatio * range &&   // rejection of the lows dominates the bar
        sigClose  >= mid;                   // and price closed back in the upper half

    if(bullishTail)
    {
        double trigger = sigHigh + trigBuffer;   // confirm by reclaiming above the bar
        double sl      = sigLow  - stopBuffer;   // invalidation just beyond the tail
        double risk    = trigger - sl;
        if(risk > 0.0)
        {
            double tp = trigger + RewardRisk * risk;
            trigger = NormalizeDouble(trigger, _Digits);
            sl      = NormalizeDouble(sl,      _Digits);
            tp      = NormalizeDouble(tp,      _Digits);
            if(trade.BuyStop(Lots, trigger, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "TailPivotLong"))
                PrintFormat("Bullish tail-pivot: buy-stop %.5f SL %.5f TP %.5f (tail %.0f%%)",
                            trigger, sl, tp, 100.0 * lowerWick / range);
        }
        return;
    }

    // ---- Long tail at a FRESH HIGH -> reversal down -> arm a SELL STOP ----
    bool freshHigh = true;
    for(int s = 2; s <= Lookback + 1; s++)
    {
        if(iHigh(_Symbol, _Period, s) >= sigHigh){ freshHigh = false; break; }
    }
    bool bearishTail =
        freshHigh &&
        upperWick >= TailRatio * range &&   // rejection of the highs dominates the bar
        sigClose  <= mid;                   // and price closed back in the lower half

    if(bearishTail)
    {
        double trigger = sigLow  - trigBuffer;   // confirm by breaking below the bar
        double sl      = sigHigh + stopBuffer;   // invalidation just beyond the tail
        double risk    = sl - trigger;
        if(risk > 0.0)
        {
            double tp = trigger - RewardRisk * risk;
            trigger = NormalizeDouble(trigger, _Digits);
            sl      = NormalizeDouble(sl,      _Digits);
            tp      = NormalizeDouble(tp,      _Digits);
            if(trade.SellStop(Lots, trigger, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "TailPivotShort"))
                PrintFormat("Bearish tail-pivot: sell-stop %.5f SL %.5f TP %.5f (tail %.0f%%)",
                            trigger, sl, tp, 100.0 * upperWick / range);
        }
    }
}
//+------------------------------------------------------------------+
