//+------------------------------------------------------------------+
//|                                        QstickPressureShift.mq5    |
//|  Native MQL5 port of the Algobot C# strategy QstickPressureShift  |
//|                                                                   |
//|  Trend-aligned momentum on Tushar Chande's Qstick (SMA of the     |
//|  candle body Close-Open). Fresh cross of a volatility-scaled       |
//|  band (PressureThreshold * ATR), confirmed by an EMA trend        |
//|  filter (price side + slope over SlopeBars). Symmetric long/short, |
//|  reversal exit on an opposing cross, ATR stop / target.           |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters / ctx.GetInput defaults) ----
input int    QstickPeriod      = 10;    // Qstick body-pressure SMA length
input int    TrendPeriod       = 50;    // EMA trend filter length
input int    SlopeBars         = 3;     // EMA slope agreement lookback
input double PressureThreshold = 0.15;  // trigger as a fraction of ATR
input int    AtrPeriod         = 14;    // ATR length (trigger + SL/TP)
input double AtrSlMult         = 2.0;   // stop  = entry -/+ mult * ATR
input double AtrTpMult         = 3.0;   // target= entry +/- mult * ATR
input double Lots              = 0.10;  // fixed lot size
input long   Magic             = 5127;  // one position per magic

//--- indicator handles ---------------------------------------------
int g_atrHandle = INVALID_HANDLE;   // ATR: trigger scale + stop/target
int g_emaHandle = INVALID_HANDLE;   // EMA of close: trend direction + slope

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

    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);
    g_emaHandle = iMA(_Symbol, _Period, TrendPeriod, 0, MODE_EMA, PRICE_CLOSE);

    if(g_atrHandle == INVALID_HANDLE || g_emaHandle == INVALID_HANDLE)
    {
        Print("QstickPressureShift: failed to create indicator handles");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Deinit                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
    if(g_emaHandle != INVALID_HANDLE) IndicatorRelease(g_emaHandle);
}

//+------------------------------------------------------------------+
//| New-bar detection (act once, on the just-completed bar)          |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| Qstick: SMA of (Close - Open) over `period` bars, newest bar at  |
//| shift `endShift` (shift 1 = the candle that just completed).     |
//+------------------------------------------------------------------+
double Qstick(int endShift, int period)
{
    double sum = 0.0;
    for(int s = endShift; s < endShift + period; s++)
        sum += iClose(_Symbol, _Period, s) - iOpen(_Symbol, _Period, s);
    return sum / period;
}

//+------------------------------------------------------------------+
//| Return the ticket of an open position for this symbol+magic,     |
//| or 0 when flat. (Equivalent of ctx.OpenPositions -> first.)      |
//+------------------------------------------------------------------+
ulong PositionTicket(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 t;
    }
    return 0;
}

//+------------------------------------------------------------------+
//| Open a trade with ATR stop / target (mirrors TryEnter).          |
//+------------------------------------------------------------------+
void TryEnter(bool isLong, double atr, double qNow, double thr)
{
    if(AtrSlMult * atr <= 0.0)
        return;

    double price, sl, tp;

    if(isLong)
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        sl    = NormalizeDouble(price - AtrSlMult * atr, _Digits);
        tp    = NormalizeDouble(price + AtrTpMult * atr, _Digits);
        bool res = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "QPS-long");
        PrintFormat("QstickPressureShift LONG @ %.5f SL %.5f TP %.5f q %.5f thr %.5f ATR %.5f -> %s",
                    price, sl, tp, qNow, thr, atr, (res ? "OK" : "FAIL"));
    }
    else
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        sl    = NormalizeDouble(price + AtrSlMult * atr, _Digits);
        tp    = NormalizeDouble(price - AtrTpMult * atr, _Digits);
        bool res = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "QPS-short");
        PrintFormat("QstickPressureShift SHORT @ %.5f SL %.5f TP %.5f q %.5f thr %.5f ATR %.5f -> %s",
                    price, sl, tp, qNow, thr, atr, (res ? "OK" : "FAIL"));
    }
}

//+------------------------------------------------------------------+
//| Tick                                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Act once, on the bar that just completed (shift 1).
    if(!IsNewBar()) return;

    // Enough history for Qstick(+1 for the prior cross), EMA slope, ATR.
    int needed = MathMax(QstickPeriod + 1,
                    MathMax(TrendPeriod + SlopeBars, AtrPeriod + 1)) + 1;
    if(Bars(_Symbol, _Period) < needed + 2) return;

    // ---- EMA buffer (index 0 = current forming bar) ----
    double emaBuf[];
    ArraySetAsSeries(emaBuf, true);
    int emaCount = SlopeBars + 3;
    if(CopyBuffer(g_emaHandle, 0, 0, emaCount, emaBuf) < emaCount) return;

    // ---- ATR buffer ----
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, 3, atrBuf) < 3) return;

    double atr = atrBuf[1];          // ATR on the just-completed bar
    if(atr <= 0.0) return;

    // ---- Qstick body-pressure now (shift 1) and prior bar (shift 2) ----
    double qNow  = Qstick(1, QstickPeriod);
    double qPrev = Qstick(2, QstickPeriod);

    // Volatility-scaled trigger band.
    double thr = PressureThreshold * atr;

    // Fresh cross of the pressure band (inside on prior bar, beyond now).
    bool crossUp   = (qPrev <= thr)  && (qNow > thr);
    bool crossDown = (qPrev >= -thr) && (qNow < -thr);

    // ---- EMA trend filter: direction (price vs EMA) + slope over SlopeBars ----
    double emaNow   = emaBuf[1];             // EMA on the just-completed bar
    double emaPast  = emaBuf[1 + SlopeBars]; // EMA SlopeBars earlier
    double curClose = iClose(_Symbol, _Period, 1);

    bool uptrend   = curClose > emaNow && emaNow > emaPast;
    bool downtrend = curClose < emaNow && emaNow < emaPast;

    bool longSignal  = crossUp   && uptrend;
    bool shortSignal = crossDown && downtrend;

    // ---- Manage an existing position: reversal exit on an opposing cross ----
    ulong ticket = PositionTicket(Magic);
    if(ticket != 0)
    {
        PositionSelectByTicket(ticket);
        long ptype = PositionGetInteger(POSITION_TYPE);
        if((ptype == POSITION_TYPE_BUY  && crossDown) ||
           (ptype == POSITION_TYPE_SELL && crossUp))
        {
            bool res = trade.PositionClose(ticket);
            PrintFormat("QstickPressureShift reversal-exit %s ticket %I64u qNow %.5f thr %.5f -> %s",
                        (ptype == POSITION_TYPE_BUY ? "Buy" : "Sell"),
                        ticket, qNow, thr, (res ? "OK" : "FAIL"));
        }
        return;   // one position per magic at a time
    }

    // ---- Flat: open on a fresh, trend-aligned pressure shift ----
    if(longSignal)
        TryEnter(true, atr, qNow, thr);
    else if(shortSignal)
        TryEnter(false, atr, qNow, thr);
}
//+------------------------------------------------------------------+
