Trend Following + Pullback + ATR Risk Management Strategy - Auto Trading Bot for Forex Gold 0.05

 #property strict

#property version "1.00"


#include <Trade/Trade.mqh>

CTrade trade;


#define SIG_NONE 0

#define SIG_BUY  1

#define SIG_SELL -1


// ---------------- USER INPUTS ----------------

input string          InpSymbols                         = "XAUUSD,BTCUSD"; // Broker symbols exact likho: XAUUSDm,BTCUSDm etc.

input ENUM_TIMEFRAMES InpSignalTF                        = PERIOD_M5;

input ENUM_TIMEFRAMES InpTrendTF                         = PERIOD_H1;

input long            InpMagic                           = 26011301;

input int             InpTimerSeconds                    = 2;

input int             InpSlippagePoints                  = 30;

input int             InpMaxSymbols                      = 5;


// Risk / Burst

input double          InpRiskPercentPerSignal            = 0.30;  // $10 cent account ke liye safe start

input double          InpHardMaxRiskPercentPerSignal     = 1.20;  // hard cap per signal

input double          InpMaxLotPerOrder                  = 0.01;

input double          InpMaxTotalLotsPerSignal           = 0.03;

input bool            InpUseMinLotWhenRiskTooSmall       = true;

input bool            InpAllowMinLotBurst                = false; // true = zyada micro trades, risk high ho sakta hai

input int             InpBurstTrades                     = 3;

input int             InpMaxPositionsPerSymbol           = 3;

input int             InpMaxPositionsTotal               = 6;

input double          InpMaxMarginUsePercent             = 35.0;


// Strategy

input int             InpTrendEMA                        = 200;

input int             InpFastEMA                         = 20;

input int             InpSlowEMA                         = 50;

input int             InpRSIPeriod                       = 14;

input int             InpATRPeriod                       = 14;

input int             InpADXPeriod                       = 14;

input double          InpADXMin                          = 18.0;


input double          InpRSIBuyMin                       = 50.0;

input double          InpRSIBuyMax                       = 68.0;

input double          InpRSISellMin                      = 32.0;

input double          InpRSISellMax                      = 50.0;


input double          InpSL_ATR_Mult                     = 1.60;

input double          InpTP_ATR_Mult                     = 0.90;

input double          InpMaxSignalCandleATR              = 2.20; // big spike candle avoid


// Filters

input double          InpMinATRPoints                    = 0.0;  // 0 = off

input double          InpMaxATRPoints                    = 0.0;  // 0 = off

input double          InpMaxSpreadPointsXAU              = 80.0;

input double          InpMaxSpreadPointsBTC              = 3000.0;

input double          InpMaxSpreadPointsDefault          = 50.0;

input bool            InpTradeOnlyOnNewBar               = true;

input int             InpCooldownBars                    = 2;

input bool            InpAllowAddOnSameDirection         = false;


// Exits / Safety

input bool            InpUseBasketTakeProfit             = true;

input double          InpBasketTPMoney                   = 1.00; // Cent account me 1.00 = 1 USC approx

input double          InpBasketTPPerPosition             = 0.30;

input double          InpBasketMaxLossPercent            = 2.0;

input double          InpDailyLossPercent                = 5.0;

input double          InpMaxEquityDDPercent              = 12.0;

input bool            InpCloseAllOnRiskStop              = true;

input int             InpMaxHoldMinutes                  = 180;

input bool            InpCloseTimedOutBasketEvenIfLoss   = false;

input bool            InpCloseOnOppositeSignal           = false;


// Breakeven / trailing

input bool            InpUseBreakeven                    = true;

input double          InpBE_ATR_Mult                     = 0.60;

input double          InpBE_Lock_ATR_Mult                = 0.05;

input bool            InpUseATRTrailing                  = true;

input double          InpTrailStart_ATR_Mult             = 0.90;

input double          InpTrailDist_ATR_Mult              = 0.70;


// ---------------- GLOBALS ----------------

struct SignalData

{

   double emaTrend1, emaTrend2;

   double emaFast1,  emaFast2;

   double emaSlow1,  emaSlow2;

   double rsi1,      rsi2;

   double atr1;

   double adx1, plusDI1, minusDI1;

   double closeSig1, closeSig2, highSig1, lowSig1;

   double closeTrend1;

};


string   gSymbols[];

int      gHTrend[];

int      gHFast[];

int      gHSlow[];

int      gHRSI[];

int      gHATR[];

int      gHADX[];

datetime gLastBar[];

datetime gLastTradeTime[];


int      gCount = 0;

double   gInitialEquity = 0.0;

double   gDayStartEquity = 0.0;

int      gDayKey = 0;

bool     gPausedToday = false;

