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;
|
|
}
|
|
//+------------------------------------------------------------------+
|