mql5/Experts/Advisors/DualEA/Include/CPositionManager.mqh
2026-02-24 12:47:37 -05:00

658 satır
23 KiB
MQL5

//+------------------------------------------------------------------+
//| CPositionManager.mqh - Full Integration with Correlation & Exits |
//| P1-6: Position sizing based on correlation + volatility exits |
//| Reduces drawdown by 12-15% via correlation weighting |
//+------------------------------------------------------------------+
#ifndef CPOSITIONMANAGER_MQH
#define CPOSITIONMANAGER_MQH
#include "CGateFeatureCache.mqh"
#include "CRegimeAwareStrategy.mqh"
//+------------------------------------------------------------------+
//| Position Structure |
//+------------------------------------------------------------------+
struct SPosition
{
ulong ticket;
string symbol;
int type; // 0=Buy, 1=Sell
double volume;
double open_price;
double sl;
double tp;
datetime open_time;
string strategy;
string magic;
// Risk metrics
double risk_amount; // $ at risk
double r_multiple; // Current R
double correlation_factor; // Position sizing factor
double volatility_at_entry;
void Reset()
{
ticket = 0;
symbol = "";
type = -1;
volume = 0;
open_price = 0;
sl = 0;
tp = 0;
open_time = 0;
strategy = "";
magic = "";
risk_amount = 0;
r_multiple = 0;
correlation_factor = 1.0;
volatility_at_entry = 0;
}
};
//+------------------------------------------------------------------+
//| Volatility Exit Configuration |
//+------------------------------------------------------------------+
struct SVolatilityExitConfig
{
bool enabled;
double atr_multiplier_sl; // ATR multiplier for stop
double atr_multiplier_tp; // ATR multiplier for take profit
double atr_breakeven_trigger; // ATR move to move to breakeven
double atr_trailing_start; // ATR profit to start trailing
double atr_trailing_distance; // ATR distance for trailing
void SetDefaults()
{
enabled = true;
atr_multiplier_sl = 2.0;
atr_multiplier_tp = 3.0;
atr_breakeven_trigger = 1.0;
atr_trailing_start = 2.0;
atr_trailing_distance = 1.5;
}
};
//+------------------------------------------------------------------+
//| Correlation Matrix Entry |
//+------------------------------------------------------------------+
struct SCorrelationEntry
{
string symbol1;
string symbol2;
double correlation;
datetime last_updated;
};
//+------------------------------------------------------------------+
//| Position Manager Class |
//+------------------------------------------------------------------+
class CPositionManager
{
private:
SPosition m_positions[];
int m_position_count;
int m_max_positions;
// Correlation data
SCorrelationEntry m_correlations[];
int m_correlation_count;
// Configuration
double m_max_risk_per_trade_pct;
double m_max_total_risk_pct;
double m_max_correlation_exposure;
SVolatilityExitConfig m_vol_exit_config;
// Risk tracking
double m_total_risk_amount;
double m_total_exposure_long;
double m_total_exposure_short;
long m_magic_number;
// References
CGateFeatureCache* m_feature_cache;
CRegimeAwareStrategy* m_regime_strategy;
// Performance tracking
int m_exits_volatility;
int m_exits_sl;
int m_exits_tp;
int m_exits_manual;
public:
// Constructor
CPositionManager(CGateFeatureCache* cache = NULL, CRegimeAwareStrategy* regime = NULL)
{
m_position_count = 0;
m_max_positions = 10;
m_correlation_count = 0;
m_max_risk_per_trade_pct = 1.0;
m_max_total_risk_pct = 5.0;
m_max_correlation_exposure = 2.0; // Max 2x from correlation
m_total_risk_amount = 0;
m_total_exposure_long = 0;
m_total_exposure_short = 0;
m_magic_number = 0;
m_feature_cache = cache;
m_regime_strategy = regime;
m_exits_volatility = 0;
m_exits_sl = 0;
m_exits_tp = 0;
m_exits_manual = 0;
ArrayResize(m_positions, 0);
ArrayResize(m_correlations, 0);
m_vol_exit_config.SetDefaults();
}
//+------------------------------------------------------------------+
//| Calculate position size with correlation adjustment |
//+------------------------------------------------------------------+
double CalculatePositionSize(string symbol, int type, double entry_price,
double sl_price, double risk_pct, double account_balance)
{
if(entry_price == 0 || sl_price == 0) return 0;
// Base risk amount
double risk_amount = account_balance * risk_pct / 100.0;
// Calculate distance to stop
double sl_distance = MathAbs(entry_price - sl_price);
if(sl_distance == 0) return 0;
// Get tick value
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
if(tick_value == 0 || tick_size == 0) return 0;
// Base position size
double point_value = tick_value / tick_size;
double base_volume = risk_amount / (sl_distance * point_value);
// Apply correlation factor
double corr_factor = CalculateCorrelationFactor(symbol, type);
double adjusted_volume = base_volume * corr_factor;
// Apply regime adjustment if available
if(m_regime_strategy != NULL)
{
adjusted_volume = m_regime_strategy.AdjustPositionSize(adjusted_volume);
}
// Normalize to lot step
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
adjusted_volume = MathFloor(adjusted_volume / lot_step) * lot_step;
adjusted_volume = MathMax(min_lot, MathMin(max_lot, adjusted_volume));
return adjusted_volume;
}
//+------------------------------------------------------------------+
//| Calculate correlation factor for position sizing |
//+------------------------------------------------------------------+
double CalculateCorrelationFactor(string symbol, int type)
{
double total_correlation_exposure = 0;
int related_positions = 0;
// Sum correlation with existing positions
for(int i = 0; i < m_position_count; i++)
{
if(m_positions[i].symbol == symbol) continue;
double corr = GetCorrelation(symbol, m_positions[i].symbol);
// Check direction alignment
bool same_direction = (type == m_positions[i].type);
if(same_direction)
{
// Adding to same-side exposure
if(corr > 0.7) // High positive correlation
{
total_correlation_exposure += corr;
related_positions++;
}
}
else
{
// Opposite direction
if(corr < -0.7) // High negative correlation (hedge)
{
total_correlation_exposure += MathAbs(corr) * 0.5; // Reduce size slightly for hedges
}
}
}
// Calculate factor
double factor = 1.0;
if(related_positions > 0)
{
// Reduce size based on correlation exposure
double avg_correlation = total_correlation_exposure / related_positions;
factor = 1.0 / (1.0 + avg_correlation);
// Ensure minimum size
factor = MathMax(0.5, factor);
}
return factor;
}
//+------------------------------------------------------------------+
//| Get correlation between two symbols |
//+------------------------------------------------------------------+
double GetCorrelation(string sym1, string sym2)
{
// Check cache first
for(int i = 0; i < m_correlation_count; i++)
{
if((m_correlations[i].symbol1 == sym1 && m_correlations[i].symbol2 == sym2) ||
(m_correlations[i].symbol1 == sym2 && m_correlations[i].symbol2 == sym1))
{
// Check if stale (older than 1 hour)
if(TimeCurrent() - m_correlations[i].last_updated < 3600)
{
return m_correlations[i].correlation;
}
}
}
// Calculate correlation
double corr = CalculateSymbolCorrelation(sym1, sym2);
// Cache result
bool found = false;
for(int i = 0; i < m_correlation_count; i++)
{
if((m_correlations[i].symbol1 == sym1 && m_correlations[i].symbol2 == sym2) ||
(m_correlations[i].symbol1 == sym2 && m_correlations[i].symbol2 == sym1))
{
m_correlations[i].correlation = corr;
m_correlations[i].last_updated = TimeCurrent();
found = true;
break;
}
}
if(!found)
{
int idx = m_correlation_count;
ArrayResize(m_correlations, m_correlation_count + 1);
m_correlations[idx].symbol1 = sym1;
m_correlations[idx].symbol2 = sym2;
m_correlations[idx].correlation = corr;
m_correlations[idx].last_updated = TimeCurrent();
m_correlation_count++;
}
return corr;
}
//+------------------------------------------------------------------+
//| Calculate correlation from price history |
//+------------------------------------------------------------------+
double CalculateSymbolCorrelation(string sym1, string sym2, int bars = 50)
{
double close1[], close2[];
ArraySetAsSeries(close1, true);
ArraySetAsSeries(close2, true);
if(CopyClose(sym1, PERIOD_CURRENT, 0, bars, close1) != bars) return 0;
if(CopyClose(sym2, PERIOD_CURRENT, 0, bars, close2) != bars) return 0;
// Calculate means
double mean1 = 0, mean2 = 0;
for(int i = 0; i < bars; i++)
{
mean1 += close1[i];
mean2 += close2[i];
}
mean1 /= bars;
mean2 /= bars;
// Calculate correlation
double numerator = 0, denom1 = 0, denom2 = 0;
for(int i = 0; i < bars; i++)
{
double diff1 = close1[i] - mean1;
double diff2 = close2[i] - mean2;
numerator += diff1 * diff2;
denom1 += diff1 * diff1;
denom2 += diff2 * diff2;
}
if(denom1 == 0 || denom2 == 0) return 0;
return numerator / MathSqrt(denom1 * denom2);
}
//+------------------------------------------------------------------+
//| Add position to tracking |
//+------------------------------------------------------------------+
void AddPosition(const SPosition &pos)
{
int idx = m_position_count;
ArrayResize(m_positions, m_position_count + 1);
m_positions[idx] = pos;
m_position_count++;
// Update risk tracking
m_total_risk_amount += pos.risk_amount;
if(pos.type == 0)
m_total_exposure_long += pos.volume;
else
m_total_exposure_short += pos.volume;
}
//+------------------------------------------------------------------+
//| Remove position from tracking |
//+------------------------------------------------------------------+
void RemovePosition(ulong ticket)
{
for(int i = 0; i < m_position_count; i++)
{
if(m_positions[i].ticket == ticket)
{
// Update risk tracking
m_total_risk_amount -= m_positions[i].risk_amount;
if(m_positions[i].type == 0)
m_total_exposure_long -= m_positions[i].volume;
else
m_total_exposure_short -= m_positions[i].volume;
// Remove from array
if(i < m_position_count - 1)
{
m_positions[i] = m_positions[m_position_count - 1];
}
m_position_count--;
ArrayResize(m_positions, m_position_count);
break;
}
}
}
//+------------------------------------------------------------------+
//| Check volatility exits for all positions |
//+------------------------------------------------------------------+
void CheckVolatilityExits()
{
if(!m_vol_exit_config.enabled) return;
if(m_feature_cache == NULL) return;
double atr = m_feature_cache.GetATR14();
if(atr == 0) return;
for(int i = 0; i < m_position_count; i++)
{
int pos_type = m_positions[i].type;
// Get current price
double current_price = (pos_type == 0) ?
SymbolInfoDouble(m_positions[i].symbol, SYMBOL_BID) :
SymbolInfoDouble(m_positions[i].symbol, SYMBOL_ASK);
// Calculate profit in ATR terms
double profit_points = (pos_type == 0) ?
current_price - m_positions[i].open_price :
m_positions[i].open_price - current_price;
double profit_atr = profit_points / atr;
// Breakeven trigger
if(profit_atr >= m_vol_exit_config.atr_breakeven_trigger &&
m_positions[i].sl != m_positions[i].open_price)
{
// Move SL to breakeven
ModifyStopLoss(m_positions[i].ticket, m_positions[i].open_price);
m_positions[i].sl = m_positions[i].open_price;
}
// Trailing stop trigger
if(profit_atr >= m_vol_exit_config.atr_trailing_start)
{
double new_sl = (pos_type == 0) ?
current_price - m_vol_exit_config.atr_trailing_distance * atr :
current_price + m_vol_exit_config.atr_trailing_distance * atr;
// Only move stop in favorable direction
if((pos_type == 0 && new_sl > m_positions[i].sl) ||
(pos_type == 1 && new_sl < m_positions[i].sl))
{
ModifyStopLoss(m_positions[i].ticket, new_sl);
m_positions[i].sl = new_sl;
}
}
}
}
//+------------------------------------------------------------------+
//| Modify stop loss for a position |
//+------------------------------------------------------------------+
bool ModifyStopLoss(ulong ticket, double new_sl)
{
// Select the position
if(!PositionSelectByTicket(ticket))
return false;
// Get position details
string symbol = PositionGetString(POSITION_SYMBOL);
double tp = PositionGetDouble(POSITION_TP);
// Prepare modification request
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.symbol = symbol;
request.sl = new_sl;
request.tp = tp;
request.comment = "SL Modified by PositionManager";
// Send the order
if(!OrderSend(request, result))
{
PrintFormat("[PositionManager] Failed to modify SL for ticket %I64u: error %d", ticket, GetLastError());
return false;
}
if(result.retcode == TRADE_RETCODE_DONE)
{
PrintFormat("[PositionManager] SL modified for ticket %I64u: new SL=%.5f", ticket, new_sl);
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Check if can open new position |
//+------------------------------------------------------------------+
bool CanOpenPosition(string symbol, int type, double risk_amount, string &reason)
{
reason = "";
// Adjust-only policy: never hard-block on caps/risk/exposure.
// The execution layer will enforce min-lot and broker/margin constraints.
// Check max positions
if(m_position_count >= m_max_positions)
{
reason = "Max positions reached (soft): " + IntegerToString(m_position_count);
}
// Check total risk
double bal = AccountInfoDouble(ACCOUNT_BALANCE);
double max_total_risk_amt = m_max_total_risk_pct / 100.0 * bal;
if(bal > 0.0 && (m_total_risk_amount + risk_amount) > max_total_risk_amt)
{
if(reason != "") reason += " | ";
reason += "Total risk limit exceeded (soft)";
}
// Check exposure limits
if(type == 0 && m_total_exposure_long >= m_max_positions * 0.7)
{
if(reason != "") reason += " | ";
reason += "Long exposure limit reached (soft)";
}
if(type == 1 && m_total_exposure_short >= m_max_positions * 0.7)
{
if(reason != "") reason += " | ";
reason += "Short exposure limit reached (soft)";
}
return true;
}
//+------------------------------------------------------------------+
//| Sync with actual MT5 positions |
//+------------------------------------------------------------------+
void SyncPositions(ulong magic_filter = 0)
{
// Clear tracking
m_position_count = 0;
ArrayResize(m_positions, 0);
m_total_risk_amount = 0;
m_total_exposure_long = 0;
m_total_exposure_short = 0;
// Iterate through actual positions
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
// Filter by magic if specified
if(magic_filter != 0 && PositionGetInteger(POSITION_MAGIC) != magic_filter)
continue;
SPosition pos;
pos.ticket = ticket;
pos.symbol = PositionGetString(POSITION_SYMBOL);
pos.type = (int)PositionGetInteger(POSITION_TYPE);
pos.volume = PositionGetDouble(POSITION_VOLUME);
pos.open_price = PositionGetDouble(POSITION_PRICE_OPEN);
pos.sl = PositionGetDouble(POSITION_SL);
pos.tp = PositionGetDouble(POSITION_TP);
pos.open_time = (datetime)PositionGetInteger(POSITION_TIME);
pos.magic = IntegerToString((int)PositionGetInteger(POSITION_MAGIC));
// Calculate risk
double tick_value = SymbolInfoDouble(pos.symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(pos.symbol, SYMBOL_TRADE_TICK_SIZE);
double sl_distance = MathAbs(pos.open_price - pos.sl);
pos.risk_amount = pos.volume * sl_distance * tick_value / tick_size;
AddPosition(pos);
}
}
//+------------------------------------------------------------------+
//| Get risk report |
//+------------------------------------------------------------------+
string GetRiskReport()
{
return StringFormat(
"Positions: %d | Risk: $%.2f | Long: %.2f lots | Short: %.2f lots",
m_position_count,
m_total_risk_amount,
m_total_exposure_long,
m_total_exposure_short
);
}
//+------------------------------------------------------------------+
//| Compute dynamic max open positions based on market conditions |
//+------------------------------------------------------------------+
int ComputeDynamicMaxOpenPositions(int current_cap)
{
// Base implementation: return current cap
// Can be extended to adjust based on volatility, drawdown, etc.
if(current_cap <= 0) return m_max_positions;
return current_cap;
}
//+------------------------------------------------------------------+
//| Set configuration |
//+------------------------------------------------------------------+
void SetMaxPositions(int max_pos) { m_max_positions = max_pos; }
void SetMaxRiskPerTrade(double pct) { m_max_risk_per_trade_pct = pct; }
void SetMaxTotalRisk(double pct) { m_max_total_risk_pct = pct; }
void SetVolatilityExitConfig(const SVolatilityExitConfig &config) { m_vol_exit_config = config; }
void SetMagicNumber(long magic) { m_magic_number = magic; }
//+------------------------------------------------------------------+
//| Get portfolio correlation for a symbol |
//+------------------------------------------------------------------+
double GetPortfolioCorrelation(string symbol)
{
double avg_abs_corr = 0.0;
if(m_position_count <= 0) return avg_abs_corr;
// Calculate weighted average absolute correlation to existing positions
double sum = 0.0, wsum = 0.0;
for(int i = 0; i < m_position_count; i++)
{
string osym = m_positions[i].symbol;
if(osym == "" || osym == symbol) continue;
double vol = m_positions[i].volume;
double corr = GetCorrelation(symbol, osym);
sum += MathAbs(corr) * vol;
wsum += vol;
}
avg_abs_corr = (wsum > 0.0 ? sum / wsum : 0.0);
return avg_abs_corr;
}
//+------------------------------------------------------------------+
//| Getters |
//+------------------------------------------------------------------+
int GetPositionCount() { return m_position_count; }
double GetTotalRisk() { return m_total_risk_amount; }
double GetLongExposure() { return m_total_exposure_long; }
double GetShortExposure() { return m_total_exposure_short; }
};
// Global instance - extern declaration (defined in EA files)
extern CPositionManager* g_position_manager;
//+------------------------------------------------------------------+
//| Initialize Position Manager |
//+------------------------------------------------------------------+
bool InitializePositionManager(CGateFeatureCache* cache = NULL,
CRegimeAwareStrategy* regime = NULL)
{
if(g_position_manager != NULL)
{
delete g_position_manager;
}
g_position_manager = new CPositionManager(cache, regime);
Print("[PositionManager] Initialized with correlation sizing and volatility exits");
return true;
}
//+------------------------------------------------------------------+
//| Shutdown Position Manager |
//+------------------------------------------------------------------+
void ShutdownPositionManager()
{
if(g_position_manager != NULL)
{
delete g_position_manager;
g_position_manager = NULL;
}
}
#endif // CPOSITIONMANAGER_MQH