//+------------------------------------------------------------------+
//|                                          FalseBreakoutHedge.mq5   |
//|                                                          Algobot  |
//|                                     https://www.algobot.live      |
//+------------------------------------------------------------------+
//  Native MQL5 port of the C# Algobot strategy "FalseBreakoutHedge".
//
//  PURE PRICE-ACTION system (NO indicators -- no MA / RSI / ATR / BB).
//  It chains: true daily-SESSION floor PIVOTS, the ENGULFING candle
//  PATTERN, a pivot BREAKOUT, a managed REVERSAL HEDGE, and SCALP TPs.
//
//  Levels -- session floor pivots, fixed for the whole day:
//      PP=(H+L+C)/3 ; R1=2PP-L ; S1=2PP-H ; R2=PP+(H-L) ; S2=PP-(H-L)
//      range = H-L. Folded from the prior calendar day's H/L/C.
//
//  Entries (flat only, mirror-image):
//      LONG  -- bullish engulfing that opens <= R1 and closes
//               decisively above R1 (Close > R1 + BreakFrac*range).
//      SHORT -- bearish engulfing that opens >= S1 and closes
//               decisively below S1.
//
//  Hedge (confirmed false break):
//      While a lone base leg is open, if a later bar CLOSES back
//      through the broken level by FailFrac*range, deploy an opposite
//      market leg (volume = base * HedgeMult).
//
//  Basket money management (every tick once exposure is held):
//      net floating >= BasketTpMoney  -> close everything (bank)
//      net floating <= -BasketSlMoney -> flatten (hard cap)
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters / GetInput defaults) -----------------
input double MinBodyFrac     = 0.55;   // engulfing body >= this fraction of its own range
input double BreakFrac       = 0.05;   // close beyond pivot by this fraction of range = real break
input double TpFrac          = 0.60;   // scalp TP distance as a fraction of session range (cap)
input double StopBufferFrac  = 0.15;   // stop pad beyond engulfing bar's opposite extreme (fraction)
input double FailFrac        = 0.10;   // close back through level by this fraction = confirmed false break
input double HedgeMult       = 1.50;   // recovery-hedge volume as a multiple of the base leg
input double BasketTpMoney   = 30.0;   // bank the whole basket once net floating profit reaches this
input double BasketSlMoney   = 400.0;  // flatten the whole basket once net floating loss reaches this
input int    MaxSpreadPoints = 40;     // skip new entries while spread (points) exceeds this (0 = off)
input double Lots            = 0.10;   // base volume
input long   Magic           = 8310;   // magic number

//--- live state -------------------------------------------------------------
int      g_digits = 0;
datetime g_lastBarTime = 0;            // time of the bar at shift 0 last processed

//--- session-pivot accumulation (running, single timeframe) -----------------
bool     g_haveSession = false;        // false == C# DateTime.MinValue (no session started yet)
datetime g_sessionDate = 0;            // calendar day of the session being accumulated
double   g_sH = 0, g_sL = 0, g_sC = 0; // running high / low / last-close of that session
bool     g_havePivots = false;
double   g_pp = 0, g_r1 = 0, g_s1 = 0, g_r2 = 0, g_s2 = 0, g_range = 0; // FIXED for current day

//--- trade state ------------------------------------------------------------
int      g_dir = 0;                    // 0 flat, +1 long breakout, -1 short breakout
bool     g_hedged = false;             // opposite recovery leg already deployed?
double   g_level = 0;                  // the broken pivot (R1 for long, S1 for short)

