Initialize project in MT5 Experts directory
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Risk/PortfolioManager.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef __PORTFOLIO_MANAGER_MQH__
|
||||
#define __PORTFOLIO_MANAGER_MQH__
|
||||
|
||||
#include "../Core/Config.mqh"
|
||||
#include "../Core/State.mqh"
|
||||
#include "../Core/Logger.mqh"
|
||||
|
||||
extern CLogger g_logger;
|
||||
|
||||
class CPortfolioManager
|
||||
{
|
||||
private:
|
||||
int m_corrLookback;
|
||||
ENUM_TIMEFRAMES m_mtf;
|
||||
double m_maxTotalRiskPercent;
|
||||
|
||||
public:
|
||||
bool Init(int lookback, ENUM_TIMEFRAMES mtf)
|
||||
{
|
||||
m_corrLookback = lookback; m_mtf = mtf; m_maxTotalRiskPercent = InpMaxTotalRisk;
|
||||
Print("[PortfolioManager] Correlation lookback: ", lookback, " bars");
|
||||
return true;
|
||||
}
|
||||
void UpdateState(EAState &state)
|
||||
{
|
||||
state.openPositions = 0; double totalRiskAmount = 0;
|
||||
int posTotal = PositionsTotal();
|
||||
for(int i = posTotal - 1; i >= 0; i--)
|
||||
{
|
||||
string sym = PositionGetSymbol(i);
|
||||
if(sym != _Symbol) continue;
|
||||
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
||||
state.openPositions++;
|
||||
double lots = PositionGetDouble(POSITION_VOLUME);
|
||||
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double sl = PositionGetDouble(POSITION_SL);
|
||||
double slDist = MathAbs(entry - sl);
|
||||
double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
|
||||
double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
||||
if(tickSize > 0) { double ticks = slDist / tickSize; totalRiskAmount += lots * ticks * tickValue; }
|
||||
}
|
||||
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
if(equity > 0) state.totalOpenRisk = (totalRiskAmount / equity) * 100.0;
|
||||
else state.totalOpenRisk = 0;
|
||||
}
|
||||
bool IsCorrelated(const SignalData &signal, const EAState &state)
|
||||
{
|
||||
if(!InpUseCorrelationFilter) return false;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string posSymbol = PositionGetSymbol(i);
|
||||
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
||||
if(posSymbol == _Symbol) continue;
|
||||
double corr = CalculateCorrelation(_Symbol, posSymbol);
|
||||
if(MathAbs(corr) > 0.7)
|
||||
{
|
||||
g_logger.LogEvent("PORTFOLIO", StringFormat("REJECTED: Correlation %.2f with %s", corr, posSymbol));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CheckExposure(const TradeParams ¶ms, const EAState &state)
|
||||
{
|
||||
double projectedRisk = state.totalOpenRisk + params.riskPercent;
|
||||
if(projectedRisk > m_maxTotalRiskPercent)
|
||||
{
|
||||
g_logger.LogEvent("PORTFOLIO", StringFormat("REJECTED: Risk %.2f%% > max %.2f%%", projectedRisk, m_maxTotalRiskPercent));
|
||||
return false;
|
||||
}
|
||||
int forexCount = 0, metalCount = 0, indexCount = 0, cryptoCount = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
||||
string sym = PositionGetSymbol(i);
|
||||
if(StringFind(sym, "XAU") >= 0 || StringFind(sym, "XAG") >= 0) metalCount++;
|
||||
else if(StringFind(sym, "BTC") >= 0 || StringFind(sym, "ETH") >= 0) cryptoCount++;
|
||||
else if(StringFind(sym, "US30") >= 0 || StringFind(sym, "NAS") >= 0 || StringFind(sym, "GER") >= 0) indexCount++;
|
||||
else forexCount++;
|
||||
}
|
||||
ENUM_ASSET_CLASS cls = state.assetProfile.assetClass;
|
||||
if((cls == ASSET_FOREX_MAJOR || cls == ASSET_FOREX_CROSS) && forexCount >= 2) { g_logger.LogEvent("PORTFOLIO", "REJECTED: Max 2 Forex"); return false; }
|
||||
if(cls == ASSET_METAL && metalCount >= 1) { g_logger.LogEvent("PORTFOLIO", "REJECTED: Max 1 Metal"); return false; }
|
||||
if(cls == ASSET_INDEX && indexCount >= 1) { g_logger.LogEvent("PORTFOLIO", "REJECTED: Max 1 Index"); return false; }
|
||||
if(cls == ASSET_CRYPTO && cryptoCount >= 1) { g_logger.LogEvent("PORTFOLIO", "REJECTED: Max 1 Crypto"); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
double CalculateCorrelation(string sym1, string sym2)
|
||||
{
|
||||
double c1[], c2[]; ArraySetAsSeries(c1, true); ArraySetAsSeries(c2, true);
|
||||
if(CopyClose(sym1, m_mtf, 1, m_corrLookback, c1) < m_corrLookback) return 0;
|
||||
if(CopyClose(sym2, m_mtf, 1, m_corrLookback, c2) < m_corrLookback) return 0;
|
||||
double mean1 = 0, mean2 = 0;
|
||||
for(int i = 0; i < m_corrLookback; i++) { mean1 += c1[i]; mean2 += c2[i]; }
|
||||
mean1 /= m_corrLookback; mean2 /= m_corrLookback;
|
||||
double cov = 0, var1 = 0, var2 = 0;
|
||||
for(int i = 0; i < m_corrLookback; i++)
|
||||
{ double d1 = c1[i] - mean1; double d2 = c2[i] - mean2; cov += d1 * d2; var1 += d1 * d1; var2 += d2 * d2; }
|
||||
double std1 = MathSqrt(var1); double std2 = MathSqrt(var2);
|
||||
if(std1 * std2 == 0) return 0;
|
||||
return cov / (std1 * std2);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __PORTFOLIO_MANAGER_MQH__
|
||||
@@ -0,0 +1,69 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Risk/PositionSizer.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef __POSITION_SIZER_MQH__
|
||||
#define __POSITION_SIZER_MQH__
|
||||
|
||||
#include "../Core/Config.mqh"
|
||||
#include "../Core/State.mqh"
|
||||
|
||||
class CPositionSizer
|
||||
{
|
||||
private:
|
||||
AssetProfile m_profile;
|
||||
double m_maxRiskPercent;
|
||||
|
||||
public:
|
||||
bool Init(const AssetProfile &profile, double maxRisk)
|
||||
{
|
||||
m_profile = profile; m_maxRiskPercent = maxRisk;
|
||||
Print("[PositionSizer] Max risk per trade: ", maxRisk, "%");
|
||||
return true;
|
||||
}
|
||||
void Calculate(TradeParams ¶ms, const SignalData &signal, const EAState &state)
|
||||
{
|
||||
params.isValid = false; params.rejectReason = "";
|
||||
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
if(equity <= 0) { params.rejectReason = "Invalid account equity"; return; }
|
||||
double riskAmount = equity * (m_maxRiskPercent / 100.0);
|
||||
double slDistance = MathAbs(signal.entryPrice - signal.slPrice);
|
||||
if(slDistance <= 0) { params.rejectReason = "Invalid SL distance"; return; }
|
||||
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||
if(tickValue <= 0 || tickSize <= 0) { params.rejectReason = "Invalid tick value/size"; return; }
|
||||
double slTicks = slDistance / tickSize;
|
||||
double lotSize = riskAmount / (slTicks * tickValue);
|
||||
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||||
if(lotStep > 0) lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
||||
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
|
||||
|
||||
double marginRequired = 0;
|
||||
double price = signal.entryPrice;
|
||||
bool marginCalc = OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lotSize, price, marginRequired);
|
||||
|
||||
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
||||
if(marginCalc && marginRequired > 0 && freeMargin < marginRequired * 1.2)
|
||||
{
|
||||
double maxLotByMargin = (freeMargin / 1.2) / (marginRequired / lotSize);
|
||||
if(lotStep > 0) lotSize = MathFloor(maxLotByMargin / lotStep) * lotStep;
|
||||
lotSize = MathMax(minLot, lotSize);
|
||||
if(lotSize <= minLot) { params.rejectReason = "Insufficient margin"; return; }
|
||||
marginCalc = OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lotSize, price, marginRequired);
|
||||
}
|
||||
|
||||
double finalSlTicks = slDistance / tickSize;
|
||||
double finalRisk = lotSize * finalSlTicks * tickValue;
|
||||
double finalRiskPercent = (finalRisk / equity) * 100.0;
|
||||
if(finalRiskPercent > m_maxRiskPercent * 1.1)
|
||||
{ params.rejectReason = "Risk exceeds max"; return; }
|
||||
params.lotSize = lotSize; params.riskAmount = finalRisk;
|
||||
params.riskPercent = finalRiskPercent; params.slDistance = slDistance;
|
||||
params.tp1Distance = MathAbs(signal.tp1Price - signal.entryPrice);
|
||||
params.tp2Distance = MathAbs(signal.tp2Price - signal.entryPrice);
|
||||
params.marginRequired = marginRequired; params.isValid = true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __POSITION_SIZER_MQH__
|
||||
@@ -0,0 +1,170 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Risk/Protection.mqh |
|
||||
//| Circuit Breakers: Daily/Weekly Loss, Consecutive Loss, Spread |
|
||||
//| MODIFIED: Completed UpdateState with live statistics tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef __PROTECTION_MQH__
|
||||
#define __PROTECTION_MQH__
|
||||
|
||||
#include "../Core/Config.mqh"
|
||||
#include "../Core/State.mqh"
|
||||
#include "../Core/Logger.mqh"
|
||||
|
||||
extern CLogger g_logger;
|
||||
|
||||
class CProtection
|
||||
{
|
||||
private:
|
||||
double m_maxDailyLoss;
|
||||
double m_maxWeeklyLoss;
|
||||
int m_maxConsecLosses;
|
||||
int m_maxPositions;
|
||||
double m_maxTotalRisk;
|
||||
datetime m_lastDailyReset;
|
||||
datetime m_lastWeeklyReset;
|
||||
double m_lastEquity;
|
||||
int m_consecLossCounter;
|
||||
datetime m_lastTradeTime;
|
||||
|
||||
public:
|
||||
bool Init(double dailyLoss, double weeklyLoss, int consecLoss, int maxPos, double maxRisk)
|
||||
{
|
||||
m_maxDailyLoss = dailyLoss;
|
||||
m_maxWeeklyLoss = weeklyLoss;
|
||||
m_maxConsecLosses = consecLoss;
|
||||
m_maxPositions = maxPos;
|
||||
m_maxTotalRisk = maxRisk;
|
||||
m_lastDailyReset = 0;
|
||||
m_lastWeeklyReset = 0;
|
||||
m_lastEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
m_consecLossCounter = 0;
|
||||
m_lastTradeTime = 0;
|
||||
Print("[Protection] Circuit breakers active. Daily:", dailyLoss, "% Weekly:", weeklyLoss, "% Consec:", consecLoss);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsCircuitBreakerActive(EAState &state) const
|
||||
{
|
||||
if(TimeCurrent() < state.circuitBreakerUntil)
|
||||
return true;
|
||||
if(state.circuitBreakerUntil > 0 && TimeCurrent() >= state.circuitBreakerUntil)
|
||||
{
|
||||
g_logger.LogEvent("PROTECTION", "Circuit breaker expired. Trading resumed.");
|
||||
state.circuitBreakerUntil = 0;
|
||||
state.circuitBreakerReason = "";
|
||||
state.dailyLimitHit = false;
|
||||
state.weeklyLimitHit = false;
|
||||
state.consecLossHalted = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreTradeCheck(EAState &state) const
|
||||
{
|
||||
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
if(equity <= 0) return false;
|
||||
double dailyLimit = equity * (m_maxDailyLoss / 100.0);
|
||||
if(state.dailyPnL <= -dailyLimit)
|
||||
{
|
||||
ActivateBreaker(state, "Daily Loss Limit", 86400);
|
||||
state.dailyLimitHit = true;
|
||||
return false;
|
||||
}
|
||||
double weeklyLimit = equity * (m_maxWeeklyLoss / 100.0);
|
||||
if(state.weeklyPnL <= -weeklyLimit)
|
||||
{
|
||||
ActivateBreaker(state, "Weekly Loss Limit", 7 * 86400);
|
||||
state.weeklyLimitHit = true;
|
||||
return false;
|
||||
}
|
||||
if(state.consecutiveLosses >= m_maxConsecLosses)
|
||||
{
|
||||
ActivateBreaker(state, "Consecutive Losses", 86400);
|
||||
state.consecLossHalted = true;
|
||||
return false;
|
||||
}
|
||||
if(state.openPositions >= m_maxPositions)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsSpreadAcceptable(const AssetProfile &profile) const
|
||||
{
|
||||
if(!InpUseSpreadFilter) return true;
|
||||
long spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||||
double spreadPrice = spreadPoints * _Point;
|
||||
return (spreadPrice <= profile.maxSpreadPoints);
|
||||
}
|
||||
|
||||
void UpdateState(EAState &state)
|
||||
{
|
||||
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
if(m_lastEquity > 0 && currentEquity != m_lastEquity)
|
||||
{
|
||||
double equityChange = currentEquity - m_lastEquity;
|
||||
state.dailyPnL += equityChange;
|
||||
state.weeklyPnL += equityChange;
|
||||
if(equityChange < 0)
|
||||
{
|
||||
if(TimeCurrent() != m_lastTradeTime)
|
||||
{
|
||||
m_consecLossCounter++;
|
||||
state.consecutiveLosses = m_consecLossCounter;
|
||||
m_lastTradeTime = TimeCurrent();
|
||||
g_logger.LogEvent("PROTECTION", StringFormat("Loss detected. Consecutive: %d/%d", m_consecLossCounter, m_maxConsecLosses));
|
||||
}
|
||||
}
|
||||
else if(equityChange > 0)
|
||||
{
|
||||
if(m_consecLossCounter > 0)
|
||||
{
|
||||
m_consecLossCounter = 0;
|
||||
state.consecutiveLosses = 0;
|
||||
g_logger.LogEvent("PROTECTION", "Profit detected. Consecutive loss counter reset.");
|
||||
}
|
||||
}
|
||||
}
|
||||
m_lastEquity = currentEquity;
|
||||
if(InpDebugMode)
|
||||
{
|
||||
g_logger.LogEvent("PROTECTION", StringFormat("State | Daily: %.2f | Weekly: %.2f | Consec: %d | Equity: %.2f",
|
||||
state.dailyPnL, state.weeklyPnL, state.consecutiveLosses, currentEquity));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckDailyReset(EAState &state)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
datetime todayStart = StringToTime(StringFormat("%04d.%02d.%02d 00:00:00", dt.year, dt.mon, dt.day));
|
||||
if(todayStart > m_lastDailyReset)
|
||||
{
|
||||
state.dailyPnL = 0;
|
||||
state.totalTradesToday = 0;
|
||||
state.consecutiveLosses = 0;
|
||||
m_consecLossCounter = 0;
|
||||
m_lastDailyReset = todayStart;
|
||||
state.equityAtStart = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
m_lastEquity = state.equityAtStart;
|
||||
g_logger.LogEvent("PROTECTION", "Daily counters reset");
|
||||
}
|
||||
if(dt.day_of_week == 1 && todayStart > m_lastWeeklyReset)
|
||||
{
|
||||
state.weeklyPnL = 0;
|
||||
state.totalTradesWeek = 0;
|
||||
m_lastWeeklyReset = todayStart;
|
||||
state.equityAtWeekStart = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
g_logger.LogEvent("PROTECTION", "Weekly counters reset");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ActivateBreaker(EAState &state, string reason, int seconds) const
|
||||
{
|
||||
state.circuitBreakerUntil = TimeCurrent() + seconds;
|
||||
state.circuitBreakerReason = reason;
|
||||
g_logger.LogEvent("PROTECTION", StringFormat("CIRCUIT BREAKER: %s. Halted for %d sec.", reason, seconds));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __PROTECTION_MQH__
|
||||
Reference in New Issue
Block a user