//+------------------------------------------------------------------+
//|                                          FlagPoleProjection.mq5   |
//|                                                          Algobot  |
//|                                       https://www.algobot.live    |
//+------------------------------------------------------------------+
//  Concept : Pure PRICE-ACTION trend-continuation breakout of a classic
//            BULL / BEAR FLAG, traded to its textbook MEASURED-MOVE target.
//            NO indicators of any kind -- everything is raw candle geometry.
//
//            POLE : a strong, clean directional impulse over PoleBars closed
//                   candles. Net open->close travel >= PoleFactor x the average
//                   bar range of a quiet BaselineLookback stretch before it, AND
//                   that net travel must dominate the pole's high-low range
//                   (>= PoleDominance) so only impulsive thrusts qualify.
//            FLAG : a tight, shallow consolidation of FlagBars candles pausing
//                   near the pole top (bull) / bottom (bear). Whole flag range
//                   <= FlagMaxFrac x pole height, and the give-back from the pole
//                   extreme must also stay within FlagMaxFrac of pole height.
//
//            Enter only when LIVE price breaks the flag (with a buffer) and is
//            still FRESH (not extended a full flag-range past the break). Risk
//            sits beyond the far side of the flag; target is the pole height
//            projected from entry (the measured move).
//
//  LONG  (bull flag): up-pole + tight shallow flag near pole top, live Ask breaks
//        above flag high -> buy. SL below flag low; TP = entry + pole height.
//  SHORT (bear flag): mirror -- down-pole + tight shallow flag near pole bottom,
//        live Bid breaks below flag low -> sell. SL above flag high; TP = entry
//        - pole height.
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs -------------------------------------------------------------------
input int    PoleBars         = 5;      // Candles that form the impulsive pole
input double PoleFactor       = 2.00;   // Pole net move >= this x pre-pole avg bar range
input double PoleDominance    = 0.55;   // Fraction of pole high-low range its net travel must cover
input int    FlagBars         = 4;      // Candles that form the consolidation flag
input double FlagMaxFrac      = 0.50;   // Flag range AND give-back each <= this x pole height
input int    BaselineLookback = 20;     // Quiet pre-pole stretch for the avg-range baseline
input double TargetFrac       = 1.00;   // Measured-move target = this x pole height from entry
input double BufferFrac       = 0.10;   // Breakout / stop buffer as fraction of flag range
input int    MaxSpreadPoints  = 30;     // Skip trade if live spread (points) wider than this
input double Lots             = 0.10;   // Order volume
input long   Magic            = 7411;   // 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).
}