//+------------------------------------------------------------------+
//| Init                                                             |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber((ulong)Magic);
    g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

    g_haveSession = false; g_havePivots = false;
    g_sessionDate = 0;
    g_dir = 0; g_hedged = false; g_level = 0;

    // Replay all available CLOSED bars (oldest -> newest) so session pivots
    // are ready immediately. Closed bars are shifts (Bars-1) .. 1; shift 0
    // is the forming bar.
    int available = Bars(_Symbol, _Period);
    for(int shift = available - 1; shift >= 1; shift--)
        Ingest(iTime(_Symbol, _Period, shift),
               iHigh(_Symbol, _Period, shift),
               iLow (_Symbol, _Period, shift),
               iClose(_Symbol, _Period, shift));

    g_lastBarTime = iTime(_Symbol, _Period, 0);   // current (forming) bar
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Deinit (no indicator handles are used)                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}

//+------------------------------------------------------------------+
//| Tick                                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // ---- A) While exposure is held: basket money management (every tick) ----
    int posCount = CountPos(Magic);
    if(posCount > 0)
    {
        double floating = FloatingPL(Magic);
        if(floating >=  BasketTpMoney) { FlattenAll(Magic, StringFormat("basket TP %.2f", floating)); return; }
        if(floating <= -BasketSlMoney) { FlattenAll(Magic, StringFormat("basket SL %.2f", floating)); return; }
    }
    else if(g_dir != 0)
    {
        // Everything closed (base TP/SL or a flatten) -> reset for next setup.
        g_dir = 0; g_hedged = false;
    }

    // ---- B) Structural work gated to ONE pass per freshly-closed bar ----
    datetime curBarTime = iTime(_Symbol, _Period, 0);
    if(curBarTime == g_lastBarTime) return;
    g_lastBarTime = curBarTime;

    // The bar that was forming has just closed -> it is now shift 1. Fold it in.
    Ingest(iTime(_Symbol, _Period, 1),
           iHigh(_Symbol, _Period, 1),
           iLow (_Symbol, _Period, 1),
           iClose(_Symbol, _Period, 1));

    // While a lone base leg is live, a confirmed false break (close back
    // through the level) deploys the recovery hedge.
    int live = CountPos(Magic);
    if(g_dir != 0 && !g_hedged && live == 1 && g_range > 0)
    {
        double fail    = FailFrac * g_range;
        double jcClose = iClose(_Symbol, _Period, 1);
        if(g_dir > 0 && jcClose < g_level - fail)        DeployHedge(false, "R1 break failed"); // sell hedge
        else if(g_dir < 0 && jcClose > g_level + fail)   DeployHedge(true,  "S1 break failed"); // buy  hedge
        return;
    }

    // Only hunt fresh breakouts when completely flat.
    if(live > 0) return;
    TrySeekEntry();
}

//+------------------------------------------------------------------+
//| Fold one CLOSED bar into the running session; on day-rollover    |
//| finalize prior session into FIXED pivots, then start new session |
//+------------------------------------------------------------------+
void Ingest(datetime t, double h, double l, double c)
{
    datetime day = DayStart(t);

    if(!g_haveSession)
    {
        g_haveSession = true; g_sessionDate = day;
        g_sH = h; g_sL = l; g_sC = c;
        return;
    }

    if(day != g_sessionDate)
    {
        // Previous calendar day just completed -> its H/L/C set the pivots.
        g_pp    = (g_sH + g_sL + g_sC) / 3.0;
        g_r1    = 2.0 * g_pp - g_sL;
        g_s1    = 2.0 * g_pp - g_sH;
        g_r2    = g_pp + (g_sH - g_sL);
        g_s2    = g_pp - (g_sH - g_sL);
        g_range = g_sH - g_sL;
        g_havePivots = (g_range > 0);

        g_sessionDate = day; g_sH = h; g_sL = l; g_sC = c;   // begin new session
        return;
    }

    // Same day -> extend the running session.
    if(h > g_sH) g_sH = h;
    if(l < g_sL) g_sL = l;
    g_sC = c;
}

