//+------------------------------------------------------------------+
//|              AcceleratorOscillatorSaucerSignal.mq5               |
//|  Bill Williams' Accelerator Oscillator (AC) traded with its      |
//|  authentic asymmetric "saucer" signal (2 bars with-trend,        |
//|  3 bars counter-trend). Converted from the Algobot C# strategy.  |
//|                                                                  |
//|  AO = SMA(median, Fast) - SMA(median, Slow), median=(H+L)/2      |
//|  AC = AO - SMA(AO, Signal)                                        |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters / OnInit) ------------------
input int    FastAo       = 5;       // AO fast median SMA period
input int    SlowAo       = 34;      // AO slow median SMA period
input int    AcSignal     = 5;       // AC signal SMA period (over AO)
input int    AtrPeriod    = 14;      // ATR period for stop distance
input double AtrStop      = 1.20;    // ATR multiple beyond signal-bar extreme
input double RewardRisk   = 1.60;    // fixed reward:risk take-profit
input int    MaxSpreadPts = 100;     // max spread (points) to allow a fill
input double Lots         = 0.10;    // fixed lot size
input long   Magic        = 52171;   // magic number (one position per magic)

//--- indicator handles --------------------------------------------
int    g_atrHandle = INVALID_HANDLE;

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

    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);
    if(g_atrHandle == INVALID_HANDLE)
    {
        Print("Failed to create ATR handle");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Act once per freshly closed bar.                                 |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // Need enough closed history for AO(shift up to 3) + its Signal SMA + ATR.
    // (matches C#: need = Max(SlowAo + AcSignal + 5, AtrPeriod + 2), over closed bars)
    int need = MathMax(SlowAo + AcSignal + 5, AtrPeriod + 2);
    if(Bars(_Symbol, _Period) < need + 2) return;   // +1 forming bar, +1 margin

    // One position at a time for this magic — SL/TP manage the exit.
    if(HasPosition(Magic)) return;

    // Realistic fills only.
    if(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPts) return;

    // Accelerator Oscillator at C# shifts 0..3 (0 = latest closed bar).
    double ac0 = Ac(0), ac1 = Ac(1), ac2 = Ac(2), ac3 = Ac(3);
    if(ac0 == EMPTY_VALUE || ac1 == EMPTY_VALUE ||
       ac2 == EMPTY_VALUE || ac3 == EMPTY_VALUE) return;

    // ATR at the just-closed signal bar (shift 1).
    double atr = AtrAtShift(1);
    if(atr == EMPTY_VALUE || atr <= 0) return;

    // Signal bar = the just-closed bar (MQL5 shift 1).
    double cHigh = iHigh(_Symbol, _Period, 1);
    double cLow  = iLow(_Symbol,  _Period, 1);

    // ---- LONG saucers ----
    bool longAbove = ac0 > 0 && ac0 > ac1 && ac1 > ac2;                // 2 rising, with-trend
    bool longBelow = ac0 < 0 && ac0 > ac1 && ac1 > ac2 && ac2 > ac3;   // 3 rising, early turn
    if(longAbove || longBelow)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = cLow - AtrStop * atr;
        double risk  = entry - sl;
        if(risk > 0)
            Send(ORDER_TYPE_BUY, entry, sl, entry + RewardRisk * risk,
                 longBelow ? "AC saucer long (turn)" : "AC saucer long");
        return;
    }

    // ---- SHORT saucers ----
    bool shortBelow = ac0 < 0 && ac0 < ac1 && ac1 < ac2;               // 2 falling, with-trend
    bool shortAbove = ac0 > 0 && ac0 < ac1 && ac1 < ac2 && ac2 < ac3;  // 3 falling, early turn
    if(shortBelow || shortAbove)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = cHigh + AtrStop * atr;
        double risk  = sl - entry;
        if(risk > 0)
            Send(ORDER_TYPE_SELL, entry, sl, entry - RewardRisk * risk,
                 shortAbove ? "AC saucer short (turn)" : "AC saucer short");
    }
}

//+------------------------------------------------------------------+
//| Accelerator Oscillator at C# bar-shift `csShift`                 |
//| (0 = latest closed bar): AC = AO - SMA(AO, Signal).              |
//+------------------------------------------------------------------+
double Ac(int csShift)
{
    double ao = Ao(csShift);
    if(ao == EMPTY_VALUE) return EMPTY_VALUE;
    double sum = 0.0;
    for(int k = 0; k < AcSignal; k++)
    {
        double a = Ao(csShift + k);
        if(a == EMPTY_VALUE) return EMPTY_VALUE;
        sum += a;
    }
    return ao - sum / AcSignal;
}

//+------------------------------------------------------------------+
//| Awesome Oscillator at C# bar-shift `csShift`:                    |
//| SMA(median, Fast) - SMA(median, Slow).                           |
//+------------------------------------------------------------------+
double Ao(int csShift)
{
    // C# shift s maps to MQL5 shift s+1 (latest closed bar = shift 1).
    double fast = MedianSma(csShift + 1, FastAo);
    double slow = MedianSma(csShift + 1, SlowAo);
    if(fast == EMPTY_VALUE || slow == EMPTY_VALUE) return EMPTY_VALUE;
    return fast - slow;
}

//+------------------------------------------------------------------+
//| SMA of median price (H+L)/2 over `period` bars starting at the   |
//| given MQL5 bar shift (inclusive, going back in time).            |
//+------------------------------------------------------------------+
double MedianSma(int startShift, int period)
{
    int total = Bars(_Symbol, _Period);
    if(startShift + period > total) return EMPTY_VALUE;   // not enough history
    double sum = 0.0;
    for(int i = 0; i < period; i++)
    {
        double h = iHigh(_Symbol, _Period, startShift + i);
        double l = iLow(_Symbol,  _Period, startShift + i);
        sum += (h + l) / 2.0;
    }
    return sum / period;
}

//+------------------------------------------------------------------+
//| ATR value at a given MQL5 bar shift.                             |
//+------------------------------------------------------------------+
double AtrAtShift(int shift)
{
    double buf[];
    ArraySetAsSeries(buf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, shift + 1, buf) < shift + 1) return EMPTY_VALUE;
    return buf[shift];
}

//+------------------------------------------------------------------+
//| Send a market order with absolute SL/TP prices.                  |
//+------------------------------------------------------------------+
void Send(ENUM_ORDER_TYPE type, double price, double sl, double tp, string comment)
{
    int    dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double nSl = NormalizeDouble(sl, dg);
    double nTp = NormalizeDouble(tp, dg);

    bool ok;
    if(type == ORDER_TYPE_BUY)
        ok = trade.Buy(Lots, _Symbol, 0.0, nSl, nTp, comment);   // 0.0 = market (Ask)
    else
        ok = trade.Sell(Lots, _Symbol, 0.0, nSl, nTp, comment);  // 0.0 = market (Bid)

    PrintFormat("%s @ %s sl=%s tp=%s -> ok=%s ret=%d",
                comment,
                DoubleToString(price, dg),
                DoubleToString(nSl, dg),
                DoubleToString(nTp, dg),
                (string)ok, trade.ResultRetcode());
}

//+------------------------------------------------------------------+
//| True only on the first tick of a newly opened bar.               |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| One open position per (symbol, magic)?                           |
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