bool     gHardPaused = false;

bool     gBusy = false;


// ---------------- BASIC HELPERS ----------------

string TrimString(string s)

{

   s = StringTrimLeft(s);

   s = StringTrimRight(s);

   return s;

}


int DayKey(datetime t)

{

   MqlDateTime dt;

   TimeToStruct(t, dt);

   return dt.year * 10000 + dt.mon * 100 + dt.day;

}


void ResizeArrays(const int size)

{

   ArrayResize(gSymbols, size);

   ArrayResize(gHTrend, size);

   ArrayResize(gHFast, size);

   ArrayResize(gHSlow, size);

   ArrayResize(gHRSI, size);

   ArrayResize(gHATR, size);

   ArrayResize(gHADX, size);

   ArrayResize(gLastBar, size);

   ArrayResize(gLastTradeTime, size);

}


int VolumeDigits(double step)

{

   if(step <= 0.0) return 2;


   int digits = 0;

   double x = step;

   while(MathAbs(x - MathRound(x)) > 1e-8 && digits < 8)

   {

      x *= 10.0;

      digits++;

   }

   return digits;

}


double FloorVolume(const string sym, double volume)

{

   double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);

   double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);

   double step   = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);


   if(minLot <= 0.0 || maxLot <= 0.0) return 0.0;

   if(step <= 0.0) step = minLot;


   if(volume < minLot - 1e-12) return 0.0;

   if(volume > maxLot) volume = maxLot;


   double steps = MathFloor((volume - minLot) / step + 1e-9);

   double v = minLot + steps * step;


   if(v < minLot) v = minLot;

   if(v > maxLot) v = maxLot;


   return NormalizeDouble(v, VolumeDigits(step));

}


double RiskPerLot(const string sym, const double slDistance)

{

   double tickSize  = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);

   double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE_LOSS);


   if(tickValue <= 0.0)

      tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);


   if(tickSize <= 0.0 || tickValue <= 0.0 || slDistance <= 0.0)

      return 0.0;


   return (slDistance / tickSize) * tickValue;

}


double MinStopDistance(const string sym)

{

   double point = SymbolInfoDouble(sym, SYMBOL_POINT);

   long stops   = SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);

   long freeze  = SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL);

   long level   = stops;


   if(freeze > level) level = freeze;

   if(point <= 0.0) return 0.0;


   return (level + 2) * point;

}


// ---------------- INIT ----------------

int OnInit()

{

   trade.SetExpertMagicNumber(InpMagic);

   trade.SetDeviationInPoints(InpSlippagePoints);


   gInitialEquity = AccountInfoDouble(ACCOUNT_EQUITY);

   gDayStartEquity = gInitialEquity;

   gDayKey = DayKey(TimeCurrent());


   if(!InitSymbols())

      return INIT_FAILED;


   int sec = InpTimerSeconds;

   if(sec < 1) sec = 1;

   EventSetTimer(sec);


   Print("UltraCentBurst EA initialized. Symbols loaded: ", gCount);

   return INIT_SUCCEEDED;

}


bool InitSymbols()

{

   string parts[];

   int n = StringSplit(InpSymbols, (ushort)',', parts);


   if(n <= 0)

   {

      Print("No symbols found in InpSymbols.");

      return false;

   }


   int maxSymbols = InpMaxSymbols;

   if(maxSymbols < 1) maxSymbols = 1;


   for(int p = 0; p < n && gCount < maxSymbols; p++)

   {

      string sym = TrimString(parts[p]);

      if(sym == "") continue;


      if(!SymbolSelect(sym, true))

      {

         Print("Cannot select symbol: ", sym, ". Check broker symbol name.");

         continue;

      }


      int ht = iMA(sym, InpTrendTF,  InpTrendEMA, 0, MODE_EMA, PRICE_CLOSE);

      int hf = iMA(sym, InpSignalTF, InpFastEMA,  0, MODE_EMA, PRICE_CLOSE);

      int hs = iMA(sym, InpSignalTF, InpSlowEMA,  0, MODE_EMA, PRICE_CLOSE);

      int hr = iRSI(sym, InpSignalTF, InpRSIPeriod, PRICE_CLOSE);

      int ha = iATR(sym, InpSignalTF, InpATRPeriod);

      int hd = iADX(sym, InpSignalTF, InpADXPeriod);


      if(ht == INVALID_HANDLE || hf == INVALID_HANDLE || hs == INVALID_HANDLE ||

         hr == INVALID_HANDLE || ha == INVALID_HANDLE || hd == INVALID_HANDLE)

      {

         Print("Indicator handle failed for: ", sym);


         if(ht != INVALID_HANDLE) IndicatorRelease(ht);

         if(hf != INVALID_HANDLE) IndicatorRelease(hf);

         if(hs != INVALID_HANDLE) IndicatorRelease(hs);

         if(hr != INVALID_HANDLE) IndicatorRelease(hr);

         if(ha != INVALID_HANDLE) IndicatorRelease(ha);

         if(hd != INVALID_HANDLE) IndicatorRelease(hd);


         continue;

      }


      int idx = gCount;

      ResizeArrays(gCount + 1);


      gSymbols[idx] = sym;

      gHTrend[idx] = ht;

      gHFast[idx]  = hf;

      gHSlow[idx]  = hs;

      gHRSI[idx]   = hr;

      gHATR[idx]   = ha;

      gHADX[idx]   = hd;

      gLastBar[idx] = 0;

      gLastTradeTime[idx] = 0;


      gCount++;

      Print("Loaded symbol: ", sym);

   }


   return (gCount > 0);

}


