Initialize project in MT5 Experts directory

This commit is contained in:
Huthayfa
2026-05-28 19:06:07 +03:00
commit 7545b842a4
21 changed files with 3213 additions and 0 deletions
+110
View File
@@ -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 &params, 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__
+69
View File
@@ -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 &params, 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__
+170
View File
@@ -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__