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

//+------------------------------------------------------------------+
//| RangeStraddleHedge                                               |
//| -----------------                                               |
//| A pure PRICE-ACTION HEDGING system - NO indicators of any kind. |
//| Everything is driven by raw bar highs/lows and by the floating  |
//| P/L of the open basket, exactly like a hand-managed hedge book. |
//|                                                                 |
//| State machine over the primary timeframe only:                  |
//|   IDLE    -> flat, nothing resting: measure range, straddle it. |
//|   ARMED   -> straddle resting, no fill yet: expire after N bars. |
//|   ENGAGED -> one stop filled; opposite stop left as the HEDGE.  |
//|                                                                 |
//| Exits: per-leg TP/SL (range-width based) PLUS the dominant      |
//| basket money layer (BasketTpMoney / BasketSlMoney every tick).  |
//+------------------------------------------------------------------+

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

//--- inputs (mirror of C# DescribeParameters) ---
input int    RangeLookback   = 20;     // CLOSED bars whose hi/lo define the range
input double BufferFrac      = 0.10;   // breakout offset beyond boundary (fraction of width)
input double RewardRatio     = 2.00;   // per-leg take-profit (multiple of width)
input double StopMult        = 3.00;   // per-leg catastrophic stop-loss (multiple of width)
input double BasketTpMoney   = 30.0;   // close WHOLE basket once floating profit >= this (ccy)
input double BasketSlMoney   = 400.0;  // flatten WHOLE basket once floating loss >= this (ccy)
input int    ExpiryBars      = 8;      // cancel un-triggered straddle after this many bars
input int    MaxSpreadPoints = 50;     // skip straddle while spread (points) > this (0 = off)
input double Lots            = 0.10;   // volume per leg
input long   Magic           = 5210;   // EA magic number

//--- state machine ---
enum Phase { PHASE_IDLE = 0, PHASE_ARMED = 1, PHASE_ENGAGED = 2 };

Phase    g_phase           = PHASE_IDLE;
int      g_barsSincePlaced = 0;          // age of a resting straddle (ARMED)
datetime g_lastBarTime     = 0;

//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber((ulong)Magic);

    g_phase           = PHASE_IDLE;
    g_barsSincePlaced = 0;

    // Seed with the current (forming) bar so structural logic only runs
    // once the first NEW bar closes - exactly like the C# OnInit seed.
    g_lastBarTime = iTime(_Symbol, _Period, 0);

    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
void OnTick()
{
    // ---- A) Basket money management - runs EVERY tick while we hold exposure ----
    // This is the dominant exit: banks a recovered whipsaw the instant the net
    // turns sufficiently green, and caps the whole hedge book's downside.
    double floating = 0.0;
    int    posCount = GetBasket(Magic, floating);

    if(posCount > 0)
    {
        if(floating >= BasketTpMoney)
        {
            FlattenAll(Magic, StringFormat("basket TP %.2f", floating), posCount);
            return;
        }
        if(floating <= -BasketSlMoney)
        {
            FlattenAll(Magic, StringFormat("basket SL %.2f", floating), posCount);
            return;
        }
    }

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

    int pendCount = CountPending(Magic);

    // --- A position is open: we are ENGAGED (one breakout leg, maybe + hedge). ---
    // The opposite resting stop is left in place ON PURPOSE as the hedge trigger;
    // per-leg TP/SL and the basket layer handle every exit from here.
    if(posCount > 0)
    {
        g_phase = PHASE_ENGAGED;
        return;
    }

    // --- Flat, but orders still rest: either ARMED (waiting) or a leg just closed. ---
    if(pendCount > 0)
    {
        if(g_phase == PHASE_ENGAGED)
        {
            // A leg closed (its TP/SL) and left the un-triggered hedge resting -> clean up.
            CancelAllPending(Magic);
            g_phase = PHASE_IDLE;
            g_barsSincePlaced = 0;
            return;
        }

        // ARMED: age the straddle; expire it if the range never broke.
        g_barsSincePlaced++;
        if(g_barsSincePlaced >= ExpiryBars)
        {
            CancelAllPending(Magic);
            g_phase = PHASE_IDLE;
            g_barsSincePlaced = 0;
        }
        return;
    }

    // --- Flat and nothing resting -> IDLE: measure a fresh range and straddle it. ---
    g_phase = PHASE_IDLE;
    TryPlaceStraddle();
}