void OnDeinit(const int reason)

{

   EventKillTimer();


   for(int i = 0; i < gCount; i++)

   {

      if(gHTrend[i] != INVALID_HANDLE) IndicatorRelease(gHTrend[i]);

      if(gHFast[i]  != INVALID_HANDLE) IndicatorRelease(gHFast[i]);

      if(gHSlow[i]  != INVALID_HANDLE) IndicatorRelease(gHSlow[i]);

      if(gHRSI[i]   != INVALID_HANDLE) IndicatorRelease(gHRSI[i]);

      if(gHATR[i]   != INVALID_HANDLE) IndicatorRelease(gHATR[i]);

      if(gHADX[i]   != INVALID_HANDLE) IndicatorRelease(gHADX[i]);

   }

}


void OnTick()

{

   RunEA();

}


void OnTimer()

{

   RunEA();

}


// ---------------- MAIN LOOP ----------------

void RunEA()

{

   if(gBusy) return;

   gBusy = true;


   ResetDailyIfNeeded();

   ManageAllOpenPositions();

   CheckGlobalStops();


   if(gPausedToday || gHardPaused || !AlgoAllowed())

   {

      gBusy = false;

      return;

   }


   int ps = PeriodSeconds(InpSignalTF);

   if(ps <= 0) ps = 60;


   int coolBars = InpCooldownBars;

   if(coolBars < 0) coolBars = 0;


   for(int i = 0; i < gCount; i++)

   {

      string sym = gSymbols[i];


      if(InpTradeOnlyOnNewBar && !IsNewBar(i))

         continue;


      if(!IsSymbolTradingAllowed(sym, SIG_NONE))

         continue;


      if(!SpreadOK(sym))

         continue;


      if(CountAllEAPositions() >= InpMaxPositionsTotal)

         break;


      int posCount = CountSymbolPositions(sym);

      if(posCount >= InpMaxPositionsPerSymbol)

         continue;


      if(posCount > 0 && !InpAllowAddOnSameDirection)

         continue;


      if(gLastTradeTime[i] > 0 && (TimeCurrent() - gLastTradeTime[i]) < ps * coolBars)

         continue;


      SignalData d;

      if(!ReadSignalData(i, d))

         continue;


      int signal = BuildSignal(i, d);

      if(signal == SIG_NONE)

         continue;


      int existingDirection = SymbolPositionDirection(sym);

      if(existingDirection == 2)

         continue;


      if(posCount > 0 && existingDirection != signal)

         continue;


      if(!IsSymbolTradingAllowed(sym, signal))

         continue;


      OpenBurst(i, signal, d);

   }


   gBusy = false;

}


bool AlgoAllowed()

{

   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;

   if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;

   return true;

}


bool IsSymbolTradingAllowed(const string sym, const int signal)

{

   long mode = SymbolInfoInteger(sym, SYMBOL_TRADE_MODE);


   if(mode == SYMBOL_TRADE_MODE_DISABLED || mode == SYMBOL_TRADE_MODE_CLOSEONLY)

      return false;


   if(signal == SIG_BUY && mode == SYMBOL_TRADE_MODE_SHORTONLY)

      return false;


   if(signal == SIG_SELL && mode == SYMBOL_TRADE_MODE_LONGONLY)

      return false;


   return true;

}


// ---------------- DAILY / GLOBAL RISK ----------------

void ResetDailyIfNeeded()

{

   int today = DayKey(TimeCurrent());

   if(today != gDayKey)

   {

      gDayKey = today;

      gDayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);

      gPausedToday = false;

      Print("New trading day. Day start equity: ", DoubleToString(gDayStartEquity, 2));

   }

}


void CheckGlobalStops()

