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

// ApexCompressionBreak
// --------------------
// Pure price-action breakout: NO indicators.
//
// Idea: markets coil before they expand. We measure the price range of a recent
// window of bars (the "apex") and compare it to the range of the window just
// before it. When the recent range has CONTRACTED to a fraction of the prior
// range, energy is building inside a tightening box. We then wait for a single
// decisive candle to CLOSE beyond the box boundary, with the close pinned to the
// extreme of its own range (a strong body, not a wick-rejection). That break of
// the coil triggers a position in the breakout direction.
//
// Stops are STRUCTURAL (the opposite edge of the coil) and the target is a
// MEASURED MOVE (box height * reward multiple) projected from entry. One position
// per magic at a time; the trade is then managed entirely by its SL/TP.

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

// --- inputs ---
input int    RangeBars        = 10;    // Bars in the coil window (apex) AND the prior reference window
input double ContractionRatio = 0.80;  // Recent range must be <= prior range * this ratio to qualify as a coil
input double BodyStrength     = 0.60;  // How close the breakout candle's close must sit to its extreme (0..1)
input double RewardMult       = 1.5;   // Measured-move target = boxHeight * this multiple
input double Lots             = 0.10;  // Trade volume
input long   Magic            = 7720;  // Magic number

int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    return INIT_SUCCEEDED;
}

void OnTick()
{
    // Act once per completed bar only.
    if(!IsNewBar()) return;

    // Need the coil window, the prior window, plus the breakout bar.
    // Layout (shift): 1 = breakout bar, 2..N+1 = coil box, N+2..2N+1 = prior window.
    int needed = 2 * RangeBars + 2;
    if(Bars(_Symbol, _Period) < needed) return;

    // One position per magic at a time -> let SL/TP manage it.
    if(HasPosition(Magic)) return;

    // Coil box (the apex) over shifts 2..N+1.
    double boxHigh = -DBL_MAX, boxLow = DBL_MAX;
    for(int s = 2; s <= RangeBars + 1; s++)
    {
        double h = iHigh(_Symbol, _Period, s);
        double l = iLow(_Symbol, _Period, s);
        if(h > boxHigh) boxHigh = h;
        if(l < boxLow)  boxLow  = l;
    }
    double boxHeight = boxHigh - boxLow;
    if(boxHeight <= 0) return;

    // Prior reference window over shifts N+2..2N+1.
    double priorHigh = -DBL_MAX, priorLow = DBL_MAX;
    for(int s = RangeBars + 2; s <= 2 * RangeBars + 1; s++)
    {
        double h = iHigh(_Symbol, _Period, s);
        double l = iLow(_Symbol, _Period, s);
        if(h > priorHigh) priorHigh = h;
        if(l < priorLow)  priorLow  = l;
    }
    double priorHeight = priorHigh - priorLow;
    if(priorHeight <= 0) return;

    // Compression filter: the coil must be tighter than the prior window.
    if(boxHeight > priorHeight * ContractionRatio) return;

    // The decisive breakout candle (most recently closed bar, shift 1).
    double brkHigh  = iHigh(_Symbol,  _Period, 1);
    double brkLow   = iLow(_Symbol,   _Period, 1);
    double brkOpen  = iOpen(_Symbol,  _Period, 1);
    double brkClose = iClose(_Symbol, _Period, 1);

    double range = brkHigh - brkLow;
    if(range <= 0) return;
    double closePos = (brkClose - brkLow) / range;  // 0 = at low, 1 = at high

    bool bullBreak = brkClose > boxHigh && brkClose > brkOpen && closePos >= BodyStrength;
    bool bearBreak = brkClose < boxLow  && brkClose < brkOpen && (1.0 - closePos) >= BodyStrength;

    // Small structural buffer so the stop sits just outside the coil.
    double buffer = boxHeight * 0.05;

    if(bullBreak)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = boxLow - buffer;
        double tp    = entry + boxHeight * RewardMult;
        Send(ORDER_TYPE_BUY, sl, tp, "ApexBull");
    }
    else if(bearBreak)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = boxHigh + buffer;
        double tp    = entry - boxHeight * RewardMult;
        Send(ORDER_TYPE_SELL, sl, tp, "ApexBear");
    }
}

void OnDeinit(const int reason)
{
}

void Send(ENUM_ORDER_TYPE type, double sl, double tp, string tag)
{
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    sl = NormalizeDouble(sl, digits);
    tp = NormalizeDouble(tp, digits);

    bool res = false;
    if(type == ORDER_TYPE_BUY)
        res = trade.Buy(Lots, _Symbol, 0, sl, tp, tag);
    else if(type == ORDER_TYPE_SELL)
        res = trade.Sell(Lots, _Symbol, 0, sl, tp, tag);

    PrintFormat("%s %s %.2f %s sl=%.5f tp=%.5f -> %s (retcode=%d)",
                tag, EnumToString(type), Lots, _Symbol, sl, tp,
                (res ? "OK" : "FAIL"), trade.ResultRetcode());
}

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

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;
}
