создано из Stingdondaleatih/Mql5
733 строки
21 КиБ
MQL5
733 строки
21 КиБ
MQL5
//+------------------------------------------------------------------+
|
|
//| XAUUSD_M5_Professional_Scalper_V3.mq5 |
|
|
//| Professional-style XAUUSD M5 scalper with risk controls |
|
|
//| No Martingale / No Grid / No guaranteed-profit logic |
|
|
//+------------------------------------------------------------------+
|
|
#property strict
|
|
#property version "3.00"
|
|
#property description "XAUUSD M5 professional scalper. Test on DEMO before live use."
|
|
|
|
#include <Trade/Trade.mqh>
|
|
|
|
CTrade trade;
|
|
|
|
//--------------------------- Inputs ---------------------------------
|
|
input group "GENERAL"
|
|
input ulong InpMagic = 26092403;
|
|
input ENUM_TIMEFRAMES InpTF = PERIOD_M5;
|
|
input bool InpOnlyGold = true;
|
|
input bool InpOneTradePerBar = true;
|
|
|
|
input group "RISK"
|
|
input double InpRiskPercent = 1.00; // risk per trade
|
|
input double InpMaxDailyLossPercent = 5.00; // closed P/L + floating loss protection
|
|
input int InpMaxConsecutiveLosses = 3;
|
|
input int InpMaxPositions = 2;
|
|
input double InpMaxTotalRiskPercent = 2.00;
|
|
|
|
input group "ENTRY"
|
|
input int InpFastEMA = 20;
|
|
input int InpSlowEMA = 50;
|
|
input int InpRSIPeriod = 14;
|
|
input double InpBuyRSIMin = 52.0;
|
|
input double InpBuyRSIMax = 72.0;
|
|
input double InpSellRSIMin = 28.0;
|
|
input double InpSellRSIMax = 48.0;
|
|
input int InpADXPeriod = 14;
|
|
input double InpMinADX = 18.0;
|
|
input int InpATRPeriod = 14;
|
|
input double InpSL_ATR = 1.35;
|
|
input double InpMinBodyATR = 0.12;
|
|
input double InpBreakoutATR = 0.05;
|
|
|
|
input group "HIGHER TIMEFRAME FILTER"
|
|
input bool InpUseHTFFilter = true;
|
|
input int InpHTFFastEMA = 50;
|
|
input int InpHTFSlowEMA = 200;
|
|
|
|
input group "TRADE MANAGEMENT"
|
|
input double InpRR = 2.20;
|
|
input double InpBreakEvenR = 0.90;
|
|
input double InpBreakEvenOffsetR = 0.08;
|
|
input double InpTrailStartR = 1.25;
|
|
input double InpTrailATR = 1.00;
|
|
input bool InpPartialClose = true;
|
|
input double InpPartialAtR = 1.00;
|
|
input double InpPartialPercent = 50.0;
|
|
|
|
input group "MARKET FILTERS"
|
|
input int InpMaxSpreadPoints = 70;
|
|
input int InpMaxSlippagePoints = 30;
|
|
input bool InpUseSession = true;
|
|
input int InpSessionStartHour = 7;
|
|
input int InpSessionEndHour = 22;
|
|
input int InpCooldownMinutes = 5;
|
|
input int InpMaxTradesPerDay = 12;
|
|
input bool InpAvoidFridayLate = true;
|
|
input int InpFridayStopHour = 19;
|
|
|
|
input group "DASHBOARD"
|
|
input bool InpShowDashboard = true;
|
|
|
|
//------------------------- Indicator handles ------------------------
|
|
int hFastEMA = INVALID_HANDLE;
|
|
int hSlowEMA = INVALID_HANDLE;
|
|
int hRSI = INVALID_HANDLE;
|
|
int hATR = INVALID_HANDLE;
|
|
int hADX = INVALID_HANDLE;
|
|
int hHTFFast = INVALID_HANDLE;
|
|
int hHTFSlow = INVALID_HANDLE;
|
|
|
|
datetime g_lastBar = 0;
|
|
datetime g_lastEntry = 0;
|
|
int g_dayOfYear = -1;
|
|
int g_year = -1;
|
|
|
|
//------------------------- Utility ----------------------------------
|
|
bool IsGoldSymbol()
|
|
{
|
|
if(!InpOnlyGold) return true;
|
|
string s = _Symbol;
|
|
StringToUpper(s);
|
|
return (StringFind(s,"XAU") >= 0 || StringFind(s,"GOLD") >= 0);
|
|
}
|
|
|
|
bool GetTick(MqlTick &tick)
|
|
{
|
|
return SymbolInfoTick(_Symbol,tick);
|
|
}
|
|
|
|
double PointValue()
|
|
{
|
|
return SymbolInfoDouble(_Symbol,SYMBOL_POINT);
|
|
}
|
|
|
|
int DigitsValue()
|
|
{
|
|
return (int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
|
|
}
|
|
|
|
double NormalizePrice(double p)
|
|
{
|
|
return NormalizeDouble(p,DigitsValue());
|
|
}
|
|
|
|
double NormalizeVolumeDown(double vol)
|
|
{
|
|
double minv = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
|
|
double maxv = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
|
|
double step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
|
|
|
|
if(step <= 0.0) return 0.0;
|
|
if(vol > maxv) vol = maxv;
|
|
|
|
double n = MathFloor(vol / step + 1e-9);
|
|
double out = n * step;
|
|
|
|
if(out < minv) return 0.0;
|
|
return NormalizeDouble(out,2);
|
|
}
|
|
|
|
bool GetBufferValue(int handle,int buffer,int shift,double &value)
|
|
{
|
|
double a[];
|
|
ArraySetAsSeries(a,true);
|
|
if(CopyBuffer(handle,buffer,shift,1,a) != 1)
|
|
return false;
|
|
value = a[0];
|
|
return true;
|
|
}
|
|
|
|
bool GetRates(MqlRates &r1,MqlRates &r2,MqlRates &r3)
|
|
{
|
|
MqlRates rates[];
|
|
ArraySetAsSeries(rates,true);
|
|
if(CopyRates(_Symbol,InpTF,0,4,rates) < 4)
|
|
return false;
|
|
r1=rates[1]; // completed candle
|
|
r2=rates[2];
|
|
r3=rates[3];
|
|
return true;
|
|
}
|
|
|
|
bool IsNewBar()
|
|
{
|
|
datetime t=iTime(_Symbol,InpTF,0);
|
|
if(t==0) return false;
|
|
if(t!=g_lastBar)
|
|
{
|
|
g_lastBar=t;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ResetDayIfNeeded()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(),dt);
|
|
if(dt.day_of_year!=g_dayOfYear || dt.year!=g_year)
|
|
{
|
|
g_dayOfYear=dt.day_of_year;
|
|
g_year=dt.year;
|
|
}
|
|
}
|
|
|
|
bool InSession()
|
|
{
|
|
if(!InpUseSession) return true;
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(),dt);
|
|
int h=dt.hour;
|
|
|
|
if(InpSessionStartHour <= InpSessionEndHour)
|
|
return (h>=InpSessionStartHour && h<InpSessionEndHour);
|
|
|
|
return (h>=InpSessionStartHour || h<InpSessionEndHour);
|
|
}
|
|
|
|
bool FridayLate()
|
|
{
|
|
if(!InpAvoidFridayLate) return false;
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(),dt);
|
|
return (dt.day_of_week==5 && dt.hour>=InpFridayStopHour);
|
|
}
|
|
|
|
double CurrentSpreadPoints()
|
|
{
|
|
MqlTick tick;
|
|
if(!GetTick(tick)) return 999999.0;
|
|
double pt=PointValue();
|
|
if(pt<=0) return 999999.0;
|
|
return (tick.ask-tick.bid)/pt;
|
|
}
|
|
|
|
bool SpreadOK()
|
|
{
|
|
return CurrentSpreadPoints() <= InpMaxSpreadPoints;
|
|
}
|
|
|
|
int CountOpenPositions()
|
|
{
|
|
int count=0;
|
|
for(int i=PositionsTotal()-1;i>=0;i--)
|
|
{
|
|
ulong ticket=PositionGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
double CurrentOpenRiskMoney()
|
|
{
|
|
double total=0.0;
|
|
double tickSize=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
|
|
if(tickSize<=0 || tickValue<=0) return 0.0;
|
|
|
|
for(int i=PositionsTotal()-1;i>=0;i--)
|
|
{
|
|
ulong ticket=PositionGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
|
|
|
|
double open=PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double sl=PositionGetDouble(POSITION_SL);
|
|
double vol=PositionGetDouble(POSITION_VOLUME);
|
|
if(sl<=0 || vol<=0) continue;
|
|
|
|
double dist=MathAbs(open-sl);
|
|
total += (dist/tickSize)*tickValue*vol;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
datetime DayStart()
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(),dt);
|
|
dt.hour=0; dt.min=0; dt.sec=0;
|
|
return StructToTime(dt);
|
|
}
|
|
|
|
double ClosedProfitToday()
|
|
{
|
|
datetime from=DayStart();
|
|
datetime to=TimeCurrent();
|
|
if(!HistorySelect(from,to)) return 0.0;
|
|
|
|
double result=0.0;
|
|
int deals=HistoryDealsTotal();
|
|
for(int i=0;i<deals;i++)
|
|
{
|
|
ulong ticket=HistoryDealGetTicket(i);
|
|
if(ticket==0) continue;
|
|
|
|
string sym=HistoryDealGetString(ticket,DEAL_SYMBOL);
|
|
long magic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
|
|
long entry=HistoryDealGetInteger(ticket,DEAL_ENTRY);
|
|
|
|
if(sym!=_Symbol || (ulong)magic!=InpMagic) continue;
|
|
if(entry!=DEAL_ENTRY_OUT && entry!=DEAL_ENTRY_OUT_BY) continue;
|
|
|
|
result += HistoryDealGetDouble(ticket,DEAL_PROFIT);
|
|
result += HistoryDealGetDouble(ticket,DEAL_SWAP);
|
|
result += HistoryDealGetDouble(ticket,DEAL_COMMISSION);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double FloatingProfit()
|
|
{
|
|
double result=0.0;
|
|
for(int i=PositionsTotal()-1;i>=0;i--)
|
|
{
|
|
ulong ticket=PositionGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
|
|
result += PositionGetDouble(POSITION_PROFIT);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int TradesToday()
|
|
{
|
|
datetime from=DayStart();
|
|
if(!HistorySelect(from,TimeCurrent())) return 0;
|
|
int count=0;
|
|
int deals=HistoryDealsTotal();
|
|
|
|
for(int i=0;i<deals;i++)
|
|
{
|
|
ulong ticket=HistoryDealGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)HistoryDealGetInteger(ticket,DEAL_MAGIC)!=InpMagic) continue;
|
|
if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_IN)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int ConsecutiveLosses()
|
|
{
|
|
datetime from=DayStart();
|
|
if(!HistorySelect(from,TimeCurrent())) return 0;
|
|
|
|
int losses=0;
|
|
int total=HistoryDealsTotal();
|
|
|
|
for(int i=total-1;i>=0;i--)
|
|
{
|
|
ulong ticket=HistoryDealGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)HistoryDealGetInteger(ticket,DEAL_MAGIC)!=InpMagic) continue;
|
|
|
|
long entry=HistoryDealGetInteger(ticket,DEAL_ENTRY);
|
|
if(entry!=DEAL_ENTRY_OUT && entry!=DEAL_ENTRY_OUT_BY) continue;
|
|
|
|
double p=HistoryDealGetDouble(ticket,DEAL_PROFIT)
|
|
+HistoryDealGetDouble(ticket,DEAL_SWAP)
|
|
+HistoryDealGetDouble(ticket,DEAL_COMMISSION);
|
|
|
|
if(p < -0.01) losses++;
|
|
else if(p > 0.01) break;
|
|
}
|
|
return losses;
|
|
}
|
|
|
|
bool DailyLossReached()
|
|
{
|
|
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
if(balance<=0) return true;
|
|
|
|
double closed=ClosedProfitToday();
|
|
double floating=FloatingProfit();
|
|
double lossMoney=-(closed+floating);
|
|
double maxLoss=balance*InpMaxDailyLossPercent/100.0;
|
|
|
|
return (lossMoney >= maxLoss);
|
|
}
|
|
|
|
double CalculateLot(double stopDistancePrice)
|
|
{
|
|
if(stopDistancePrice<=0) return 0.0;
|
|
|
|
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskMoney=balance*InpRiskPercent/100.0;
|
|
|
|
double tickSize=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
|
|
if(tickValue<=0)
|
|
tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
|
|
|
|
if(tickSize<=0 || tickValue<=0) return 0.0;
|
|
|
|
double lossPerLot=(stopDistancePrice/tickSize)*tickValue;
|
|
if(lossPerLot<=0) return 0.0;
|
|
|
|
double lots=riskMoney/lossPerLot;
|
|
return NormalizeVolumeDown(lots);
|
|
}
|
|
|
|
bool CooldownOK()
|
|
{
|
|
if(g_lastEntry==0) return true;
|
|
return (TimeCurrent()-g_lastEntry >= InpCooldownMinutes*60);
|
|
}
|
|
|
|
bool HTFTrendOK(bool buy)
|
|
{
|
|
if(!InpUseHTFFilter) return true;
|
|
|
|
double fast,slow;
|
|
if(!GetBufferValue(hHTFFast,0,1,fast)) return false;
|
|
if(!GetBufferValue(hHTFSlow,0,1,slow)) return false;
|
|
|
|
if(buy) return fast>slow;
|
|
return fast<slow;
|
|
}
|
|
|
|
bool EntrySignal(bool buy,double &atrOut)
|
|
{
|
|
MqlRates r1,r2,r3;
|
|
if(!GetRates(r1,r2,r3)) return false;
|
|
|
|
double emaFast,emaSlow,rsi,atr,adx;
|
|
if(!GetBufferValue(hFastEMA,0,1,emaFast)) return false;
|
|
if(!GetBufferValue(hSlowEMA,0,1,emaSlow)) return false;
|
|
if(!GetBufferValue(hRSI,0,1,rsi)) return false;
|
|
if(!GetBufferValue(hATR,0,1,atr)) return false;
|
|
if(!GetBufferValue(hADX,0,1,adx)) return false;
|
|
|
|
atrOut=atr;
|
|
if(atr<=0 || adx<InpMinADX) return false;
|
|
|
|
double body=MathAbs(r1.close-r1.open);
|
|
if(body < atr*InpMinBodyATR) return false;
|
|
|
|
bool bullish=r1.close>r1.open;
|
|
bool bearish=r1.close<r1.open;
|
|
|
|
if(buy)
|
|
{
|
|
if(!(emaFast>emaSlow)) return false;
|
|
if(!HTFTrendOK(true)) return false;
|
|
if(!bullish) return false;
|
|
if(rsi<InpBuyRSIMin || rsi>InpBuyRSIMax) return false;
|
|
if(r1.close <= r2.high + atr*InpBreakoutATR) return false;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if(!(emaFast<emaSlow)) return false;
|
|
if(!HTFTrendOK(false)) return false;
|
|
if(!bearish) return false;
|
|
if(rsi<InpSellRSIMin || rsi>InpSellRSIMax) return false;
|
|
if(r1.close >= r2.low - atr*InpBreakoutATR) return false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool ModifyPosition(ulong ticket,double sl,double tp)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
string sym=PositionGetString(POSITION_SYMBOL);
|
|
if(sym!=_Symbol) return false;
|
|
return trade.PositionModify(ticket,NormalizePrice(sl),NormalizePrice(tp));
|
|
}
|
|
|
|
bool PartialDone(ulong ticket)
|
|
{
|
|
string key="V3P_"+(string)ticket;
|
|
return GlobalVariableCheck(key);
|
|
}
|
|
|
|
void MarkPartialDone(ulong ticket)
|
|
{
|
|
string key="V3P_"+(string)ticket;
|
|
GlobalVariableSet(key,(double)TimeCurrent());
|
|
}
|
|
|
|
void ManagePositions()
|
|
{
|
|
double atr;
|
|
if(!GetBufferValue(hATR,0,1,atr)) return;
|
|
|
|
MqlTick tick;
|
|
if(!GetTick(tick)) return;
|
|
|
|
double pt=PointValue();
|
|
int stopsLevel=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
|
|
double minStop=stopsLevel*pt;
|
|
|
|
for(int i=PositionsTotal()-1;i>=0;i--)
|
|
{
|
|
ulong ticket=PositionGetTicket(i);
|
|
if(ticket==0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
|
|
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
|
|
|
|
long type=PositionGetInteger(POSITION_TYPE);
|
|
double open=PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double sl=PositionGetDouble(POSITION_SL);
|
|
double tp=PositionGetDouble(POSITION_TP);
|
|
double volume=PositionGetDouble(POSITION_VOLUME);
|
|
|
|
if(sl<=0) continue;
|
|
|
|
double current=(type==POSITION_TYPE_BUY ? tick.bid : tick.ask);
|
|
double initialRisk=MathAbs(open-sl);
|
|
|
|
// If SL has already moved beyond the original area, use a conservative
|
|
// proxy based on ATR for management.
|
|
if(initialRisk < pt*2) initialRisk=atr*InpSL_ATR;
|
|
|
|
double profitDist=(type==POSITION_TYPE_BUY ? current-open : open-current);
|
|
double R=profitDist/initialRisk;
|
|
|
|
// Partial close
|
|
if(InpPartialClose && !PartialDone(ticket) && R>=InpPartialAtR)
|
|
{
|
|
double closeVol=NormalizeVolumeDown(volume*InpPartialPercent/100.0);
|
|
double minv=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
|
|
|
|
if(closeVol>=minv && closeVol<volume)
|
|
{
|
|
if(trade.PositionClosePartial(ticket,closeVol))
|
|
MarkPartialDone(ticket);
|
|
}
|
|
else
|
|
{
|
|
MarkPartialDone(ticket);
|
|
}
|
|
}
|
|
|
|
// Break-even
|
|
if(R>=InpBreakEvenR)
|
|
{
|
|
double beOffset=initialRisk*InpBreakEvenOffsetR;
|
|
double newSL=(type==POSITION_TYPE_BUY ? open+beOffset : open-beOffset);
|
|
|
|
bool better=(type==POSITION_TYPE_BUY ? (sl<newSL) : (sl>newSL));
|
|
bool valid=(type==POSITION_TYPE_BUY ? (newSL<current-minStop) : (newSL>current+minStop));
|
|
|
|
if(better && valid)
|
|
ModifyPosition(ticket,newSL,tp);
|
|
}
|
|
|
|
// ATR trailing
|
|
if(R>=InpTrailStartR)
|
|
{
|
|
double trail=atr*InpTrailATR;
|
|
double newSL=(type==POSITION_TYPE_BUY ? current-trail : current+trail);
|
|
|
|
bool better=(type==POSITION_TYPE_BUY ? (newSL>sl) : (newSL<sl));
|
|
bool valid=(type==POSITION_TYPE_BUY ? (newSL<current-minStop) : (newSL>current+minStop));
|
|
|
|
if(better && valid)
|
|
ModifyPosition(ticket,newSL,tp);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CanTradeNow()
|
|
{
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;
|
|
if(!IsGoldSymbol()) return false;
|
|
if(!InSession()) return false;
|
|
if(FridayLate()) return false;
|
|
if(!SpreadOK()) return false;
|
|
if(DailyLossReached()) return false;
|
|
if(ConsecutiveLosses()>=InpMaxConsecutiveLosses) return false;
|
|
if(TradesToday()>=InpMaxTradesPerDay) return false;
|
|
if(CountOpenPositions()>=InpMaxPositions) return false;
|
|
if(!CooldownOK()) return false;
|
|
|
|
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
if(balance<=0) return false;
|
|
|
|
double maxRiskMoney=balance*InpMaxTotalRiskPercent/100.0;
|
|
if(CurrentOpenRiskMoney()>=maxRiskMoney) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void OpenTrade(bool buy,double atr)
|
|
{
|
|
MqlTick tick;
|
|
if(!GetTick(tick)) return;
|
|
|
|
double entry=(buy ? tick.ask : tick.bid);
|
|
double slDist=atr*InpSL_ATR;
|
|
double tpDist=slDist*InpRR;
|
|
|
|
int stopsLevel=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
|
|
double minStop=stopsLevel*PointValue();
|
|
if(slDist<minStop*1.2) slDist=minStop*1.2;
|
|
|
|
double lots=CalculateLot(slDist);
|
|
if(lots<=0) return;
|
|
|
|
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double currentRisk=CurrentOpenRiskMoney();
|
|
double newRisk=0.0;
|
|
|
|
double tickSize=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
|
|
if(tickValue<=0) tickValue=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
|
|
if(tickSize>0 && tickValue>0)
|
|
newRisk=(slDist/tickSize)*tickValue*lots;
|
|
|
|
double maxRiskMoney=balance*InpMaxTotalRiskPercent/100.0;
|
|
if(currentRisk+newRisk>maxRiskMoney+0.01) return;
|
|
|
|
double sl=(buy ? entry-slDist : entry+slDist);
|
|
double tp=(buy ? entry+tpDist : entry-tpDist);
|
|
|
|
sl=NormalizePrice(sl);
|
|
tp=NormalizePrice(tp);
|
|
|
|
trade.SetExpertMagicNumber(InpMagic);
|
|
trade.SetDeviationInPoints(InpMaxSlippagePoints);
|
|
trade.SetTypeFillingBySymbol(_Symbol);
|
|
|
|
bool ok=false;
|
|
if(buy)
|
|
ok=trade.Buy(lots,_Symbol,0.0,sl,tp,"V3_SCALP_BUY");
|
|
else
|
|
ok=trade.Sell(lots,_Symbol,0.0,sl,tp,"V3_SCALP_SELL");
|
|
|
|
if(ok)
|
|
g_lastEntry=TimeCurrent();
|
|
}
|
|
|
|
string StatusText()
|
|
{
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return "TERMINAL TRADE OFF";
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return "EA TRADE OFF";
|
|
if(!IsGoldSymbol()) return "WRONG SYMBOL";
|
|
if(DailyLossReached()) return "DAILY STOP";
|
|
if(ConsecutiveLosses()>=InpMaxConsecutiveLosses) return "LOSS STOP";
|
|
if(!InSession()) return "OUT OF SESSION";
|
|
if(FridayLate()) return "FRIDAY STOP";
|
|
if(!SpreadOK()) return "HIGH SPREAD";
|
|
if(TradesToday()>=InpMaxTradesPerDay) return "DAILY TRADE LIMIT";
|
|
if(CountOpenPositions()>=InpMaxPositions) return "MAX POSITIONS";
|
|
return "READY";
|
|
}
|
|
|
|
void UpdateDashboard()
|
|
{
|
|
if(!InpShowDashboard) return;
|
|
|
|
double balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double equity=AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double closed=ClosedProfitToday();
|
|
double floating=FloatingProfit();
|
|
double daily=closed+floating;
|
|
double dd=(balance>0 ? MathMax(0.0,(balance-equity)/balance*100.0) : 0.0);
|
|
|
|
string text=
|
|
"XAUUSD M5 SCALPER V3\n"+
|
|
"Status: "+StatusText()+"\n"+
|
|
"Balance: $"+DoubleToString(balance,2)+"\n"+
|
|
"Equity: $"+DoubleToString(equity,2)+"\n"+
|
|
"Daily P/L: $"+DoubleToString(daily,2)+"\n"+
|
|
"Drawdown: "+DoubleToString(dd,2)+"%\n"+
|
|
"Trades today: "+IntegerToString(TradesToday())+"\n"+
|
|
"Open positions: "+IntegerToString(CountOpenPositions())+"\n"+
|
|
"Loss streak: "+IntegerToString(ConsecutiveLosses())+"\n"+
|
|
"Spread: "+DoubleToString(CurrentSpreadPoints(),1)+" pts";
|
|
|
|
Comment(text);
|
|
}
|
|
|
|
//------------------------- Lifecycle --------------------------------
|
|
int OnInit()
|
|
{
|
|
if(!IsGoldSymbol())
|
|
Print("Warning: EA is designed for XAUUSD/GOLD symbols.");
|
|
|
|
hFastEMA=iMA(_Symbol,InpTF,InpFastEMA,0,MODE_EMA,PRICE_CLOSE);
|
|
hSlowEMA=iMA(_Symbol,InpTF,InpSlowEMA,0,MODE_EMA,PRICE_CLOSE);
|
|
hRSI=iRSI(_Symbol,InpTF,InpRSIPeriod,PRICE_CLOSE);
|
|
hATR=iATR(_Symbol,InpTF,InpATRPeriod);
|
|
hADX=iADX(_Symbol,InpTF,InpADXPeriod);
|
|
|
|
if(InpUseHTFFilter)
|
|
{
|
|
hHTFFast=iMA(_Symbol,PERIOD_M15,InpHTFFastEMA,0,MODE_EMA,PRICE_CLOSE);
|
|
hHTFSlow=iMA(_Symbol,PERIOD_M15,InpHTFSlowEMA,0,MODE_EMA,PRICE_CLOSE);
|
|
}
|
|
|
|
if(hFastEMA==INVALID_HANDLE || hSlowEMA==INVALID_HANDLE ||
|
|
hRSI==INVALID_HANDLE || hATR==INVALID_HANDLE || hADX==INVALID_HANDLE)
|
|
{
|
|
Print("Failed to create indicator handles.");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
if(InpUseHTFFilter && (hHTFFast==INVALID_HANDLE || hHTFSlow==INVALID_HANDLE))
|
|
{
|
|
Print("Failed to create HTF indicator handles.");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
ResetDayIfNeeded();
|
|
trade.SetExpertMagicNumber(InpMagic);
|
|
trade.SetDeviationInPoints(InpMaxSlippagePoints);
|
|
trade.SetTypeFillingBySymbol(_Symbol);
|
|
|
|
Print("XAUUSD M5 Professional Scalper V3 initialized.");
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(hFastEMA!=INVALID_HANDLE) IndicatorRelease(hFastEMA);
|
|
if(hSlowEMA!=INVALID_HANDLE) IndicatorRelease(hSlowEMA);
|
|
if(hRSI!=INVALID_HANDLE) IndicatorRelease(hRSI);
|
|
if(hATR!=INVALID_HANDLE) IndicatorRelease(hATR);
|
|
if(hADX!=INVALID_HANDLE) IndicatorRelease(hADX);
|
|
if(hHTFFast!=INVALID_HANDLE) IndicatorRelease(hHTFFast);
|
|
if(hHTFSlow!=INVALID_HANDLE) IndicatorRelease(hHTFSlow);
|
|
Comment("");
|
|
}
|
|
|
|
void OnTick()
|
|
{
|
|
ResetDayIfNeeded();
|
|
ManagePositions();
|
|
UpdateDashboard();
|
|
|
|
if(!IsNewBar()) return;
|
|
if(!CanTradeNow()) return;
|
|
|
|
double atr=0.0;
|
|
if(EntrySignal(true,atr))
|
|
{
|
|
OpenTrade(true,atr);
|
|
return;
|
|
}
|
|
|
|
if(EntrySignal(false,atr))
|
|
{
|
|
OpenTrade(false,atr);
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|