{

   double equity = AccountInfoDouble(ACCOUNT_EQUITY);


   if(!gPausedToday && InpDailyLossPercent > 0.0 && gDayStartEquity > 0.0)

   {

      double minEq = gDayStartEquity * (1.0 - InpDailyLossPercent / 100.0);

      if(equity <= minEq)

      {

         Print("Daily loss limit hit. Equity=", equity, " Limit=", minEq);

         if(InpCloseAllOnRiskStop) CloseAllEA("Daily loss stop");

         gPausedToday = true;

      }

   }


   if(!gHardPaused && InpMaxEquityDDPercent > 0.0 && gInitialEquity > 0.0)

   {

      double minEq = gInitialEquity * (1.0 - InpMaxEquityDDPercent / 100.0);

      if(equity <= minEq)

      {

         Print("Hard equity DD stop hit. Equity=", equity, " Limit=", minEq);

         if(InpCloseAllOnRiskStop) CloseAllEA("Hard equity DD stop");

         gHardPaused = true;

      }

   }

}


// ---------------- POSITION MANAGEMENT ----------------

void ManageAllOpenPositions()

{

   for(int i = 0; i < gCount; i++)

      ManageTrailing(i);


   for(int i = 0; i < gCount; i++)

      ManageBasket(i);

}


void ManageBasket(const int idx)

{

   string sym = gSymbols[idx];


   int count = 0;

   datetime oldest = 0;

   double profit = SymbolFloatingProfit(sym, count, oldest);


   if(count <= 0)

      return;


   if(InpUseBasketTakeProfit)

   {

      double target = InpBasketTPMoney;

      double byCount = InpBasketTPPerPosition * count;

      if(byCount > target) target = byCount;


      if(target > 0.0 && profit >= target)

      {

         CloseSymbolPositions(sym, "Basket TP");

         return;

      }

   }


   if(InpBasketMaxLossPercent > 0.0)

   {

      double lossLimit = AccountInfoDouble(ACCOUNT_BALANCE) * InpBasketMaxLossPercent / 100.0;

      if(lossLimit > 0.0 && profit <= -lossLimit)

      {

         CloseSymbolPositions(sym, "Basket loss limit");

         return;

      }

   }


   if(InpMaxHoldMinutes > 0 && oldest > 0)

   {

      if((TimeCurrent() - oldest) >= InpMaxHoldMinutes * 60)

      {

         if(profit >= 0.0 || InpCloseTimedOutBasketEvenIfLoss)

         {

            CloseSymbolPositions(sym, "Max hold time");

            return;

         }

      }

   }


   if(InpCloseOnOppositeSignal && profit > 0.0)

   {

      int dir = SymbolPositionDirection(sym);

      if(dir != SIG_NONE && dir != 2)

      {

         SignalData d;

         if(ReadSignalData(idx, d))

         {

            int sig = BuildSignal(idx, d);

            if(sig != SIG_NONE && sig == -dir)

            {

               CloseSymbolPositions(sym, "Opposite signal profit close");

               return;

            }

         }

      }

   }

}


void ManageTrailing(const int idx)

{

   if(!InpUseBreakeven && !InpUseATRTrailing)

      return;


   string sym = gSymbols[idx];


   SignalData d;

   if(!ReadSignalData(idx, d))

      return;


   double atr = d.atr1;

   if(atr <= 0.0)

      return;


   MqlTick tick;

   if(!SymbolInfoTick(sym, tick))

      return;


   double point = SymbolInfoDouble(sym, SYMBOL_POINT);

   int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);

   double minStop = MinStopDistance(sym);


   trade.SetExpertMagicNumber(InpMagic);

   trade.SetDeviationInPoints(InpSlippagePoints);

   trade.SetTypeFillingBySymbol(sym);


   for(int i = PositionsTotal() - 1; i >= 0; i--)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      if(PositionGetString(POSITION_SYMBOL) != sym) continue;


      long type = PositionGetInteger(POSITION_TYPE);

      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);

      double sl = PositionGetDouble(POSITION_SL);

      double tp = PositionGetDouble(POSITION_TP);


      if(type == POSITION_TYPE_BUY)

      {

         double profitDist = tick.bid - openPrice;

         if(profitDist <= 0.0) continue;


         double newSL = sl;

         bool hasNew = false;


         if(InpUseBreakeven && profitDist >= atr * InpBE_ATR_Mult)

         {

            double be = openPrice + atr * InpBE_Lock_ATR_Mult;

            if(sl == 0.0 || be > newSL)

            {

               newSL = be;

               hasNew = true;

            }

         }


         if(InpUseATRTrailing && profitDist >= atr * InpTrailStart_ATR_Mult)

         {

            double tr = tick.bid - atr * InpTrailDist_ATR_Mult;

            if(sl == 0.0 || tr > newSL)

            {

               newSL = tr;

               hasNew = true;

            }

         }


         if(hasNew)

         {

            double maxSL = tick.bid - minStop;

            if(newSL > maxSL) newSL = maxSL;

            newSL = NormalizeDouble(newSL, digits);


            if(newSL > 0.0 && (sl == 0.0 || newSL > sl + 2.0 * point))

               trade.PositionModify(ticket, newSL, tp);

         }

      }

      else if(type == POSITION_TYPE_SELL)

      {

         double profitDist = openPrice - tick.ask;

         if(profitDist <= 0.0) continue;


         double newSL = (sl > 0.0 ? sl : 1.0e100);

         bool hasNew = false;


         if(InpUseBreakeven && profitDist >= atr * InpBE_ATR_Mult)

         {

            double be = openPrice - atr * InpBE_Lock_ATR_Mult;

            if(sl == 0.0 || be < newSL)

            {

               newSL = be;

               hasNew = true;

            }

         }


         if(InpUseATRTrailing && profitDist >= atr * InpTrailStart_ATR_Mult)

         {

            double tr = tick.ask + atr * InpTrailDist_ATR_Mult;

            if(sl == 0.0 || tr < newSL)

            {

               newSL = tr;

               hasNew = true;

            }

         }


         if(hasNew)

         {

            double minSL = tick.ask + minStop;

            if(newSL < minSL) newSL = minSL;

            newSL = NormalizeDouble(newSL, digits);


            if(newSL > 0.0 && (sl == 0.0 || newSL < sl - 2.0 * point))

               trade.PositionModify(ticket, newSL, tp);

         }

      }

   }

}