//+------------------------------------------------------------------+
//| Seek a fresh breakout entry (called only when flat)              |
//+------------------------------------------------------------------+
void TrySeekEntry()
{
    if(!g_havePivots || g_range <= 0) return;
    if(Bars(_Symbol, _Period) < 3) return;                 // need shift 1 and shift 2 (C#: _bars.Count < 2)
    if(MaxSpreadPoints > 0 && (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return;

    double cO = iOpen (_Symbol, _Period, 1);   // just-closed candle (shift 1) -- signal bar
    double cC = iClose(_Symbol, _Period, 1);
    double cH = iHigh (_Symbol, _Period, 1);
    double cL = iLow  (_Symbol, _Period, 1);
    double pO = iOpen (_Symbol, _Period, 2);   // prior candle (shift 2) -- engulfed by c
    double pC = iClose(_Symbol, _Period, 2);

    double rng = cH - cL;
    if(rng <= 0) return;
    double body = MathAbs(cC - cO);
    if(body < MinBodyFrac * rng) return;                   // conviction: break bar is mostly body

    bool cBull = cC > cO, cBear = cC < cO;
    bool pBull = pC > pO, pBear = pC < pO;
    // Strict engulfing: c's body fully swallows p's body.
    bool bullEngulf = cBull && pBear && cC >= pO && cO <= pC;
    bool bearEngulf = cBear && pBull && cC <= pO && cO >= pC;

    double minDist  = MinStopDist();
    double breakPad = BreakFrac * g_range;

    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    if(ask <= 0 || bid <= 0) return;
    double vol = NormVol(Lots);
    if(vol <= 0) return;

    // ---- LONG: bullish engulfing breaks UP through resistance R1 ----
    bool brokeR1 = cO <= g_r1 && cC > g_r1 + breakPad;
    if(bullEngulf && brokeR1)
    {
        double sl = cL - MathMax(StopBufferFrac * g_range, minDist);
        double tp = ask + ScalpDist(g_r2 - ask, minDist);   // scalp toward R2, capped to TpFrac
        if(sl > bid - minDist) sl = bid - minDist;           // keep SL a valid distance away
        OpenBase(true, vol, sl, tp, g_r1, "R1 breakout");
        return;
    }

    // ---- SHORT: bearish engulfing breaks DOWN through support S1 ----
    bool brokeS1 = cO >= g_s1 && cC < g_s1 - breakPad;
    if(bearEngulf && brokeS1)
    {
        double sl = cH + MathMax(StopBufferFrac * g_range, minDist);
        double tp = bid - ScalpDist(bid - g_s2, minDist);    // scalp toward S2, capped to TpFrac
        if(sl < ask + minDist) sl = ask + minDist;
        OpenBase(false, vol, sl, tp, g_s1, "S1 breakout");
        return;
    }
}

//+------------------------------------------------------------------+
//| Distance to the next pivot, capped to a scalp-sized TpFrac of    |
//| the range, floored at the broker's minimum stop distance.        |
//+------------------------------------------------------------------+
double ScalpDist(double toNextPivot, double minDist)
{
    double cap = MathMax(TpFrac * g_range, minDist);
    double d   = (toNextPivot > minDist) ? MathMin(toNextPivot, cap) : cap;
    return MathMax(d, minDist);
}

//+------------------------------------------------------------------+
//| Open the base breakout leg                                       |
//+------------------------------------------------------------------+
void OpenBase(bool isBuy, double vol, double sl, double tp, double level, string why)
{
    double entry = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                         : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    bool ok;
    if(isBuy) ok = trade.Buy (vol, _Symbol, 0.0, Norm(sl), Norm(tp), "FBH " + why);
    else      ok = trade.Sell(vol, _Symbol, 0.0, Norm(sl), Norm(tp), "FBH " + why);

    if(ok)
    {
        g_dir    = isBuy ? 1 : -1;
        g_hedged = false;
        g_level  = level;
    }
    PrintFormat("FBH %s (%s) @ %.5f SL %.5f TP %.5f level %.5f -> ok=%s ret=%d",
                (isBuy ? "Buy" : "Sell"), why, entry, sl, tp, level,
                (ok ? "true" : "false"), (int)trade.ResultRetcode());
}

//+------------------------------------------------------------------+
//| Deploy the opposite recovery leg (confirmed FALSE break)         |
//|   hedgeIsBuy == false -> Sell hedge (base was long)              |
//|   hedgeIsBuy == true  -> Buy  hedge (base was short)             |
//+------------------------------------------------------------------+
void DeployHedge(bool hedgeIsBuy, string why)
{
    double vol = NormVol(Lots * HedgeMult);
    if(vol <= 0) return;

    double minDist = MinStopDist();
    double pad     = MathMax(StopBufferFrac * g_range, minDist);
    double entry, sl, tp;

    if(!hedgeIsBuy)   // base was long; R1 reclaimed from above -> ride the drop
    {
        entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        sl    = g_level + pad;                               // wrong if price climbs back above R1
        tp    = entry - ScalpDist(entry - g_s1, minDist);    // recover toward the opposite pivot
        if(sl < entry + minDist) sl = entry + minDist;
        if(tp > entry - minDist) tp = entry - minDist;
    }
    else              // base was short; S1 reclaimed from below -> ride the bounce
    {
        entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        sl    = g_level - pad;
        tp    = entry + ScalpDist(g_r1 - entry, minDist);
        if(sl > entry - minDist) sl = entry - minDist;
        if(tp < entry + minDist) tp = entry + minDist;
    }

    bool ok;
    if(hedgeIsBuy) ok = trade.Buy (vol, _Symbol, 0.0, Norm(sl), Norm(tp), "FBH hedge");
    else           ok = trade.Sell(vol, _Symbol, 0.0, Norm(sl), Norm(tp), "FBH hedge");

    if(ok) g_hedged = true;
    PrintFormat("FBH HEDGE %s (%s) @ %.5f SL %.5f TP %.5f -> ok=%s ret=%d",
                (hedgeIsBuy ? "Buy" : "Sell"), why, entry, sl, tp,
                (ok ? "true" : "false"), (int)trade.ResultRetcode());
}

//+------------------------------------------------------------------+
//| Flatten every leg of this symbol/magic                           |
//+------------------------------------------------------------------+
void FlattenAll(long magic, string why)
{
    int legs = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL)   == _Symbol &&
           PositionGetInteger(POSITION_MAGIC)   == magic)
        {
            trade.PositionClose(t);
            legs++;
        }
    }
    g_dir = 0; g_hedged = false;
    PrintFormat("FBH FLATTEN (%s) legs=%d", why, legs);
}

