708 lines
26 KiB
MQL5
708 lines
26 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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;
| |||
}
| |||
//+------------------------------------------------------------------+
|