// ---------------- SIGNAL ----------------

bool IsNewBar(const int idx)

{

   datetime t = iTime(gSymbols[idx], InpSignalTF, 0);

   if(t <= 0) return false;


   if(gLastBar[idx] != t)

   {

      gLastBar[idx] = t;

      return true;

   }


   return false;

}


bool ReadSignalData(const int idx, SignalData &d)

{

   string sym = gSymbols[idx];


   double trend[], fast[], slow[], rsi[], atr[], adx[], plusDI[], minusDI[];


   ArrayResize(trend, 3);

   ArrayResize(fast, 3);

   ArrayResize(slow, 3);

   ArrayResize(rsi, 3);

   ArrayResize(atr, 3);

   ArrayResize(adx, 3);

   ArrayResize(plusDI, 3);

   ArrayResize(minusDI, 3);


   ArraySetAsSeries(trend, true);

   ArraySetAsSeries(fast, true);

   ArraySetAsSeries(slow, true);

   ArraySetAsSeries(rsi, true);

   ArraySetAsSeries(atr, true);

   ArraySetAsSeries(adx, true);

   ArraySetAsSeries(plusDI, true);

   ArraySetAsSeries(minusDI, true);


   if(CopyBuffer(gHTrend[idx], 0, 0, 3, trend) < 3) return false;

   if(CopyBuffer(gHFast[idx],  0, 0, 3, fast)  < 3) return false;

   if(CopyBuffer(gHSlow[idx],  0, 0, 3, slow)  < 3) return false;

   if(CopyBuffer(gHRSI[idx],   0, 0, 3, rsi)   < 3) return false;

   if(CopyBuffer(gHATR[idx],   0, 0, 3, atr)   < 3) return false;

   if(CopyBuffer(gHADX[idx],   0, 0, 3, adx)   < 3) return false;

   if(CopyBuffer(gHADX[idx],   1, 0, 3, plusDI) < 3) return false;

   if(CopyBuffer(gHADX[idx],   2, 0, 3, minusDI) < 3) return false;


   d.emaTrend1 = trend[1];

   d.emaTrend2 = trend[2];

   d.emaFast1  = fast[1];

   d.emaFast2  = fast[2];

   d.emaSlow1  = slow[1];

   d.emaSlow2  = slow[2];

   d.rsi1      = rsi[1];

   d.rsi2      = rsi[2];

   d.atr1      = atr[1];

   d.adx1      = adx[1];

   d.plusDI1   = plusDI[1];

   d.minusDI1  = minusDI[1];


   d.closeSig1 = iClose(sym, InpSignalTF, 1);

   d.closeSig2 = iClose(sym, InpSignalTF, 2);

   d.highSig1  = iHigh(sym, InpSignalTF, 1);

   d.lowSig1   = iLow(sym, InpSignalTF, 1);

   d.closeTrend1 = iClose(sym, InpTrendTF, 1);


   if(d.closeSig1 <= 0.0 || d.closeTrend1 <= 0.0 || d.atr1 <= 0.0)

      return false;


   return true;

}


int BuildSignal(const int idx, SignalData &d)