//+------------------------------------------------------------------+
//| Fire once per newly-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 per magic on this symbol                            |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
{
    // Re-evaluate once per newly-closed bar (shift 0 is the still-forming bar).
    if(!IsNewBar()) return;

    // ---- Window layout in MQL5 shifts (newest closed bar = shift 1) ----
    //   flag     = shifts 1 .. FlagBars                          (most recent FlagBars candles)
    //   pole     = shifts FlagBars+1 .. FlagBars+PoleBars        (PoleBars candles before the flag)
    //   baseline = shifts FlagBars+PoleBars+1 .. +BaselineLookback (quiet pre-pole stretch)
    int poleNewShift = FlagBars + 1;                      // poleEnd  (newest pole bar -> close)
    int poleOldShift = FlagBars + PoleBars;               // poleStart(oldest pole bar -> open)
    int baseNewShift = FlagBars + PoleBars + 1;
    int baseOldShift = FlagBars + PoleBars + BaselineLookback;

    // Equivalent of C#'s "n < Needed" / "baseStart < 0" guards: need the deepest shift available.
    if(Bars(_Symbol, _Period) < baseOldShift + 1) return;

    // One position per magic -- let the structural stop / measured-move target exit.
    if(HasPosition(Magic)) return;

    // Spread gate (breakout entries pay the spread, so keep it sane).
    if((int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return;

    // ---- Pre-pole average bar range (raw geometry, not an indicator) ----
    double sumRange = 0.0;
    for(int s = baseNewShift; s <= baseOldShift; s++)
        sumRange += iHigh(_Symbol, _Period, s) - iLow(_Symbol, _Period, s);
    double avgRange = sumRange / BaselineLookback;
    if(avgRange <= 0.0) return;

    // ---- Pole geometry ----
    double poleHigh = -DBL_MAX, poleLow = DBL_MAX;
    for(int s = poleNewShift; s <= poleOldShift; s++)
    {
        double h = iHigh(_Symbol, _Period, s);
        double l = iLow(_Symbol, _Period, s);
        if(h > poleHigh) poleHigh = h;
        if(l < poleLow)  poleLow  = l;
    }
    double poleHeight = poleHigh - poleLow;
    if(poleHeight <= 0.0) return;

    double poleMove = iClose(_Symbol, _Period, poleNewShift) - iOpen(_Symbol, _Period, poleOldShift); // signed net travel
    // A real impulse: big vs baseline AND directionally dominant (not chop inside a band).
    if(MathAbs(poleMove) < PoleFactor    * avgRange)   return;
    if(MathAbs(poleMove) < PoleDominance * poleHeight) return;

    // ---- Flag geometry ----
    double flagHigh = -DBL_MAX, flagLow = DBL_MAX;
    for(int s = 1; s <= FlagBars; s++)
    {
        double h = iHigh(_Symbol, _Period, s);
        double l = iLow(_Symbol, _Period, s);
        if(h > flagHigh) flagHigh = h;
        if(l < flagLow)  flagLow  = l;
    }
    double flagRange = flagHigh - flagLow;
    if(flagRange <= 0.0) return;

    // The flag must be a genuinely TIGHT pause relative to the pole it follows.
    if(flagRange > FlagMaxFrac * poleHeight) return;

    double buffer = BufferFrac * flagRange;

    // ---- LONG: bull flag continuation ----
    if(poleMove > 0.0)
    {
        // Shallow give-back: the flag must hold near the pole TOP, not retrace deeply.
        if(poleHigh - flagLow > FlagMaxFrac * poleHeight) return;

        double trigger = flagHigh + buffer;
        double ask     = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        // Break must be live AND fresh (don't chase a move already extended past the flag).
        if(ask > trigger && ask <= flagHigh + flagRange + buffer)
        {
            double sl   = flagLow - buffer;                 // beyond the flag floor
            double risk = ask - sl;
            if(risk > 0.0)
            {
                double tp = ask + TargetFrac * poleHeight;  // measured move projected up
                sl = NormalizeDouble(sl, _Digits);
                tp = NormalizeDouble(tp, _Digits);
                bool ok = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "FlagPole long");
                PrintFormat("FlagPole LONG bull-flag break: poleH=%.5f flagR=%.5f flagHi=%.5f entry=%.5f sl=%.5f tp=%.5f -> %s",
                            poleHeight, flagRange, flagHigh, ask, sl, tp, (ok ? "OK" : "FAIL"));
            }
        }
        return;
    }

    // ---- SHORT: bear flag continuation ----
    if(poleMove < 0.0)
    {
        // Shallow give-back: the flag must hold near the pole BOTTOM.
        if(flagHigh - poleLow > FlagMaxFrac * poleHeight) return;

        double trigger = flagLow - buffer;
        double bid     = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        if(bid < trigger && bid >= flagLow - flagRange - buffer)
        {
            double sl   = flagHigh + buffer;                // beyond the flag ceiling
            double risk = sl - bid;
            if(risk > 0.0)
            {
                double tp = bid - TargetFrac * poleHeight;  // measured move projected down
                sl = NormalizeDouble(sl, _Digits);
                tp = NormalizeDouble(tp, _Digits);
                bool ok = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "FlagPole short");
                PrintFormat("FlagPole SHORT bear-flag break: poleH=%.5f flagR=%.5f flagLo=%.5f entry=%.5f sl=%.5f tp=%.5f -> %s",
                            poleHeight, flagRange, flagLow, bid, sl, tp, (ok ? "OK" : "FAIL"));
            }
        }
        return;
    }
}
//+------------------------------------------------------------------+