//+------------------------------------------------------------------+
//| Measure the range over the last RangeLookback CLOSED bars and    |
//| straddle it with a mirror-image pair of pending STOP orders.     |
//+------------------------------------------------------------------+
void TryPlaceStraddle()
{
    // Need at least RangeLookback closed bars (shifts 1..RangeLookback).
    if(Bars(_Symbol, _Period) < RangeLookback + 1) return;

    if(MaxSpreadPoints > 0)
    {
        long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
        if(spread > MaxSpreadPoints) return;
    }

    // Range = highest high / lowest low of the last RangeLookback CLOSED bars.
    double hi = -DBL_MAX, lo = DBL_MAX;
    for(int shift = 1; shift <= RangeLookback; shift++)
    {
        double h = iHigh(_Symbol, _Period, shift);
        double l = iLow(_Symbol, _Period, shift);
        if(h > hi) hi = h;
        if(l < lo) lo = l;
    }
    double width = hi - lo;
    if(width <= 0) return;

    double point      = _Point;
    long   stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    double minDist    = (stopsLevel + 1) * point;
    double buffer     = MathMax(BufferFrac * width, minDist);

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

    double buyPrice  = hi + buffer;   // BUY STOP above the range
    double sellPrice = lo - buffer;   // SELL STOP below the range

    double buyTp  = buyPrice  + MathMax(RewardRatio * width, minDist);
    double buySl  = buyPrice  - MathMax(StopMult    * width, minDist);
    double sellTp = sellPrice - MathMax(RewardRatio * width, minDist);
    double sellSl = sellPrice + MathMax(StopMult    * width, minDist);

    double vol = NormalizeVolume(Lots);
    if(vol <= 0) return;

    bool placed = false;

    // LONG breakout leg - only if the buy stop sits a valid distance above the ask.
    if(buyPrice - ask >= minDist)
    {
        bool ok = trade.BuyStop(vol, Norm(buyPrice), _Symbol, Norm(buySl), Norm(buyTp),
                                ORDER_TIME_GTC, 0, "RSH buy stop");
        if(ok) placed = true;
        PrintFormat("RSH BUYSTOP @ %.*f sl=%.*f tp=%.*f range[%.*f-%.*f] -> ok=%s ret=%d",
                    _Digits, buyPrice, _Digits, buySl, _Digits, buyTp,
                    _Digits, lo, _Digits, hi, (string)ok, trade.ResultRetcode());
    }

    // SHORT breakout leg - only if the sell stop sits a valid distance below the bid.
    if(bid - sellPrice >= minDist)
    {
        bool ok = trade.SellStop(vol, Norm(sellPrice), _Symbol, Norm(sellSl), Norm(sellTp),
                                 ORDER_TIME_GTC, 0, "RSH sell stop");
        if(ok) placed = true;
        PrintFormat("RSH SELLSTOP @ %.*f sl=%.*f tp=%.*f range[%.*f-%.*f] -> ok=%s ret=%d",
                    _Digits, sellPrice, _Digits, sellSl, _Digits, sellTp,
                    _Digits, lo, _Digits, hi, (string)ok, trade.ResultRetcode());
    }

    if(placed)
    {
        g_phase = PHASE_ARMED;
        g_barsSincePlaced = 0;
    }
}

//+------------------------------------------------------------------+
//| Close every open leg for this Magic, pull any resting hedge,     |
//| then reset the state machine.                                    |
//+------------------------------------------------------------------+
void FlattenAll(long magic, string why, int legs)
{
    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);
    }
    CancelAllPending(magic);
    g_phase = PHASE_IDLE;
    g_barsSincePlaced = 0;
    PrintFormat("RSH FLATTEN (%s) legs=%d", why, legs);
}

//+------------------------------------------------------------------+
//| Cancel every resting pending order for this Magic / symbol.      |
//+------------------------------------------------------------------+
void CancelAllPending(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);
    }
}

//+------------------------------------------------------------------+
//| Sum floating P/L (profit + swap) and count open legs for Magic.  |
//+------------------------------------------------------------------+
int GetBasket(long magic, double &floating)
{
    floating = 0.0;
    int count = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
            count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| Count resting pending orders for Magic / symbol.                 |
//+------------------------------------------------------------------+
int CountPending(long magic)
{
    int count = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        ulong t = OrderGetTicket(i);
        if(OrderSelect(t) &&
           OrderGetString(ORDER_SYMBOL) == _Symbol &&
           OrderGetInteger(ORDER_MAGIC) == magic)
            count++;
    }
    return count;
}

//+------------------------------------------------------------------+
//| Snap a requested volume to the symbol's step and min/max limits. |
//+------------------------------------------------------------------+
double NormalizeVolume(double lots)
{
    double vstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(vstep <= 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, _Digits);
}
//+------------------------------------------------------------------+