{

   string sym = gSymbols[idx];


   double point = SymbolInfoDouble(sym, SYMBOL_POINT);

   if(point <= 0.0 || d.atr1 <= 0.0)

      return SIG_NONE;


   double atrPoints = d.atr1 / point;


   if(InpMinATRPoints > 0.0 && atrPoints < InpMinATRPoints)

      return SIG_NONE;


   if(InpMaxATRPoints > 0.0 && atrPoints > InpMaxATRPoints)

      return SIG_NONE;


   double candleRange = d.highSig1 - d.lowSig1;

   if(InpMaxSignalCandleATR > 0.0 && candleRange > d.atr1 * InpMaxSignalCandleATR)

      return SIG_NONE;


   bool trendUp   = (d.closeTrend1 > d.emaTrend1 && d.emaTrend1 >= d.emaTrend2);

   bool trendDown = (d.closeTrend1 < d.emaTrend1 && d.emaTrend1 <= d.emaTrend2);


   bool buyBase =

      trendUp &&

      d.emaFast1 > d.emaSlow1 &&

      d.adx1 >= InpADXMin &&

      d.plusDI1 > d.minusDI1 &&

      d.rsi1 >= InpRSIBuyMin &&

      d.rsi1 <= InpRSIBuyMax;


   bool sellBase =

      trendDown &&

      d.emaFast1 < d.emaSlow1 &&

      d.adx1 >= InpADXMin &&

      d.minusDI1 > d.plusDI1 &&

      d.rsi1 <= InpRSISellMax &&

      d.rsi1 >= InpRSISellMin;


   double tolerance = d.atr1 * 0.15;


   bool buyPullback =

      ((d.lowSig1 <= d.emaFast1 + tolerance) && (d.closeSig1 > d.emaFast1)) ||

      (d.rsi2 < 50.0 && d.rsi1 >= 50.0);


   bool sellPullback =

      ((d.highSig1 >= d.emaFast1 - tolerance) && (d.closeSig1 < d.emaFast1)) ||

      (d.rsi2 > 50.0 && d.rsi1 <= 50.0);


   if(buyBase && buyPullback)

      return SIG_BUY;


   if(sellBase && sellPullback)

      return SIG_SELL;


   return SIG_NONE;

}


// ---------------- SPREAD ----------------

double GetMaxSpreadPoints(const string sym)

{

   string s = sym;

   StringToUpper(s);


   if(StringFind(s, "XAU") >= 0 || StringFind(s, "GOLD") >= 0)

      return InpMaxSpreadPointsXAU;


   if(StringFind(s, "BTC") >= 0)

      return InpMaxSpreadPointsBTC;


   return InpMaxSpreadPointsDefault;

}


bool SpreadOK(const string sym)

{

   MqlTick tick;

   if(!SymbolInfoTick(sym, tick))

      return false;


   double point = SymbolInfoDouble(sym, SYMBOL_POINT);

   if(point <= 0.0)

      return false;


   double spread = (tick.ask - tick.bid) / point;

   double maxSpread = GetMaxSpreadPoints(sym);


   if(maxSpread > 0.0 && spread > maxSpread)

      return false;


   return true;

}


// ---------------- ORDER OPENING ----------------

bool PrepareBurstPlan(const string sym, const double slDistance, const int desiredOrders,

                      double &perLot, int &orders)