//+------------------------------------------------------------------+
//| Helpers                                                          |
//+------------------------------------------------------------------+
// Start-of-day timestamp (calendar day) for rollover detection.
datetime DayStart(datetime t)
{
    return (datetime)(t - (t % 86400));
}

// Count this symbol/magic's open positions.
int CountPos(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;
}

// Net floating P/L (profit + swap) across this symbol/magic.
double FloatingPL(long magic)
{
    double f = 0.0;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL)  == _Symbol &&
           PositionGetInteger(POSITION_MAGIC)  == magic)
            f += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
    }
    return f;
}

// Broker minimum stop distance: (StopLevelPoints + 1) * Point.
double MinStopDist()
{
    double point     = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    long   stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    return (double)(stopLevel + 1) * point;
}

// Snap a requested volume to the symbol's step and min/max limits.
double NormVol(double lots)
{
    double vstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(vstep <= 0.0) vstep = 0.01;
    double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

    double v = MathRound(lots / vstep) * vstep;
    v = NormalizeDouble(v, 2);
    if(v < vmin) v = vmin;
    if(v > vmax) v = vmax;
    return v;
}

// Round a price to the symbol's digits.
double Norm(double price)
{
    return NormalizeDouble(price, g_digits);
}
//+------------------------------------------------------------------+
