Mt5pro-bot2/MT5ProBot.mq5

708 lines
26 KiB
MQL5

2026-10-08 09:07:44 +04:00
//+------------------------------------------------------------------+
//| MT5ProBot.mq5 |
//| Multi-strategy EA: EMA/ATR trend, session breakout, |
//| RSI/Bollinger mean-reversion, ADX-regime RSI |
//+------------------------------------------------------------------+
#property copyright "jamesta361"
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Multi-strategy Expert Advisor with a fixed stop-loss on every trade."
#property description "Risk-based position sizing, daily and overall loss limits, prop-firm mode."
#property description "No martingale. No grid. No averaging down."
#property description "Trading involves risk. Past performance does not guarantee future results."
#include <Trade\Trade.mqh>
CTrade trade;
//--- General -------------------------------------------------------
input group "=== General ==="
input ulong InpMagic = 240601; // Magic number
input ENUM_TIMEFRAMES InpTF = PERIOD_M15; // Signal timeframe
input int InpDeviation = 20; // Max slippage (points)
input int InpAtrPeriod = 14; // ATR period
//--- Strategies ----------------------------------------------------
input group "=== Strategy 1: EMA cross + ATR ==="
input bool InpUseEmaAtr = true; // Enable
input int InpEmaFast = 20; // Fast EMA
input int InpEmaSlow = 50; // Slow EMA
input int InpEmaTrend = 200; // Trend EMA (filter)
input double InpEmaSlAtr = 1.5; // SL = x ATR
input double InpEmaTpAtr = 3.0; // TP = x ATR
input group "=== Strategy 2: Session breakout (server time) ==="
input bool InpUseBreakout = true; // Enable
input int InpBrkRangeStart= 0; // Range start hour
input int InpBrkRangeEnd = 7; // Range end hour (breakout window opens)
input int InpBrkUntil = 11; // Breakout window closes (hour)
input double InpBrkSlAtr = 1.5; // SL = x ATR
input double InpBrkRR = 2.0; // TP = RR x SL
input group "=== Strategy 3: RSI + Bollinger mean-reversion ==="
input bool InpUseRsiMr = true; // Enable
input int InpMrRsiPeriod = 14; // RSI period
input double InpMrRsiLow = 30.0; // Oversold level
input double InpMrRsiHigh = 70.0; // Overbought level
input int InpMrBbPeriod = 20; // Bollinger period
input double InpMrBbDev = 2.0; // Bollinger deviation
input double InpMrSlAtr = 1.5; // SL = x ATR
input double InpMrTpAtr = 2.0; // TP = x ATR
input group "=== Strategy 4: ADX-regime RSI ==="
input bool InpUseRegime = true; // Enable
input int InpRgFast = 13; // Fast EMA
input int InpRgSlow = 34; // Slow EMA
input int InpRgRsiPeriod = 14; // RSI period
input double InpRgAdxThr = 25.0; // ADX: range / trend threshold
input double InpRgAdxMax = 60.0; // ADX: no entry above this
input double InpRgBuyRange = 48.0; // Range: buy if RSI below
input double InpRgSellRange = 52.0; // Range: sell if RSI above
input double InpRgOb = 70.0; // Trend: max RSI for buys
input double InpRgOs = 30.0; // Trend: min RSI for sells
input double InpRgSlAtr = 1.5; // SL = x ATR
input double InpRgTpAtr = 2.5; // TP = x ATR
//--- Risk ----------------------------------------------------------
input group "=== Risk management ==="
input double InpRiskPct = 0.5; // Risk per trade (% of equity)
input double InpMaxRiskMult = 3.0; // Skip trade if min lot risks more than x target
input int InpMaxOpen = 4; // Max open trades (all symbols, same magic)
input double InpDailyLossPct = 4.0; // Daily loss limit (%)
input double InpMaxDDPct = 9.0; // Max drawdown from peak equity (%)
input double InpBreakEvenAtr = 1.0; // Break-even after x ATR profit (0 = off)
input double InpTrailAtr = 1.5; // Trailing distance x ATR (0 = off)
input group "=== Prop firm mode ==="
input bool InpPropMode = false; // Enable prop firm mode
input double InpPropInitial = 100000; // Initial account size
input double InpPropDaily = 4.5; // Daily loss limit (%)
input double InpPropDD = 9.0; // Max drawdown from initial balance (%)
//--- Filters -------------------------------------------------------
input group "=== Filters (server time) ==="
input bool InpUseSession = true; // Trade only inside session
input int InpSessStart = 7; // Session start hour
input int InpSessEnd = 20; // Session end hour
input double InpMaxSpreadAtr = 20.0; // Max spread (% of ATR)
input int InpMaxSpreadPts = 0; // Max spread in points (0 = off)
input bool InpUseVolFilter = true; // Skip very low volatility
input double InpMinAtrRatio = 0.6; // Min ATR / average ATR(100)
input bool InpUseNews = false; // Pause around high-impact news (live only)
input int InpNewsBefore = 15; // Minutes before news
input int InpNewsAfter = 30; // Minutes after news
//--- Notifications -------------------------------------------------
input group "=== Notifications ==="
input bool InpPush = false; // Mobile push notifications
input string InpTgToken = ""; // Telegram bot token (optional)
input string InpTgChat = ""; // Telegram chat id (optional)
//+------------------------------------------------------------------+
//| Globals |
//+------------------------------------------------------------------+
struct Sig
{
int dir; // 1 buy, -1 sell, 0 none
double slDist; // price distance
double tpDist; // price distance
string tag; // short strategy tag
datetime day; // used by the breakout strategy
};
ENUM_TIMEFRAMES g_tf;
datetime g_lastBar = 0;
datetime g_brkDay = 0;
bool g_paused = false;
long g_tgOffset = 0;
int g_hAtr = INVALID_HANDLE;
int g_hEmaF = INVALID_HANDLE;
int g_hEmaS = INVALID_HANDLE;
int g_hEmaT = INVALID_HANDLE;
int g_hRsiMr = INVALID_HANDLE;
int g_hBb = INVALID_HANDLE;
int g_hRgF = INVALID_HANDLE;
int g_hRgS = INVALID_HANDLE;
int g_hRgRsi = INVALID_HANDLE;
int g_hAdx = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| Helpers |
//+------------------------------------------------------------------+
bool Bad(const double v)
{
return(v==EMPTY_VALUE || !MathIsValidNumber(v));
}
double Buf(const int handle, const int buffer, const int shift)
{
double a[1];
if(handle==INVALID_HANDLE) return(EMPTY_VALUE);
if(CopyBuffer(handle,buffer,shift,1,a)!=1) return(EMPTY_VALUE);
return(a[0]);
}
string GV(const string key)
{
return("MPB_"+(string)AccountInfoInteger(ACCOUNT_LOGIN)+"_"+(string)InpMagic+"_"+key);
}
datetime DayStart(const datetime t)
{
MqlDateTime d;
TimeToStruct(t,d);
d.hour=0; d.min=0; d.sec=0;
return(StructToTime(d));
}
int CountPositions(const bool onlyThisSymbol)
{
int n=0;
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
if(onlyThisSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
n++;
}
return(n);
}
void CloseAll()
{
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
trade.PositionClose(tk);
}
}
string UrlEncode(const string text)
{
uchar b[];
int n=StringToCharArray(text,b,0,WHOLE_ARRAY,CP_UTF8)-1;
string out="";
for(int i=0;i<n;i++)
{
uchar c=b[i];
if((c>='0' && c<='9')||(c>='A' && c<='Z')||(c>='a' && c<='z')||c=='-'||c=='_'||c=='.'||c=='~')
out+=CharToString(c);
else
out+=StringFormat("%%%02X",c);
}
return(out);
}
void TgSend(const string text)
{
if(InpTgToken=="" || InpTgChat=="" || MQLInfoInteger(MQL_TESTER)) return;
string url="https://api.telegram.org/bot"+InpTgToken+"/sendMessage";
string body="chat_id="+InpTgChat+"&text="+UrlEncode(text);
char post[], res[];
string hdr="Content-Type: application/x-www-form-urlencoded\r\n", rh;
StringToCharArray(body,post,0,StringLen(body));
ResetLastError();
if(WebRequest("POST",url,hdr,5000,post,res,rh)==-1)
Print("Telegram: add https://api.telegram.org to Tools > Options > Expert Advisors > Allow WebRequest. Error ",GetLastError());
}
void Notify(const string msg)
{
Print(msg);
if(MQLInfoInteger(MQL_TESTER)) return;
if(InpPush) SendNotification(msg);
TgSend(msg);
}
//+------------------------------------------------------------------+
//| Daily / drawdown tracking (shared between charts via globals) |
//+------------------------------------------------------------------+
bool IsHalted()
{
if(!GlobalVariableCheck(GV("haltDay"))) return(false);
return((datetime)GlobalVariableGet(GV("haltDay"))==DayStart(TimeCurrent()));
}
void UpdateDay()
{
double bal=AccountInfoDouble(ACCOUNT_BALANCE);
double eq =AccountInfoDouble(ACCOUNT_EQUITY);
datetime today=DayStart(TimeCurrent());
datetime stored=0;
if(GlobalVariableCheck(GV("day"))) stored=(datetime)GlobalVariableGet(GV("day"));
if(stored!=today)
{
GlobalVariableSet(GV("day"),(double)today);
GlobalVariableSet(GV("dayEq"),MathMax(bal,eq));
if(!InpPropMode) GlobalVariableSet(GV("peak"),eq); // peak resets each day (non-prop)
}
if(!GlobalVariableCheck(GV("peak"))) GlobalVariableSet(GV("peak"),eq);
if(!GlobalVariableCheck(GV("dayEq"))) GlobalVariableSet(GV("dayEq"),MathMax(bal,eq));
double peak=GlobalVariableGet(GV("peak"));
if(eq>peak) GlobalVariableSet(GV("peak"),eq);
}
double DailyLossPct()
{
double dayEq=GlobalVariableGet(GV("dayEq"));
if(dayEq<=0) return(0.0);
return((dayEq-AccountInfoDouble(ACCOUNT_EQUITY))/dayEq*100.0);
}
double DrawdownPct()
{
double ref=(InpPropMode && InpPropInitial>0) ? InpPropInitial : GlobalVariableGet(GV("peak"));
if(ref<=0) return(0.0);
return((ref-AccountInfoDouble(ACCOUNT_EQUITY))/ref*100.0);
}
bool RiskOk(string &why)
{
double dailyLim=InpPropMode ? InpPropDaily : InpDailyLossPct;
double ddLim =InpPropMode ? InpPropDD : InpMaxDDPct;
double dl=DailyLossPct();
double dd=DrawdownPct();
if(dl>=dailyLim){ why=StringFormat("Daily loss %.2f%% >= %.2f%%",dl,dailyLim); return(false); }
if(dd>=ddLim) { why=StringFormat("Drawdown %.2f%% >= %.2f%%",dd,ddLim); return(false); }
return(true);
}
void CheckKillSwitch()
{
if(IsHalted()) return;
string why;
if(RiskOk(why)) return;
GlobalVariableSet(GV("haltDay"),(double)DayStart(TimeCurrent()));
CloseAll();
Notify("KILL-SWITCH: "+why+". Positions closed, trading stopped until next day.");
}
//+------------------------------------------------------------------+
//| Strategies (closed bars only: shift 1 = last closed bar) |
//+------------------------------------------------------------------+
bool SigEmaAtr(Sig &sg)
{
double f1=Buf(g_hEmaF,0,1), f2=Buf(g_hEmaF,0,2);
double s1=Buf(g_hEmaS,0,1), s2=Buf(g_hEmaS,0,2);
double t1=Buf(g_hEmaT,0,1), a=Buf(g_hAtr,0,1);
if(Bad(f1)||Bad(f2)||Bad(s1)||Bad(s2)||Bad(t1)||Bad(a)||a<=0) return(false);
double c1=iClose(_Symbol,g_tf,1);
bool up=(f2<=s2 && f1>s1 && c1>t1);
bool dn=(f2>=s2 && f1<s1 && c1<t1);
if(!up && !dn) return(false);
sg.dir=up?1:-1; sg.slDist=InpEmaSlAtr*a; sg.tpDist=InpEmaTpAtr*a; sg.tag="EMA"; sg.day=0;
return(true);
}
bool SigBreakout(Sig &sg)
{
datetime t1=iTime(_Symbol,g_tf,1);
if(t1==0) return(false);
MqlDateTime d; TimeToStruct(t1,d);
if(d.hour<InpBrkRangeEnd || d.hour>=InpBrkUntil) return(false);
datetime dayStart=DayStart(t1);
if(g_brkDay==dayStart) return(false);
int maxBars=(int)(86400/PeriodSeconds(g_tf))+5;
MqlRates r[];
ArraySetAsSeries(r,true);
int got=CopyRates(_Symbol,g_tf,1,maxBars,r);
if(got<=0) return(false);
double hi=-DBL_MAX, lo=DBL_MAX; int cnt=0;
for(int i=0;i<got;i++)
{
if(r[i].time<dayStart) break;
MqlDateTime x; TimeToStruct(r[i].time,x);
if(x.hour>=InpBrkRangeStart && x.hour<InpBrkRangeEnd)
{
if(r[i].high>hi) hi=r[i].high;
if(r[i].low<lo) lo=r[i].low;
cnt++;
}
}
if(cnt<3) return(false);
double a=Buf(g_hAtr,0,1);
if(Bad(a)||a<=0) return(false);
double c1=r[0].close;
int dir=0;
if(c1>hi) dir=1; else if(c1<lo) dir=-1;
if(dir==0) return(false);
sg.dir=dir; sg.slDist=InpBrkSlAtr*a; sg.tpDist=sg.slDist*InpBrkRR; sg.tag="BRK"; sg.day=dayStart;
return(true);
}
bool SigRsiMr(Sig &sg)
{
double r1=Buf(g_hRsiMr,0,1);
double up=Buf(g_hBb,1,1), lo=Buf(g_hBb,2,1);
double a=Buf(g_hAtr,0,1);
if(Bad(r1)||Bad(up)||Bad(lo)||Bad(a)||a<=0) return(false);
double c1=iClose(_Symbol,g_tf,1);
int dir=0;
if(r1<InpMrRsiLow && c1<lo) dir=1;
else if(r1>InpMrRsiHigh && c1>up) dir=-1;
if(dir==0) return(false);
sg.dir=dir; sg.slDist=InpMrSlAtr*a; sg.tpDist=InpMrTpAtr*a; sg.tag="RSI"; sg.day=0;
return(true);
}
bool SigRegime(Sig &sg)
{
double ef=Buf(g_hRgF,0,1), es=Buf(g_hRgS,0,1);
double r1=Buf(g_hRgRsi,0,1), r0=Buf(g_hRgRsi,0,2);
double adx=Buf(g_hAdx,0,1), a=Buf(g_hAtr,0,1);
if(Bad(ef)||Bad(es)||Bad(r1)||Bad(r0)||Bad(adx)||Bad(a)||a<=0) return(false);
if(adx>InpRgAdxMax) return(false);
bool up=(ef>es);
bool buy=false, sell=false;
if(adx<InpRgAdxThr)
{
buy =( up && r1<InpRgBuyRange && r1>r0);
sell=(!up && r1>InpRgSellRange && r1<r0);
}
else
{
buy =( up && r0<50.0 && r1>=50.0 && r1<InpRgOb);
sell=(!up && r0>50.0 && r1<=50.0 && r1>InpRgOs);
}
if(!buy && !sell) return(false);
sg.dir=buy?1:-1; sg.slDist=InpRgSlAtr*a; sg.tpDist=InpRgTpAtr*a; sg.tag="REG"; sg.day=0;
return(true);
}
bool FindSignal(Sig &sg)
{
if(InpUseEmaAtr && SigEmaAtr(sg)) return(true);
if(InpUseBreakout && SigBreakout(sg)) return(true);
if(InpUseRsiMr && SigRsiMr(sg)) return(true);
if(InpUseRegime && SigRegime(sg)) return(true);
return(false);
}
//+------------------------------------------------------------------+
//| Filters |
//+------------------------------------------------------------------+
bool NewsBlocked()
{
if(!InpUseNews || MQLInfoInteger(MQL_TESTER)) return(false);
static datetime lastCheck=0;
static bool lastRes=false;
datetime now=TimeCurrent();
if(now-lastCheck<60) return(lastRes);
lastCheck=now; lastRes=false;
string cur[2];
cur[0]=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE);
cur[1]=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT);
datetime from=now-InpNewsAfter*60, to=now+InpNewsBefore*60;
for(int k=0;k<2;k++)
{
if(cur[k]=="") continue;
MqlCalendarValue v[];
int n=CalendarValueHistory(v,from,to,NULL,cur[k]);
for(int i=0;i<n;i++)
{
MqlCalendarEvent ev;
if(CalendarEventById(v[i].event_id,ev) && ev.importance==CALENDAR_IMPORTANCE_HIGH)
{
lastRes=true;
return(true);
}
}
}
return(false);
}
bool FiltersOk()
{
if(InpUseSession)
{
MqlDateTime d; TimeToStruct(TimeCurrent(),d);
bool inside=(InpSessStart<=InpSessEnd) ? (d.hour>=InpSessStart && d.hour<InpSessEnd)
: (d.hour>=InpSessStart || d.hour<InpSessEnd);
if(!inside) return(false);
}
if(NewsBlocked()) return(false);
double a=Buf(g_hAtr,0,1);
if(Bad(a)||a<=0) return(false);
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK), bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double spread=ask-bid;
if(InpMaxSpreadAtr>0 && spread>a*InpMaxSpreadAtr/100.0) return(false);
if(InpMaxSpreadPts>0 && spread/_Point>InpMaxSpreadPts) return(false);
if(InpUseVolFilter)
{
double arr[];
ArraySetAsSeries(arr,true);
if(CopyBuffer(g_hAtr,0,1,100,arr)==100)
{
double sum=0; for(int i=0;i<100;i++) sum+=arr[i];
double mean=sum/100.0;
if(mean>0 && arr[0]/mean<InpMinAtrRatio) return(false);
}
}
return(true);
}
bool CanTrade()
{
if(g_paused || IsHalted()) return(false);
string why;
if(!RiskOk(why)) return(false);
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) return(false);
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) return(false);
if(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE)!=SYMBOL_TRADE_MODE_FULL) return(false);
if(Bars(_Symbol,g_tf)<300) return(false);
if(CountPositions(true)>=1) return(false);
if(CountPositions(false)>=InpMaxOpen) return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Position sizing and order sending |
//+------------------------------------------------------------------+
double CalcLots(const double slDist, const ENUM_ORDER_TYPE type, const double price)
{
double riskMoney=AccountInfoDouble(ACCOUNT_EQUITY)*InpRiskPct/100.0;
if(riskMoney<=0) return(0.0);
double slPrice=(type==ORDER_TYPE_BUY) ? price-slDist : price+slDist;
double profit=0.0;
if(!OrderCalcProfit(type,_Symbol,1.0,price,slPrice,profit) || profit>=0.0) return(0.0);
double lossPerLot=-profit;
double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double vmin=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double vmax=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
if(step<=0) return(0.0);
double lots=MathFloor(riskMoney/lossPerLot/step)*step;
if(lots<vmin)
{
if(vmin*lossPerLot>riskMoney*InpMaxRiskMult) return(0.0); // min lot too risky for this account
lots=vmin;
}
if(lots>vmax) lots=vmax;
double vlim=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); // max total volume per symbol (0 = none)
if(vlim>0.0 && lots>vlim) lots=MathFloor(vlim/step)*step;
if(lots<vmin) return(0.0);
int digs=(int)MathMax(0.0,MathCeil(-MathLog10(step)-0.0000001));
lots=NormalizeDouble(lots,digs);
double margin=0.0;
if(!OrderCalcMargin(type,_Symbol,lots,price,margin)) return(0.0);
if(margin>AccountInfoDouble(ACCOUNT_MARGIN_FREE)*0.9) return(0.0);
return(lots);
}
bool OpenTrade(const Sig &sg)
{
MqlTick tk;
if(!SymbolInfoTick(_Symbol,tk)) return(false);
bool buy=(sg.dir>0);
double price=buy ? tk.ask : tk.bid;
double minDist=((double)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)+2.0)*_Point;
double slD=MathMax(sg.slDist,minDist);
double tpD=MathMax(sg.tpDist,minDist);
double sl=NormalizeDouble(buy ? price-slD : price+slD,_Digits);
double tp=NormalizeDouble(buy ? price+tpD : price-tpD,_Digits);
ENUM_ORDER_TYPE ot=buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
double lots=CalcLots(slD,ot,price);
if(lots<=0.0) return(false);
string comment="MPB_"+sg.tag;
bool ok=buy ? trade.Buy(lots,_Symbol,price,sl,tp,comment)
: trade.Sell(lots,_Symbol,price,sl,tp,comment);
uint rc=trade.ResultRetcode();
if(ok && (rc==TRADE_RETCODE_DONE || rc==TRADE_RETCODE_PLACED))
{
Notify(StringFormat("%s %s %.2f lots | %s | SL %s TP %s",buy?"BUY":"SELL",_Symbol,lots,sg.tag,
DoubleToString(sl,_Digits),DoubleToString(tp,_Digits)));
return(true);
}
Print("Order failed: ",rc," ",trade.ResultRetcodeDescription());
return(false);
}
//+------------------------------------------------------------------+
//| Break-even and ATR trailing |
//+------------------------------------------------------------------+
void ManagePositions()
{
if(InpBreakEvenAtr<=0 && InpTrailAtr<=0) return;
double atr=Buf(g_hAtr,0,1);
if(Bad(atr)||atr<=0) return;
double stopLvl=(double)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point;
double frz=(double)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)*_Point;
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
bool buy=(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY);
double open=PositionGetDouble(POSITION_PRICE_OPEN);
double sl =PositionGetDouble(POSITION_SL);
double tp =PositionGetDouble(POSITION_TP);
double price=buy ? SymbolInfoDouble(_Symbol,SYMBOL_BID) : SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double profit=buy ? price-open : open-price;
double cand=0.0; bool have=false;
if(InpBreakEvenAtr>0 && profit>=InpBreakEvenAtr*atr){ cand=open; have=true; }
if(InpTrailAtr>0 && profit>=InpTrailAtr*atr)
{
double t=buy ? price-InpTrailAtr*atr : price+InpTrailAtr*atr;
if(!have) cand=t; else cand=buy ? MathMax(cand,t) : MathMin(cand,t);
have=true;
}
if(!have) continue;
cand=NormalizeDouble(cand,_Digits);
bool better=buy ? (sl==0.0 || cand>sl+_Point*0.5) : (sl==0.0 || cand<sl-_Point*0.5);
if(!better) continue;
if(buy && cand>price-stopLvl) continue;
if(!buy && cand<price+stopLvl) continue;
if(frz>0 && ((sl>0 && MathAbs(price-sl)<=frz) || (tp>0 && MathAbs(price-tp)<=frz))) continue;
trade.PositionModify(tk,cand,tp);
}
}
//+------------------------------------------------------------------+
//| Status panel and Telegram commands |
//+------------------------------------------------------------------+
string StatusText()
{
int n=CountPositions(false);
double pnl=0.0;
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
pnl+=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
}
string state="ACTIVE";
if(IsHalted()) state="HALTED (risk limit)";
else if(g_paused) state="PAUSED";
return(StringFormat("MT5ProBot | %s\nBalance %.2f | Equity %.2f\nDaily loss %.2f%% | Drawdown %.2f%%\nOpen trades %d | Floating P/L %.2f",
state,AccountInfoDouble(ACCOUNT_BALANCE),AccountInfoDouble(ACCOUNT_EQUITY),
DailyLossPct(),DrawdownPct(),n,pnl));
}
void TgPoll()
{
if(InpTgToken=="" || InpTgChat=="" || MQLInfoInteger(MQL_TESTER)) return;
string url="https://api.telegram.org/bot"+InpTgToken+"/getUpdates?timeout=0&offset="+(string)g_tgOffset;
char post[], res[];
string rh;
ResetLastError();
if(WebRequest("GET",url,"",4000,post,res,rh)==-1) return;
string js=CharArrayToString(res,0,WHOLE_ARRAY,CP_UTF8);
string key="\"update_id\":";
int pos=0;
while((pos=StringFind(js,key,pos))>=0)
{
int st=pos+StringLen(key);
int en=StringFind(js,",",st);
if(en<0) break;
long uid=StringToInteger(StringSubstr(js,st,en-st));
int nxt=StringFind(js,key,en);
string seg=(nxt>=0) ? StringSubstr(js,pos,nxt-pos) : StringSubstr(js,pos);
if(uid>=g_tgOffset) g_tgOffset=uid+1;
if(StringFind(seg,"\"chat\":{\"id\":"+InpTgChat+",")>=0)
{
if(StringFind(seg,"\"text\":\"/status")>=0) TgSend(StatusText());
else if(StringFind(seg,"\"text\":\"/pause")>=0) { g_paused=true; TgSend("Paused: no new trades."); }
else if(StringFind(seg,"\"text\":\"/resume")>=0) { g_paused=false; TgSend("Resumed."); }
else if(StringFind(seg,"\"text\":\"/closeall")>=0){ CloseAll(); g_paused=true; TgSend("All positions closed. Bot paused."); }
}
pos=en;
}
}
//+------------------------------------------------------------------+
//| Events |
//+------------------------------------------------------------------+
int OnInit()
{
g_tf=(InpTF==PERIOD_CURRENT) ? (ENUM_TIMEFRAMES)Period() : InpTF;
g_hAtr =iATR(_Symbol,g_tf,InpAtrPeriod);
g_hEmaF =iMA(_Symbol,g_tf,InpEmaFast,0,MODE_EMA,PRICE_CLOSE);
g_hEmaS =iMA(_Symbol,g_tf,InpEmaSlow,0,MODE_EMA,PRICE_CLOSE);
g_hEmaT =iMA(_Symbol,g_tf,InpEmaTrend,0,MODE_EMA,PRICE_CLOSE);
g_hRsiMr=iRSI(_Symbol,g_tf,InpMrRsiPeriod,PRICE_CLOSE);
g_hBb =iBands(_Symbol,g_tf,InpMrBbPeriod,0,InpMrBbDev,PRICE_CLOSE);
g_hRgF =iMA(_Symbol,g_tf,InpRgFast,0,MODE_EMA,PRICE_CLOSE);
g_hRgS =iMA(_Symbol,g_tf,InpRgSlow,0,MODE_EMA,PRICE_CLOSE);
g_hRgRsi=iRSI(_Symbol,g_tf,InpRgRsiPeriod,PRICE_CLOSE);
g_hAdx =iADX(_Symbol,g_tf,14);
if(g_hAtr==INVALID_HANDLE||g_hEmaF==INVALID_HANDLE||g_hEmaS==INVALID_HANDLE||g_hEmaT==INVALID_HANDLE||
g_hRsiMr==INVALID_HANDLE||g_hBb==INVALID_HANDLE||g_hRgF==INVALID_HANDLE||g_hRgS==INVALID_HANDLE||
g_hRgRsi==INVALID_HANDLE||g_hAdx==INVALID_HANDLE)
{
Print("Indicator initialisation failed. Error ",GetLastError());
return(INIT_FAILED);
}
if(InpRiskPct<=0 || InpRiskPct>10)
{
Print("Risk per trade must be between 0 and 10 percent.");
return(INIT_PARAMETERS_INCORRECT);
}
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(InpDeviation);
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetAsyncMode(false);
EventSetTimer(5);
UpdateDay();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
EventKillTimer();
Comment("");
if(g_hAtr !=INVALID_HANDLE) IndicatorRelease(g_hAtr);
if(g_hEmaF !=INVALID_HANDLE) IndicatorRelease(g_hEmaF);
if(g_hEmaS !=INVALID_HANDLE) IndicatorRelease(g_hEmaS);
if(g_hEmaT !=INVALID_HANDLE) IndicatorRelease(g_hEmaT);
if(g_hRsiMr!=INVALID_HANDLE) IndicatorRelease(g_hRsiMr);
if(g_hBb !=INVALID_HANDLE) IndicatorRelease(g_hBb);
if(g_hRgF !=INVALID_HANDLE) IndicatorRelease(g_hRgF);
if(g_hRgS !=INVALID_HANDLE) IndicatorRelease(g_hRgS);
if(g_hRgRsi!=INVALID_HANDLE) IndicatorRelease(g_hRgRsi);
if(g_hAdx !=INVALID_HANDLE) IndicatorRelease(g_hAdx);
}
void OnTimer()
{
TgPoll();
if(!MQLInfoInteger(MQL_TESTER)) Comment(StatusText());
}
void OnTick()
{
UpdateDay();
CheckKillSwitch();
if(CountPositions(true)>0) ManagePositions();
datetime bt=iTime(_Symbol,g_tf,0);
if(bt==0 || bt==g_lastBar) return;
g_lastBar=bt;
if(!CanTrade()) return;
Sig sg;
sg.dir=0; sg.slDist=0; sg.tpDist=0; sg.tag=""; sg.day=0;
if(!FindSignal(sg)) return;
if(!FiltersOk()) return;
if(OpenTrade(sg) && sg.tag=="BRK") g_brkDay=sg.day;
}
//+------------------------------------------------------------------+