{

   perLot = 0.0;

   orders = 0;


   double balance = AccountInfoDouble(ACCOUNT_BALANCE);

   if(balance <= 0.0)

      return false;


   double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);

   double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);


   if(minLot <= 0.0 || maxLot <= 0.0)

      return false;


   double riskPL = RiskPerLot(sym, slDistance);

   if(riskPL <= 0.0)

      return false;


   double riskTarget = balance * InpRiskPercentPerSignal / 100.0;

   double hardRisk   = balance * InpHardMaxRiskPercentPerSignal / 100.0;


   if(riskTarget <= 0.0)

      return false;


   if(hardRisk <= 0.0)

      hardRisk = riskTarget;


   double maxPerOrder = InpMaxLotPerOrder;

   if(maxPerOrder < minLot) maxPerOrder = minLot;

   if(maxPerOrder > maxLot) maxPerOrder = maxLot;


   int desired = desiredOrders;

   if(desired < 1) desired = 1;


   double inputTotalCap = maxPerOrder * desired;


   if(InpMaxTotalLotsPerSignal > 0.0)

   {

      double cap = InpMaxTotalLotsPerSignal;

      if(cap < minLot) cap = minLot;

      if(cap < inputTotalCap) inputTotalCap = cap;

   }


   double targetLots = riskTarget / riskPL;

   double hardLots   = hardRisk / riskPL;


   double totalLots = targetLots;

   if(inputTotalCap < totalLots) totalLots = inputTotalCap;

   if(hardLots < totalLots) totalLots = hardLots;


   double minRisk = riskPL * minLot;


   if(totalLots < minLot)

   {

      if(!InpUseMinLotWhenRiskTooSmall)

         return false;


      if(minRisk > hardRisk)

         return false;


      totalLots = minLot;

   }


   if(InpAllowMinLotBurst)

   {

      int maxByRisk = (int)MathFloor(hardRisk / minRisk + 1e-9);

      int maxByCap  = (int)MathFloor(inputTotalCap / minLot + 1e-9);


      orders = desired;

      if(orders > maxByRisk) orders = maxByRisk;

      if(orders > maxByCap)  orders = maxByCap;


      if(orders < 1)

         return false;


      perLot = FloorVolume(sym, minLot);

      return (perLot >= minLot);

   }


   int maxByVolume = (int)MathFloor(totalLots / minLot + 1e-9);

   if(maxByVolume < 1)

      return false;


   orders = desired;

   if(orders > maxByVolume)

      orders = maxByVolume;


   double rawPerOrder = totalLots / orders;

   if(rawPerOrder > maxPerOrder)

      rawPerOrder = maxPerOrder;


   perLot = FloorVolume(sym, rawPerOrder);


   if(perLot < minLot)

   {

      perLot = minLot;

      orders = maxByVolume;

      if(orders > desired) orders = desired;

   }


   while(orders > 0 && riskPL * perLot * orders > hardRisk + 0.0000001)

      orders--;


   if(orders < 1)

      return false;


   return true;

}


bool HasEnoughMargin(const string sym, ENUM_ORDER_TYPE type, const double volume,

                     const double price, const int remainingOrders)

{

   if(InpMaxMarginUsePercent <= 0.0)

      return true;


   double margin = 0.0;

   if(!OrderCalcMargin(type, sym, volume, price, margin))

      return true;


   double free = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

   if(free <= 0.0)

      return false;


   double need = margin * remainingOrders;

   double allowed = free * InpMaxMarginUsePercent / 100.0;


   return (need <= allowed);

}


bool OpenBurst(const int idx, const int signal, SignalData &d)

{

   string sym = gSymbols[idx];


   MqlTick tick;

   if(!SymbolInfoTick(sym, tick))

      return false;


   double point = SymbolInfoDouble(sym, SYMBOL_POINT);

   int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);


   if(point <= 0.0)

      return false;


   double minStop = MinStopDistance(sym);

   double slDist = d.atr1 * InpSL_ATR_Mult;

   double tpDist = d.atr1 * InpTP_ATR_Mult;


   if(slDist < minStop) slDist = minStop;

   if(tpDist < minStop) tpDist = minStop;


   int slotsSymbol = InpMaxPositionsPerSymbol - CountSymbolPositions(sym);

   int slotsTotal  = InpMaxPositionsTotal - CountAllEAPositions();

   int slots = slotsSymbol;

   if(slotsTotal < slots) slots = slotsTotal;


   if(slots <= 0)

      return false;


   int desired = InpBurstTrades;

   if(desired < 1) desired = 1;

   if(desired > slots) desired = slots;


   double perLot = 0.0;

   int orders = 0;


   if(!PrepareBurstPlan(sym, slDist, desired, perLot, orders))

      return false;


   if(orders > slots) orders = slots;

   if(orders <= 0 || perLot <= 0.0)

      return false;


   ENUM_ORDER_TYPE orderType = (signal == SIG_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);


   trade.SetExpertMagicNumber(InpMagic);

   trade.SetDeviationInPoints(InpSlippagePoints);

   trade.SetTypeFillingBySymbol(sym);


   int opened = 0;


   for(int k = 0; k < orders; k++)

   {

      if(CountAllEAPositions() >= InpMaxPositionsTotal) break;

      if(CountSymbolPositions(sym) >= InpMaxPositionsPerSymbol) break;


      if(!SymbolInfoTick(sym, tick))

         break;


      double entry = (signal == SIG_BUY ? tick.ask : tick.bid);

      double sl = 0.0;

      double tp = 0.0;


      if(signal == SIG_BUY)

      {

         sl = entry - slDist;

         tp = entry + tpDist;


         double maxSL = tick.bid - minStop;

         if(sl > maxSL) sl = maxSL;


         double minTP = tick.ask + minStop;

         if(tp < minTP) tp = minTP;

      }

      else

      {

         sl = entry + slDist;

         tp = entry - tpDist;


         double minSL = tick.ask + minStop;

         if(sl < minSL) sl = minSL;


         double maxTP = tick.bid - minStop;

         if(tp > maxTP) tp = maxTP;

      }


      sl = NormalizeDouble(sl, digits);

      tp = NormalizeDouble(tp, digits);


      if(!HasEnoughMargin(sym, orderType, perLot, entry, orders - k))

      {

         Print("Not enough safe margin for ", sym, ". Burst stopped.");

         break;

      }


      string comment = "UCB " + (signal == SIG_BUY ? "BUY" : "SELL") + " #" + IntegerToString(k + 1);


      bool ok = false;

      if(signal == SIG_BUY)

         ok = trade.Buy(perLot, sym, 0.0, sl, tp, comment);

      else

         ok = trade.Sell(perLot, sym, 0.0, sl, tp, comment);


      if(!ok)

      {

         Print("Order failed ", sym,

               " retcode=", trade.ResultRetcode(),

               " ", trade.ResultRetcodeDescription());

      }

      else

      {

         opened++;

      }

   }


   if(opened > 0)

   {

      gLastTradeTime[idx] = TimeCurrent();

      double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);

      Print("Opened ", opened, "/", orders, " ",

            (signal == SIG_BUY ? "BUY" : "SELL"),

            " burst on ", sym,

            " lot=", DoubleToString(perLot, VolumeDigits(step)));

      return true;

   }


   return false;

}


