forked from animatedread/Warrior_EA
139 lines
6 KiB
MQL5
139 lines
6 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| SignalRiskGuard.mqh |
|
||
|
|
//| AnimateDread |
|
||
|
|
//| https://www.mql5.com |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "AnimateDread"
|
||
|
|
#property link "https://www.mql5.com"
|
||
|
|
#include "..\Expert\ExpertSignalCustom.mqh"
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Class CSignalRiskGuard. |
|
||
|
|
//| Account-level risk circuit breaker: blocks NEW entries (same |
|
||
|
|
//| EMPTY_VALUE-veto convention every other filter here uses, via the |
|
||
|
|
//| composite Direction()) once either the current broker day's |
|
||
|
|
//| realized+floating loss, or the current drawdown from the account's|
|
||
|
|
//| all-time peak equity, exceeds its configured threshold. Deliberately|
|
||
|
|
//| scoped to blocking new entries only - it never closes existing |
|
||
|
|
//| positions, which is a materially bigger behavior change than a |
|
||
|
|
//| circuit breaker and is left to the trader/EA's own SL/TP handling. |
|
||
|
|
//| Peak equity and the current broker day's starting balance persist |
|
||
|
|
//| to a small local file (StateFileName(), keyed by symbol+magic so |
|
||
|
|
//| multiple instances never collide) - peak equity in particular must|
|
||
|
|
//| survive an EA/terminal restart to mean anything; a restart that |
|
||
|
|
//| reset it to current equity would never detect a real drawdown |
|
||
|
|
//| afterward. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
class CSignalRiskGuard : public CExpertSignalCustom
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
double m_maxDailyLossPct;
|
||
|
|
double m_maxDrawdownPct;
|
||
|
|
double m_dayStartBalance;
|
||
|
|
datetime m_dayStartDate; // truncated to midnight - the broker day m_dayStartBalance is for
|
||
|
|
double m_peakEquity;
|
||
|
|
bool m_stateLoaded;
|
||
|
|
string StateFileName(void) const;
|
||
|
|
void LoadState(void);
|
||
|
|
void SaveState(void);
|
||
|
|
public:
|
||
|
|
CSignalRiskGuard(void);
|
||
|
|
void SetMaxDailyLossPct(int value) { m_maxDailyLossPct = value; }
|
||
|
|
void SetMaxDrawdownPct(int value) { m_maxDrawdownPct = value; }
|
||
|
|
virtual double Direction(void);
|
||
|
|
};
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
CSignalRiskGuard::CSignalRiskGuard(void) : m_maxDailyLossPct(0),
|
||
|
|
m_maxDrawdownPct(0),
|
||
|
|
m_dayStartBalance(0),
|
||
|
|
m_dayStartDate(0),
|
||
|
|
m_peakEquity(0),
|
||
|
|
m_stateLoaded(false)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Local (non-shared) file, keyed by symbol+magic so multiple chart |
|
||
|
|
//| instances of this EA never read/write each other's risk state. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
string CSignalRiskGuard::StateFileName(void) const
|
||
|
|
{
|
||
|
|
return m_symbol.Name() + "_" + IntegerToString(Expert_MagicNumber) + "_riskguard.dat";
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| No prior state file just means a first-ever run - the all-zero |
|
||
|
|
//| defaults are safe: Direction() below seeds real values from the |
|
||
|
|
//| current account state on its very first call either way. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CSignalRiskGuard::LoadState(void)
|
||
|
|
{
|
||
|
|
int handle = FileOpen(StateFileName(), FILE_BIN | FILE_READ);
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
return;
|
||
|
|
m_dayStartBalance = FileReadDouble(handle);
|
||
|
|
m_dayStartDate = (datetime)FileReadLong(handle);
|
||
|
|
m_peakEquity = FileReadDouble(handle);
|
||
|
|
FileClose(handle);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CSignalRiskGuard::SaveState(void)
|
||
|
|
{
|
||
|
|
int handle = FileOpen(StateFileName(), FILE_BIN | FILE_WRITE);
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
return;
|
||
|
|
FileWriteDouble(handle, m_dayStartBalance);
|
||
|
|
FileWriteLong(handle, (long)m_dayStartDate);
|
||
|
|
FileWriteDouble(handle, m_peakEquity);
|
||
|
|
FileClose(handle);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CSignalRiskGuard::Direction(void)
|
||
|
|
{
|
||
|
|
if(m_maxDailyLossPct <= 0.0 && m_maxDrawdownPct <= 0.0)
|
||
|
|
return 0.0; // both disabled - permanent no-op, same convention as DOM_SPREADMULT_OFF
|
||
|
|
if(!m_stateLoaded)
|
||
|
|
{
|
||
|
|
LoadState();
|
||
|
|
m_stateLoaded = true;
|
||
|
|
}
|
||
|
|
MqlDateTime now;
|
||
|
|
TimeToStruct(TimeCurrent(), now);
|
||
|
|
now.hour = 0;
|
||
|
|
now.min = 0;
|
||
|
|
now.sec = 0;
|
||
|
|
datetime todayMidnight = StructToTime(now);
|
||
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
bool stateChanged = false;
|
||
|
|
if(todayMidnight != m_dayStartDate)
|
||
|
|
{
|
||
|
|
m_dayStartDate = todayMidnight;
|
||
|
|
m_dayStartBalance = balance;
|
||
|
|
stateChanged = true;
|
||
|
|
}
|
||
|
|
if(equity > m_peakEquity)
|
||
|
|
{
|
||
|
|
m_peakEquity = equity;
|
||
|
|
stateChanged = true;
|
||
|
|
}
|
||
|
|
if(stateChanged)
|
||
|
|
SaveState();
|
||
|
|
if(m_maxDailyLossPct > 0.0 && m_dayStartBalance > 0.0)
|
||
|
|
{
|
||
|
|
double dailyLossPct = (m_dayStartBalance - equity) / m_dayStartBalance * 100.0;
|
||
|
|
if(dailyLossPct >= m_maxDailyLossPct)
|
||
|
|
return EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
if(m_maxDrawdownPct > 0.0 && m_peakEquity > 0.0)
|
||
|
|
{
|
||
|
|
double drawdownPct = (m_peakEquity - equity) / m_peakEquity * 100.0;
|
||
|
|
if(drawdownPct >= m_maxDrawdownPct)
|
||
|
|
return EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|