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
Post a Comment