// ---------------- POSITION COUNT / CLOSE ----------------

int CountAllEAPositions()

{

   int count = 0;


   for(int i = 0; i < PositionsTotal(); i++)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) == InpMagic)

         count++;

   }


   return count;

}


int CountSymbolPositions(const string sym)

{

   int count = 0;


   for(int i = 0; i < PositionsTotal(); i++)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      if(PositionGetString(POSITION_SYMBOL) != sym) continue;


      count++;

   }


   return count;

}


int SymbolPositionDirection(const string sym)

{

   int buys = 0;

   int sells = 0;


   for(int i = 0; i < PositionsTotal(); i++)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      if(PositionGetString(POSITION_SYMBOL) != sym) continue;


      long type = PositionGetInteger(POSITION_TYPE);

      if(type == POSITION_TYPE_BUY) buys++;

      if(type == POSITION_TYPE_SELL) sells++;

   }


   if(buys > 0 && sells > 0) return 2;

   if(buys > 0) return SIG_BUY;

   if(sells > 0) return SIG_SELL;


   return SIG_NONE;

}


double SymbolFloatingProfit(const string sym, int &count, datetime &oldest)

{

   count = 0;

   oldest = 0;

   double profit = 0.0;


   for(int i = 0; i < PositionsTotal(); i++)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      if(PositionGetString(POSITION_SYMBOL) != sym) continue;


      profit += PositionGetDouble(POSITION_PROFIT);

      profit += PositionGetDouble(POSITION_SWAP);


      datetime t = (datetime)PositionGetInteger(POSITION_TIME);

      if(oldest == 0 || t < oldest)

         oldest = t;


      count++;

   }


   return profit;

}


void CloseSymbolPositions(const string sym, const string reason)

{

   trade.SetExpertMagicNumber(InpMagic);

   trade.SetDeviationInPoints(InpSlippagePoints);

   trade.SetTypeFillingBySymbol(sym);


   for(int i = PositionsTotal() - 1; i >= 0; i--)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      if(PositionGetString(POSITION_SYMBOL) != sym) continue;


      if(!trade.PositionClose(ticket))

      {

         Print("Close failed ", sym,

               " ticket=", ticket,

               " reason=", reason,

               " retcode=", trade.ResultRetcode(),

               " ", trade.ResultRetcodeDescription());

      }

   }


   Print("Closed basket on ", sym, ". Reason: ", reason);

}


void CloseAllEA(const string reason)

{

   trade.SetExpertMagicNumber(InpMagic);

   trade.SetDeviationInPoints(InpSlippagePoints);


   for(int i = PositionsTotal() - 1; i >= 0; i--)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket == 0) continue;

      if(!PositionSelectByTicket(ticket)) continue;


      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;


      string sym = PositionGetString(POSITION_SYMBOL);

      trade.SetTypeFillingBySymbol(sym);


      if(!trade.PositionClose(ticket))

      {

         Print("Close all failed ",

               " ticket=", ticket,

               " reason=", reason,

               " retcode=", trade.ResultRetcode(),

               " ", trade.ResultRetcodeDescription());

      }

   }


   Print("All EA positions close command sent. Reason: ", reason);

}

Comments

Popular posts from this blog

Karlos Algo Black V11 Sharp — Aihan Malik Daniyen— Extra Efficient

Karlos Alpha V11 Gentle - Dr Pnum AI | Auto Algo Trading Bot - UI VERSION

Karlos Algo Black V8 Sharp — Aihan Daniyen