2082 lines
82 KiB
MQL5
2082 lines
82 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| PositionManager.mqh |
|
|
//| Copyright 2025, EscapeEA |
|
|
//| https://www.escapeea.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, EscapeEA"
|
|
#property link "https://www.escapeea.com"
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\SymbolInfo.mqh>
|
|
#include <Trade\Trade.mqh>
|
|
#include "Escape/InputValidation.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Position Manager class |
|
|
//+------------------------------------------------------------------+
|
|
class CPositionManager {
|
|
private:
|
|
CTrade *m_trade; // Trade object
|
|
CSymbolInfo *m_symbol; // Symbol info
|
|
CPositionInfo m_position; // Position info
|
|
|
|
// Configuration
|
|
double m_slPoints; // Stop loss in points
|
|
double m_tpPoints; // Take profit in points
|
|
double m_trailingStop; // Trailing stop in points
|
|
double m_breakEven; // Break even level in points
|
|
|
|
// State
|
|
bool m_initialized; // Initialization flag
|
|
bool m_autoBreakEven; // Auto break-even flag
|
|
bool m_scaleIn; // Scale into positions flag
|
|
bool m_scaleOut; // Scale out of positions flag
|
|
|
|
// Advanced position management
|
|
double m_breakEvenAt; // Pips in profit to move to break-even
|
|
double m_scaleInAt; // Pips in drawdown to scale in
|
|
double m_scaleOutAt; // Pips in profit to scale out
|
|
double m_scaleInFactor; // Scale-in volume factor
|
|
double m_scaleOutFactor; // Scale-out volume factor
|
|
|
|
// Time-based exit
|
|
bool m_useTimeExit; // Enable time-based exit
|
|
datetime m_expiryTime; // Position expiry time
|
|
int m_maxHoldBars; // Maximum bars to hold position
|
|
|
|
// Volatility-based position sizing
|
|
bool m_useVolatilitySizing; // Enable volatility-based sizing
|
|
int m_volatilityPeriod; // Period for volatility calculation
|
|
double m_volatilityMultiplier; // Multiplier for position size
|
|
double m_maxPositionRisk; // Maximum risk per position (%)
|
|
|
|
// Correlation management
|
|
string m_correlationSymbols[]; // Array of correlated symbols
|
|
double m_maxCorrelation; // Maximum allowed correlation
|
|
|
|
// Grid trading
|
|
bool m_gridTrading; // Enable grid trading
|
|
double m_gridStep; // Grid step in pips
|
|
int m_maxGridLevels; // Maximum grid levels
|
|
double m_gridVolumeMultiplier; // Volume multiplier for grid levels
|
|
|
|
// News event handling
|
|
bool m_newsFilter; // Enable news filtering
|
|
int m_newsImpact; // Minimum news impact level (1-3)
|
|
int m_minutesBeforeNews; // Minutes before news to avoid trading
|
|
int m_minutesAfterNews; // Minutes after news to avoid trading
|
|
|
|
// Advanced risk management
|
|
bool m_useDynamicRisk; // Enable dynamic risk adjustment
|
|
double m_maxDailyDrawdown; // Maximum daily drawdown percentage
|
|
double m_maxPortfolioRisk; // Maximum portfolio risk (%)
|
|
double m_riskDecayFactor; // Risk reduction factor after losses
|
|
|
|
// Adaptive position sizing
|
|
bool m_useAdaptiveSizing; // Enable adaptive position sizing
|
|
double m_winRate; // Tracked win rate (0-1)
|
|
double m_profitFactor; // Tracked profit factor
|
|
int m_tradeCount; // Number of trades in current session
|
|
double m_equityPeak; // Peak equity in current session
|
|
|
|
// Advanced exit strategies
|
|
bool m_useTrailingDrawdown; // Enable trailing drawdown exit
|
|
double m_trailingDrawdown; // Trailing drawdown percentage
|
|
bool m_useVolatilityExit; // Enable volatility-based exit
|
|
double m_volatilityExitLevel; // Volatility level for exit
|
|
|
|
// Position clustering
|
|
bool m_usePositionClustering; // Enable position clustering
|
|
double m_clusterDistance; // Distance between position clusters
|
|
int m_maxClusters; // Maximum number of clusters
|
|
|
|
// Machine learning integration
|
|
bool m_useMLPredictions; // Enable ML-based predictions
|
|
double m_mlConfidenceThreshold; // Minimum confidence threshold
|
|
|
|
// Market regime detection
|
|
bool m_useRegimeFilter; // Enable market regime filtering
|
|
int m_regimePeriod; // Period for regime detection
|
|
double m_trendThreshold; // Threshold for trend detection
|
|
double m_rangeThreshold; // Threshold for range detection
|
|
|
|
// Private methods
|
|
bool ValidatePositionParams(const string symbol, double volume, double price,
|
|
double sl, double tp, const string comment);
|
|
|
|
public:
|
|
// Constructor/Destructor
|
|
CPositionManager();
|
|
~CPositionManager();
|
|
|
|
// Initialization
|
|
bool Initialize(CTrade *trade, CSymbolInfo *symbol, double slPoints = 0,
|
|
double tpPoints = 0, double trailingStop = 0, double breakEven = 0);
|
|
|
|
// Position management
|
|
bool OpenPosition(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double price, double sl, double tp, const string comment = "");
|
|
bool ClosePosition(ulong ticket, const string comment = "");
|
|
bool CloseAllPositions(const string symbol = "", int magic = -1);
|
|
bool ModifyPosition(ulong ticket, double sl, double tp);
|
|
|
|
// Trailing stop
|
|
void CheckTrailingStop();
|
|
|
|
// Advanced position management
|
|
bool SetBreakEven(double pips, double lockInPips = 0);
|
|
bool SetScaling(bool scaleIn, bool scaleOut, double scaleInAt = 0,
|
|
double scaleOutAt = 0, double scaleInFactor = 0.5,
|
|
double scaleOutFactor = 0.5);
|
|
bool SetTimeExit(bool enable, datetime expiry = 0, int maxBars = 0);
|
|
bool ScaleInPosition(ulong ticket, double volume);
|
|
bool ScaleOutPosition(ulong ticket, double volume);
|
|
bool PartialClose(ulong ticket, double percent);
|
|
bool CloseAtTime(ulong ticket, datetime closeTime);
|
|
bool CloseAtProfit(ulong ticket, double targetProfit, bool closePartial = false);
|
|
bool CloseAtLoss(ulong ticket, double maxLoss, bool closePartial = false);
|
|
bool MoveToBreakEven(ulong ticket);
|
|
|
|
// Position analysis
|
|
double GetPositionRisk(ulong ticket);
|
|
double GetPositionRiskPercent(ulong ticket, double accountBalance = 0);
|
|
double GetPositionRiskReward(ulong ticket);
|
|
datetime GetPositionAge(ulong ticket, ENUM_TIME_UNITS unit = PERIOD_M1);
|
|
|
|
// Getters
|
|
bool IsInitialized() const { return m_initialized; }
|
|
int TotalPositions(const string symbol = "", int magic = -1);
|
|
double GetPositionProfit(const string symbol = "", int magic = -1);
|
|
double GetPositionVolume(const string symbol = "", int magic = -1);
|
|
bool HasOpenPosition(const string symbol = "", int magic = -1);
|
|
ENUM_POSITION_TYPE GetPositionType(ulong ticket);
|
|
double GetPositionOpenPrice(ulong ticket);
|
|
double GetPositionCurrentPrice(ulong ticket);
|
|
double GetPositionStopLoss(ulong ticket);
|
|
double GetPositionTakeProfit(ulong ticket);
|
|
|
|
// Advanced position management
|
|
bool SetVolatilitySizing(bool enable, int period = 14, double multiplier = 1.0, double maxRisk = 2.0);
|
|
double CalculateVolatilityAdjustedVolume(double riskPercent, double slPips);
|
|
bool SetCorrelationFilter(string &symbols[], double maxCorrelation = 0.7);
|
|
double GetCorrelationWith(const string symbol, int period = 100);
|
|
bool IsCorrelationSafe();
|
|
bool SetGridTrading(bool enable, double gridStep = 20, int maxLevels = 5, double volumeMultiplier = 1.5);
|
|
bool ManageGridLevels(const string symbol, double currentPrice, ENUM_POSITION_TYPE type);
|
|
bool SetNewsFilter(bool enable, int minImpact = 2, int minutesBefore = 60, int minutesAfter = 30);
|
|
bool IsNewsEventImminent();
|
|
bool IsHighImpactNewsPending();
|
|
|
|
// Advanced position scaling
|
|
bool SetScalingProfile(ENUM_SCALING_PROFILE profile);
|
|
double CalculateOptimalScaleInVolume(double baseVolume, int scaleLevel, double drawdownPct);
|
|
bool CheckScalingOpportunity(ulong ticket, double &volume);
|
|
bool ScaleInPosition(ulong ticket, double volume);
|
|
bool ScaleOutPosition(ulong ticket, double volume);
|
|
|
|
// Advanced exit strategies
|
|
bool SetExitProfile(ENUM_EXIT_PROFILE profile);
|
|
bool CheckExitConditions(ulong ticket, double &closePrice, string &reason);
|
|
bool UsePyramidingExit(ulong ticket, double &closeVolume);
|
|
bool UseTimeExit(ulong ticket, datetime openTime);
|
|
bool UseVolatilityExit(ulong ticket, double currentVolatility);
|
|
|
|
// Correlation-based position sizing
|
|
double CalculateCorrelationAdjustedVolume(const string symbol, double baseVolume);
|
|
double GetPortfolioCorrelation(const string symbol);
|
|
double GetMarketCorrelation(const string symbol1, const string symbol2, int period = 100);
|
|
|
|
// Machine learning integration
|
|
bool LoadMLModel(const string modelFile);
|
|
double GetMLPositionScore(const string symbol, ENUM_POSITION_TYPE type);
|
|
bool UpdateMLModel(const MqlRates &rates[], const double &features[]);
|
|
|
|
// Advanced position monitoring
|
|
bool MonitorPosition(ulong ticket);
|
|
void UpdatePositionStats(ulong ticket);
|
|
bool NeedsAdjustment(ulong ticket);
|
|
|
|
// Advanced order management
|
|
bool PlaceBracketOrder(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double entryPrice, double sl, double tp,
|
|
double breakEvenAt = 0, double trailingStop = 0,
|
|
datetime expiration = 0, const string comment = "");
|
|
bool PlaceOCOOrders(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double price1, double price2, double sl, double tp,
|
|
const string comment = "");
|
|
|
|
// Advanced risk controls
|
|
bool CheckVolatilitySpike();
|
|
bool CheckLiquidity(const string symbol);
|
|
bool CheckMarketHours();
|
|
|
|
// Position clustering
|
|
bool SetPositionClustering(bool enable, double clusterDistance = 20.0, int maxClusters = 3);
|
|
bool IsInExistingCluster(const string symbol, double price, ENUM_POSITION_TYPE type);
|
|
|
|
// Market regime detection
|
|
bool SetRegimeFilter(bool enable, int period = 50, double trendThreshold = 0.3, double rangeThreshold = 0.2);
|
|
ENUM_MARKET_REGIME DetectMarketRegime();
|
|
bool IsFavorableRegime(ENUM_POSITION_TYPE type, ENUM_MARKET_REGIME regime);
|
|
|
|
// Advanced risk management
|
|
bool SetDynamicRisk(bool enable, double maxDailyDrawdown = 5.0, double maxPositionRisk = 2.0,
|
|
double maxPortfolioRisk = 20.0, double riskDecay = 0.9);
|
|
double CalculateDynamicRisk(double baseRisk);
|
|
bool CheckDrawdownLimits();
|
|
double GetCurrentDrawdown();
|
|
|
|
// Adaptive position sizing
|
|
bool SetAdaptiveSizing(bool enable, double initialWinRate = 0.5, double initialProfitFactor = 1.5);
|
|
void UpdateTradeStats(bool isWin, double profit);
|
|
double CalculateAdaptiveVolume(double baseVolume, double confidence = 1.0);
|
|
|
|
// Advanced exit strategies
|
|
bool SetTrailingDrawdown(bool enable, double drawdownPercent = 10.0);
|
|
bool SetVolatilityExit(bool enable, double volatilityThreshold = 2.0);
|
|
bool CheckAdvancedExitConditions(ulong ticket);
|
|
|
|
// Position clustering
|
|
bool SetPositionClustering(bool enable, double clusterDistance = 20.0, int maxClusters = 3);
|
|
bool IsInExistingCluster(const string symbol, double price, ENUM_POSITION_TYPE type);
|
|
|
|
// Machine learning integration
|
|
bool SetMLIntegration(bool enable, double confidenceThreshold = 0.7);
|
|
double GetMLPrediction(const string symbol, ENUM_TIMEFRAMES timeframe);
|
|
|
|
// Market regime detection
|
|
bool SetRegimeFilter(bool enable, int period = 50, double trendThreshold = 0.3, double rangeThreshold = 0.2);
|
|
ENUM_MARKET_REGIME DetectMarketRegime();
|
|
bool IsFavorableRegime(ENUM_POSITION_TYPE type, ENUM_MARKET_REGIME regime);
|
|
|
|
// Advanced position analysis
|
|
double CalculateOptimalF(double winRate, double winLossRatio);
|
|
double CalculateKellyCriterion(double winRate, double winLossRatio);
|
|
double CalculatePositionScore(const string symbol, ENUM_POSITION_TYPE type);
|
|
|
|
// Advanced order types
|
|
bool PlaceBracketOrder(const string symbol, ENUM_ORDER_TYPE type, double volume, double entryPrice,
|
|
double sl, double tp, double breakEvenAt = 0, double trailingStop = 0,
|
|
datetime expiration = 0, const string comment = "");
|
|
bool PlaceOCOOrders(const string symbol, ENUM_ORDER_TYPE type, double volume, double price1,
|
|
double price2, double sl, double tp, const string comment = "");
|
|
|
|
// Advanced position monitoring
|
|
bool MonitorPosition(ulong ticket);
|
|
void UpdatePositionStats(ulong ticket);
|
|
bool NeedsAdjustment(ulong ticket);
|
|
|
|
// Advanced risk controls
|
|
bool CheckVolatilitySpike();
|
|
bool CheckLiquidity(const string symbol);
|
|
bool CheckMarketHours();
|
|
|
|
// State management
|
|
void Update();
|
|
|
|
// Advanced position analysis
|
|
double CalculatePositionScore(const string symbol, ENUM_POSITION_TYPE type);
|
|
double CalculateOptimalF(double winRate, double winLossRatio);
|
|
double CalculateKellyCriterion(double winRate, double winLossRatio);
|
|
|
|
// Risk management
|
|
bool SetDynamicRisk(bool enable, double maxDailyDrawdown = 5.0, double maxPositionRisk = 2.0,
|
|
double maxPortfolioRisk = 20.0, double riskDecay = 0.9);
|
|
double CalculateDynamicRisk(double baseRisk);
|
|
bool CheckDrawdownLimits();
|
|
double GetCurrentDrawdown();
|
|
|
|
// Adaptive position sizing
|
|
bool SetAdaptiveSizing(bool enable, double initialWinRate = 0.5, double initialProfitFactor = 1.5);
|
|
void UpdateTradeStats(bool isWin, double profit);
|
|
double CalculateAdaptiveVolume(double baseVolume, double confidence = 1.0);
|
|
|
|
// Advanced exit strategies
|
|
bool SetTrailingDrawdown(bool enable, double drawdownPercent = 10.0);
|
|
bool SetVolatilityExit(bool enable, double volatilityThreshold = 2.0);
|
|
bool CheckAdvancedExitConditions(ulong ticket);
|
|
|
|
// Machine learning integration
|
|
bool SetMLIntegration(bool enable, double confidenceThreshold = 0.7);
|
|
double GetMLPrediction(const string symbol, ENUM_TIMEFRAMES timeframe);
|
|
|
|
// Position clustering
|
|
bool SetPositionClustering(bool enable, double clusterDistance = 20.0, int maxClusters = 3);
|
|
|
|
// Market regime detection
|
|
bool SetRegimeFilter(bool enable, int period = 50, double trendThreshold = 0.3, double rangeThreshold = 0.2);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CPositionManager::CPositionManager() :
|
|
m_trade(NULL),
|
|
m_symbol(NULL),
|
|
m_slPoints(0),
|
|
m_tpPoints(0),
|
|
m_trailingStop(0),
|
|
m_breakEven(0),
|
|
m_initialized(false),
|
|
m_autoBreakEven(false),
|
|
m_scaleIn(false),
|
|
m_scaleOut(false),
|
|
m_breakEvenAt(0),
|
|
m_scaleInAt(0),
|
|
m_scaleOutAt(0),
|
|
m_scaleInFactor(0.5),
|
|
m_scaleOutFactor(0.5),
|
|
m_useTimeExit(false),
|
|
m_expiryTime(0),
|
|
m_maxHoldBars(0),
|
|
m_useVolatilitySizing(false),
|
|
m_volatilityPeriod(14),
|
|
m_volatilityMultiplier(1.0),
|
|
m_maxPositionRisk(2.0),
|
|
m_maxCorrelation(0.7),
|
|
m_gridTrading(false),
|
|
m_gridStep(20.0),
|
|
m_maxGridLevels(5),
|
|
m_gridVolumeMultiplier(1.5),
|
|
m_newsFilter(false),
|
|
m_newsImpact(2),
|
|
m_minutesBeforeNews(60),
|
|
m_minutesAfterNews(30),
|
|
m_useDynamicRisk(false),
|
|
m_maxDailyDrawdown(5.0),
|
|
m_maxPortfolioRisk(20.0),
|
|
m_riskDecayFactor(0.9),
|
|
m_useAdaptiveSizing(false),
|
|
m_winRate(0.5),
|
|
m_profitFactor(1.5),
|
|
m_tradeCount(0),
|
|
m_equityPeak(0),
|
|
m_useTrailingDrawdown(false),
|
|
m_trailingDrawdown(10.0),
|
|
m_useVolatilityExit(false),
|
|
m_volatilityExitLevel(2.0),
|
|
m_usePositionClustering(false),
|
|
m_clusterDistance(20.0),
|
|
m_maxClusters(3),
|
|
m_useMLPredictions(false),
|
|
m_mlConfidenceThreshold(0.7),
|
|
m_useRegimeFilter(false),
|
|
m_regimePeriod(50),
|
|
m_trendThreshold(0.3),
|
|
m_rangeThreshold(0.2)
|
|
{
|
|
// Initialize position info
|
|
m_position = CPositionInfo();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CPositionManager::~CPositionManager() {
|
|
// Clean up
|
|
m_trade = NULL;
|
|
m_symbol = NULL;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize position manager |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::Initialize(CTrade *trade, CSymbolInfo *symbol, double slPoints,
|
|
double tpPoints, double trailingStop, double breakEven) {
|
|
// Validate inputs
|
|
if(trade == NULL || symbol == NULL) {
|
|
Print("Error: Invalid trade or symbol object");
|
|
return false;
|
|
}
|
|
|
|
// Store references and settings
|
|
m_trade = trade;
|
|
m_symbol = symbol;
|
|
m_slPoints = slPoints;
|
|
m_tpPoints = tpPoints;
|
|
m_trailingStop = trailingStop;
|
|
m_breakEven = breakEven;
|
|
|
|
m_initialized = true;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate position parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ValidatePositionParams(const string symbol, double volume, double price,
|
|
double sl, double tp, const string comment) {
|
|
// Validate symbol
|
|
if(symbol == "" || !SymbolSelect(symbol, true)) {
|
|
Print("Error: Invalid symbol ", symbol);
|
|
return false;
|
|
}
|
|
|
|
// Validate volume
|
|
double minVolume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxVolume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
double volumeStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
if(volume < minVolume || volume > maxVolume) {
|
|
PrintFormat("Error: Volume %.2f is outside allowed range [%.2f, %.2f]",
|
|
volume, minVolume, maxVolume);
|
|
return false;
|
|
}
|
|
|
|
// Normalize volume to the nearest step
|
|
volume = MathFloor(volume / volumeStep) * volumeStep;
|
|
|
|
// Validate price levels
|
|
if(price <= 0) {
|
|
Print("Error: Invalid price ", price);
|
|
return false;
|
|
}
|
|
|
|
// Validate stop loss and take profit levels
|
|
if((sl > 0 && sl >= price) || (sl < 0 && sl <= -price)) {
|
|
Print("Error: Invalid stop loss level ", sl);
|
|
return false;
|
|
}
|
|
|
|
if((tp > 0 && tp <= price) || (tp < 0 && tp >= -price)) {
|
|
Print("Error: Invalid take profit level ", tp);
|
|
return false;
|
|
}
|
|
|
|
// Validate comment length
|
|
if(StringLen(comment) > 31) {
|
|
Print("Error: Comment is too long (max 31 characters)");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open a new position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::OpenPosition(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double price, double sl, double tp, const string comment) {
|
|
if(!m_initialized) {
|
|
Print("Error: Position manager not initialized");
|
|
return false;
|
|
}
|
|
|
|
// Validate parameters
|
|
if(!ValidatePositionParams(symbol, volume, price, sl, tp, comment)) {
|
|
return false;
|
|
}
|
|
|
|
// Open position based on order type
|
|
if(type == ORDER_TYPE_BUY) {
|
|
if(!m_trade.Buy(volume, symbol, price, sl, tp, comment)) {
|
|
Print("Error opening buy position: ", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
else if(type == ORDER_TYPE_SELL) {
|
|
if(!m_trade.Sell(volume, symbol, price, sl, tp, comment)) {
|
|
Print("Error opening sell position: ", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
Print("Error: Unsupported order type");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close a position by ticket |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ClosePosition(ulong ticket, const string comment) {
|
|
if(!m_initialized) {
|
|
Print("Error: Position manager not initialized");
|
|
return false;
|
|
}
|
|
|
|
// Select position by ticket
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
Print("Error selecting position ", ticket, ": ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
// Close the position
|
|
if(!m_trade.PositionClose(ticket, m_position.Deviation())) {
|
|
Print("Error closing position ", ticket, ": ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all positions |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CloseAllPositions(const string symbol = "", int magic = -1) {
|
|
bool result = true;
|
|
|
|
// Loop through all open positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
// Check symbol filter
|
|
if(symbol != "" && m_position.Symbol() != symbol) {
|
|
continue;
|
|
}
|
|
|
|
// Check magic number filter
|
|
if(magic >= 0 && (int)m_position.Magic() != magic) {
|
|
continue;
|
|
}
|
|
|
|
// Close the position
|
|
if(!ClosePosition(m_position.Ticket())) {
|
|
result = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Modify position's stop loss and take profit |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ModifyPosition(ulong ticket, double sl, double tp) {
|
|
if(!m_initialized) {
|
|
Print("Error: Position manager not initialized");
|
|
return false;
|
|
}
|
|
|
|
// Select position by ticket
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
Print("Error selecting position ", ticket, ": ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
// Get current position details
|
|
double openPrice = m_position.PriceOpen();
|
|
double currentSl = m_position.StopLoss();
|
|
double currentTp = m_position.TakeProfit();
|
|
|
|
// Only modify if values have changed
|
|
if(currentSl != sl || currentTp != tp) {
|
|
if(!m_trade.PositionModify(ticket, sl, tp)) {
|
|
Print("Error modifying position ", ticket, ": ", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check and update trailing stops |
|
|
//+------------------------------------------------------------------+
|
|
void CPositionManager::CheckTrailingStop() {
|
|
if(!m_initialized || m_trailingStop <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Update symbol rates
|
|
m_symbol.RefreshRates();
|
|
|
|
// Loop through all open positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
// Skip if not our symbol
|
|
if(m_position.Symbol() != m_symbol.Name()) {
|
|
continue;
|
|
}
|
|
|
|
double positionSl = m_position.StopLoss();
|
|
double newSl = 0;
|
|
|
|
if(m_position.PositionType() == POSITION_TYPE_BUY) {
|
|
// For long positions, trail below the current price
|
|
newSl = m_symbol.Bid() - m_trailingStop * m_symbol.Point();
|
|
|
|
// Only move the stop loss up
|
|
if(newSl > positionSl || positionSl == 0) {
|
|
ModifyPosition(m_position.Ticket(), newSl, m_position.TakeProfit());
|
|
}
|
|
}
|
|
else if(m_position.PositionType() == POSITION_TYPE_SELL) {
|
|
// For short positions, trail above the current price
|
|
newSl = m_symbol.Ask() + m_trailingStop * m_symbol.Point();
|
|
|
|
// Only move the stop loss down
|
|
if((newSl < positionSl || positionSl == 0) && newSl > 0) {
|
|
ModifyPosition(m_position.Ticket(), newSl, m_position.TakeProfit());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get total number of positions |
|
|
//+------------------------------------------------------------------+
|
|
int CPositionManager::TotalPositions(const string symbol = "", int magic = -1) {
|
|
int count = 0;
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
// Check symbol filter
|
|
if(symbol != "" && m_position.Symbol() != symbol) {
|
|
continue;
|
|
}
|
|
|
|
// Check magic number filter
|
|
if(magic >= 0 && (int)m_position.Magic() != magic) {
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get total profit/loss for positions |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionProfit(const string symbol = "", int magic = -1) {
|
|
double profit = 0;
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
// Check symbol filter
|
|
if(symbol != "" && m_position.Symbol() != symbol) {
|
|
continue;
|
|
}
|
|
|
|
// Check magic number filter
|
|
if(magic >= 0 && (int)m_position.Magic() != magic) {
|
|
continue;
|
|
}
|
|
|
|
profit += m_position.Profit() + m_position.Swap() + m_position.Commission();
|
|
}
|
|
}
|
|
|
|
return profit;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set break-even parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetBreakEven(double pips, double lockInPips = 0) {
|
|
if(pips <= 0) {
|
|
m_autoBreakEven = false;
|
|
return true;
|
|
}
|
|
|
|
m_autoBreakEven = true;
|
|
m_breakEvenAt = pips * m_symbol.Point() * 10; // Convert pips to points
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set position scaling parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetScaling(bool scaleIn, bool scaleOut, double scaleInAt,
|
|
double scaleOutAt, double scaleInFactor, double scaleOutFactor) {
|
|
m_scaleIn = scaleIn;
|
|
m_scaleOut = scaleOut;
|
|
|
|
if(scaleIn) {
|
|
m_scaleInAt = scaleInAt * m_symbol.Point() * 10;
|
|
m_scaleInFactor = MathMin(MathMax(scaleInFactor, 0.1), 1.0);
|
|
}
|
|
|
|
if(scaleOut) {
|
|
m_scaleOutAt = scaleOutAt * m_symbol.Point() * 10;
|
|
m_scaleOutFactor = MathMin(MathMax(scaleOutFactor, 0.1), 1.0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set time-based exit parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetTimeExit(bool enable, datetime expiry, int maxBars) {
|
|
m_useTimeExit = enable;
|
|
if(enable) {
|
|
if(expiry > 0) m_expiryTime = expiry;
|
|
if(maxBars > 0) m_maxHoldBars = maxBars;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Scale out of an existing position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ScaleOutPosition(ulong ticket, double volume) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
Print("Error selecting position ", ticket);
|
|
return false;
|
|
}
|
|
|
|
// Calculate volume to close if not specified
|
|
if(volume <= 0) {
|
|
volume = m_position.Volume() * m_scaleOutFactor;
|
|
}
|
|
|
|
// Close a portion of the position
|
|
return PartialClose(ticket, volume / m_position.Volume() * 100);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Partially close a position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::PartialClose(ulong ticket, double percent) {
|
|
if(percent <= 0 || percent > 100) {
|
|
Print("Error: Invalid percentage ", percent);
|
|
return false;
|
|
}
|
|
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
Print("Error selecting position ", ticket);
|
|
return false;
|
|
}
|
|
|
|
// Calculate volume to close
|
|
double volume = NormalizeDouble(m_position.Volume() * percent / 100, 2);
|
|
if(volume < m_symbol.LotsMin()) {
|
|
Print("Error: Volume too small for partial close");
|
|
return false;
|
|
}
|
|
|
|
// Close part of the position
|
|
return m_trade.PositionClosePartial(ticket, volume);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close position at specific time |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CloseAtTime(ulong ticket, datetime closeTime) {
|
|
if(TimeCurrent() >= closeTime) {
|
|
return ClosePosition(ticket, "Time-based exit");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close position when target profit is reached |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CloseAtProfit(ulong ticket, double targetProfit, bool closePartial = false) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return false;
|
|
}
|
|
|
|
double currentProfit = m_position.Profit() + m_position.Swap() + m_position.Commission();
|
|
|
|
if(currentProfit >= targetProfit) {
|
|
if(closePartial) {
|
|
// Close a portion based on how much we're in profit
|
|
double profitRatio = MathMin(1.0, currentProfit / targetProfit);
|
|
return PartialClose(ticket, profitRatio * 100);
|
|
} else {
|
|
return ClosePosition(ticket, "Target profit reached");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close position when max loss is reached |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CloseAtLoss(ulong ticket, double maxLoss, bool closePartial = false) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return false;
|
|
}
|
|
|
|
double currentLoss = MathAbs(m_position.Profit() + m_position.Swap() + m_position.Commission());
|
|
|
|
if(currentLoss >= maxLoss) {
|
|
if(closePartial) {
|
|
// Close a portion based on how much we're in loss
|
|
double lossRatio = MathMin(1.0, currentLoss / maxLoss);
|
|
return PartialClose(ticket, lossRatio * 100);
|
|
} else {
|
|
return ClosePosition(ticket, "Max loss reached");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Move position to break-even |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::MoveToBreakEven(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return false;
|
|
}
|
|
|
|
double openPrice = m_position.PriceOpen();
|
|
double currentSl = m_position.StopLoss();
|
|
double newSl = 0;
|
|
|
|
if(m_position.PositionType() == POSITION_TYPE_BUY) {
|
|
newSl = openPrice + m_symbol.Spread() * m_symbol.Point();
|
|
if(newSl > currentSl || currentSl == 0) {
|
|
return ModifyPosition(ticket, newSl, m_position.TakeProfit());
|
|
}
|
|
} else if(m_position.PositionType() == POSITION_TYPE_SELL) {
|
|
newSl = openPrice - m_symbol.Spread() * m_symbol.Point();
|
|
if((newSl < currentSl || currentSl == 0) && newSl > 0) {
|
|
return ModifyPosition(ticket, newSl, m_position.TakeProfit());
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position risk in account currency |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionRisk(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return 0;
|
|
}
|
|
|
|
double openPrice = m_position.PriceOpen();
|
|
double sl = m_position.StopLoss();
|
|
double volume = m_position.Volume();
|
|
|
|
if(sl == 0) return 0;
|
|
|
|
double tickValue = m_symbol.TickValue();
|
|
double tickSize = m_symbol.TickSize();
|
|
double pointValue = tickValue / tickSize * m_symbol.Point();
|
|
|
|
if(m_position.PositionType() == POSITION_TYPE_BUY) {
|
|
return (openPrice - sl) * volume * pointValue;
|
|
} else {
|
|
return (sl - openPrice) * volume * pointValue;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position risk as percentage of account balance |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionRiskPercent(ulong ticket, double accountBalance = 0) {
|
|
if(accountBalance <= 0) {
|
|
accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
}
|
|
|
|
double risk = GetPositionRisk(ticket);
|
|
return (risk / accountBalance) * 100;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position risk/reward ratio |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionRiskReward(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return 0;
|
|
}
|
|
|
|
double openPrice = m_position.PriceOpen();
|
|
double sl = m_position.StopLoss();
|
|
double tp = m_position.TakeProfit();
|
|
|
|
if(sl == 0 || tp == 0) return 0;
|
|
|
|
if(m_position.PositionType() == POSITION_TYPE_BUY) {
|
|
return (tp - openPrice) / (openPrice - sl);
|
|
} else {
|
|
return (openPrice - tp) / (sl - openPrice);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position age in specified time units |
|
|
//+------------------------------------------------------------------+
|
|
datetime CPositionManager::GetPositionAge(ulong ticket, ENUM_TIME_UNITS unit = PERIOD_M1) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return 0;
|
|
}
|
|
|
|
datetime age = TimeCurrent() - m_position.Time();
|
|
|
|
switch(unit) {
|
|
case PERIOD_M1: return age / 60;
|
|
case PERIOD_M5: return age / (60 * 5);
|
|
case PERIOD_M15: return age / (60 * 15);
|
|
case PERIOD_M30: return age / (60 * 30);
|
|
case PERIOD_H1: return age / (60 * 60);
|
|
case PERIOD_H4: return age / (60 * 60 * 4);
|
|
case PERIOD_D1: return age / (60 * 60 * 24);
|
|
default: return age;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get total position volume |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionVolume(const string symbol = "", int magic = -1) {
|
|
double volume = 0;
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
if((symbol == "" || m_position.Symbol() == symbol) &&
|
|
(magic < 0 || (int)m_position.Magic() == magic)) {
|
|
volume += m_position.Volume();
|
|
}
|
|
}
|
|
}
|
|
|
|
return volume;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if there are any open positions |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::HasOpenPosition(const string symbol = "", int magic = -1) {
|
|
return (TotalPositions(symbol, magic) > 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position type |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_POSITION_TYPE CPositionManager::GetPositionType(ulong ticket) {
|
|
if(m_position.SelectByTicket(ticket)) {
|
|
return m_position.PositionType();
|
|
}
|
|
return WRONG_VALUE;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position open price |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionOpenPrice(ulong ticket) {
|
|
if(m_position.SelectByTicket(ticket)) {
|
|
return m_position.PriceOpen();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position current price |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionCurrentPrice(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) {
|
|
return 0;
|
|
}
|
|
|
|
if(m_position.PositionType() == POSITION_TYPE_BUY) {
|
|
return m_symbol.Bid();
|
|
} else {
|
|
return m_symbol.Ask();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position stop loss level |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionStopLoss(ulong ticket) {
|
|
if(m_position.SelectByTicket(ticket)) {
|
|
return m_position.StopLoss();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position take profit level |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPositionTakeProfit(ulong ticket) {
|
|
if(m_position.SelectByTicket(ticket)) {
|
|
return m_position.TakeProfit();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set volatility-based position sizing |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetVolatilitySizing(bool enable, int period, double multiplier, double maxRisk) {
|
|
m_useVolatilitySizing = enable;
|
|
if(enable) {
|
|
m_volatilityPeriod = MathMax(period, 5);
|
|
m_volatilityMultiplier = MathMax(multiplier, 0.1);
|
|
m_maxPositionRisk = MathMin(MathMax(maxRisk, 0.1), 10.0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate position size based on volatility |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateVolatilityAdjustedVolume(double riskPercent, double slPips) {
|
|
if(!m_initialized || !m_useVolatilitySizing) {
|
|
return 0.0;
|
|
}
|
|
|
|
// Calculate ATR for volatility
|
|
double atr = iATR(m_symbol.Name(), PERIOD_CURRENT, m_volatilityPeriod, 0);
|
|
double atrPips = atr / (m_symbol.Point() * 10);
|
|
|
|
// Get account balance and calculate risk amount
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskAmount = balance * (riskPercent / 100.0);
|
|
|
|
// Calculate position size based on ATR
|
|
double tickValue = m_symbol.TickValue();
|
|
double tickSize = m_symbol.TickSize();
|
|
double pointValue = tickValue / tickSize * m_symbol.Point();
|
|
|
|
// Adjust position size based on volatility
|
|
double volume = riskAmount / (slPips * 10 * pointValue);
|
|
double volatilityAdjustment = m_volatilityMultiplier * (50.0 / atrPips);
|
|
volume *= volatilityAdjustment;
|
|
|
|
// Apply position limits
|
|
double minVolume = m_symbol.LotsMin();
|
|
double maxVolume = m_symbol.LotsMax();
|
|
double stepVolume = m_symbol.LotsStep();
|
|
|
|
volume = MathFloor(volume / stepVolume) * stepVolume;
|
|
volume = MathMin(MathMax(volume, minVolume), maxVolume);
|
|
|
|
return volume;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set correlation filter for position management |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetCorrelationFilter(string &symbols[], double maxCorrelation) {
|
|
ArrayFree(m_correlationSymbols);
|
|
int count = ArraySize(symbols);
|
|
if(count == 0) return false;
|
|
|
|
ArrayResize(m_correlationSymbols, count);
|
|
for(int i = 0; i < count; i++) {
|
|
m_correlationSymbols[i] = symbols[i];
|
|
}
|
|
|
|
m_maxCorrelation = MathMin(MathMax(maxCorrelation, 0.1), 1.0);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get correlation with another symbol |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetCorrelationWith(const string symbol, int period = 100) {
|
|
if(!m_initialized || symbol == m_symbol.Name()) return 1.0;
|
|
|
|
// Get close prices for both symbols
|
|
double basePrices[], targetPrices[];
|
|
ArraySetAsSeries(basePrices, true);
|
|
ArraySetAsSeries(targetPrices, true);
|
|
|
|
if(CopyClose(m_symbol.Name(), PERIOD_CURRENT, 0, period, basePrices) != period ||
|
|
CopyClose(symbol, PERIOD_CURRENT, 0, period, targetPrices) != period) {
|
|
Print("Error getting price data for correlation");
|
|
return 0.0;
|
|
}
|
|
|
|
// Calculate correlation coefficient
|
|
return MathCorrelation(basePrices, targetPrices, period);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if correlation is within safe limits |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::IsCorrelationSafe() {
|
|
int count = ArraySize(m_correlationSymbols);
|
|
if(count == 0) return true;
|
|
|
|
for(int i = 0; i < count; i++) {
|
|
double correlation = GetCorrelationWith(m_correlationSymbols[i]);
|
|
if(MathAbs(correlation) > m_maxCorrelation) {
|
|
PrintFormat("High correlation (%.2f) with %s", correlation, m_correlationSymbols[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Configure grid trading parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetGridTrading(bool enable, double gridStep, int maxLevels, double volumeMultiplier) {
|
|
m_gridTrading = enable;
|
|
if(enable) {
|
|
m_gridStep = MathMax(gridStep, 1.0) * m_symbol.Point() * 10; // Convert pips to points
|
|
m_maxGridLevels = MathMax(maxLevels, 1);
|
|
m_gridVolumeMultiplier = MathMax(volumeMultiplier, 1.0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage grid trading levels |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ManageGridLevels(const string symbol, double currentPrice, ENUM_POSITION_TYPE type) {
|
|
if(!m_gridTrading || !m_initialized) return false;
|
|
|
|
// Count existing grid levels
|
|
int gridLevels = 0;
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i) && m_position.Symbol() == symbol) {
|
|
gridLevels++;
|
|
}
|
|
}
|
|
|
|
// Check if we can add a new grid level
|
|
if(gridLevels >= m_maxGridLevels) return false;
|
|
|
|
// Get the last position in the grid
|
|
double lastPrice = (type == POSITION_TYPE_BUY) ? 0 : DBL_MAX;
|
|
double totalVolume = 0;
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i) && m_position.Symbol() == symbol &&
|
|
m_position.PositionType() == type) {
|
|
if((type == POSITION_TYPE_BUY && m_position.PriceOpen() > lastPrice) ||
|
|
(type == POSITION_TYPE_SELL && m_position.PriceOpen() < lastPrice)) {
|
|
lastPrice = m_position.PriceOpen();
|
|
}
|
|
totalVolume += m_position.Volume();
|
|
}
|
|
}
|
|
|
|
// Calculate next grid level
|
|
double nextLevel = 0;
|
|
double volume = 0;
|
|
|
|
if(type == POSITION_TYPE_BUY) {
|
|
nextLevel = (lastPrice == 0) ? currentPrice : lastPrice - m_gridStep;
|
|
if(currentPrice <= nextLevel) {
|
|
volume = totalVolume * m_gridVolumeMultiplier;
|
|
return OpenPosition(symbol, type, volume, 0, 0, 0, "Grid Level " + IntegerToString(gridLevels + 1));
|
|
}
|
|
} else {
|
|
nextLevel = (lastPrice == DBL_MAX) ? currentPrice : lastPrice + m_gridStep;
|
|
if(currentPrice >= nextLevel) {
|
|
volume = totalVolume * m_gridVolumeMultiplier;
|
|
return OpenPosition(symbol, type, volume, 0, 0, 0, "Grid Level " + IntegerToString(gridLevels + 1));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Configure news filter parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetNewsFilter(bool enable, int minImpact, int minutesBefore, int minutesAfter) {
|
|
m_newsFilter = enable;
|
|
if(enable) {
|
|
m_newsImpact = MathMin(MathMax(minImpact, 1), 3);
|
|
m_minutesBeforeNews = MathMax(minutesBefore, 1);
|
|
m_minutesAfterNews = MathMax(minutesAfter, 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if a high-impact news event is imminent |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::IsNewsEventImminent() {
|
|
if(!m_newsFilter) return false;
|
|
|
|
// This is a placeholder - implement actual news checking logic
|
|
// using your preferred news feed or calendar
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if high-impact news is pending |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::IsHighImpactNewsPending() {
|
|
if(!m_newsFilter) return false;
|
|
|
|
// This is a placeholder - implement actual news checking logic
|
|
// using your preferred news feed or calendar
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set dynamic risk parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetDynamicRisk(bool enable, double maxDailyDrawdown, double maxPositionRisk,
|
|
double maxPortfolioRisk, double riskDecay) {
|
|
m_useDynamicRisk = enable;
|
|
if(enable) {
|
|
m_maxDailyDrawdown = MathMin(MathMax(maxDailyDrawdown, 1.0), 50.0);
|
|
m_maxPositionRisk = MathMin(MathMax(maxPositionRisk, 0.1), 10.0);
|
|
m_maxPortfolioRisk = MathMin(MathMax(maxPortfolioRisk, 1.0), 50.0);
|
|
m_riskDecayFactor = MathMin(MathMax(riskDecay, 0.5), 0.99);
|
|
|
|
// Initialize equity peak
|
|
if(m_equityPeak == 0) {
|
|
m_equityPeak = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate dynamic risk based on performance |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateDynamicRisk(double baseRisk) {
|
|
if(!m_useDynamicRisk) return baseRisk;
|
|
|
|
// Check drawdown limits
|
|
if(!CheckDrawdownLimits()) {
|
|
return 0.0; // Stop trading if drawdown limit reached
|
|
}
|
|
|
|
// Calculate risk based on performance
|
|
double riskMultiplier = 1.0;
|
|
|
|
// Reduce risk after losses
|
|
double currentDrawdown = GetCurrentDrawdown();
|
|
if(currentDrawdown > 0) {
|
|
riskMultiplier *= MathPow(m_riskDecayFactor, currentDrawdown / (m_maxDailyDrawdown / 2.0));
|
|
}
|
|
|
|
// Adjust based on win rate
|
|
if(m_tradeCount > 10) {
|
|
if(m_winRate < 0.4) {
|
|
riskMultiplier *= 0.5; // Reduce risk if win rate is low
|
|
} else if(m_winRate > 0.6) {
|
|
riskMultiplier *= 1.2; // Slightly increase risk if win rate is high
|
|
}
|
|
}
|
|
|
|
// Apply position and portfolio limits
|
|
double maxRisk = MathMin(m_maxPositionRisk, m_maxPortfolioRisk / (PositionsTotal() + 1));
|
|
return MathMin(baseRisk * riskMultiplier, maxRisk);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if drawdown limits are exceeded |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckDrawdownLimits() {
|
|
if(!m_useDynamicRisk) return true;
|
|
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
m_equityPeak = MathMax(m_equityPeak, currentEquity);
|
|
|
|
double drawdown = 1.0 - (currentEquity / m_equityPeak);
|
|
return (drawdown * 100.0) < m_maxDailyDrawdown;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current drawdown percentage |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetCurrentDrawdown() {
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
m_equityPeak = MathMax(m_equityPeak, currentEquity);
|
|
return (1.0 - (currentEquity / m_equityPeak)) * 100.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set adaptive sizing parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetAdaptiveSizing(bool enable, double initialWinRate, double initialProfitFactor) {
|
|
m_useAdaptiveSizing = enable;
|
|
if(enable) {
|
|
m_winRate = MathMin(MathMax(initialWinRate, 0.01), 0.99);
|
|
m_profitFactor = MathMax(initialProfitFactor, 0.1);
|
|
m_tradeCount = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update trade statistics |
|
|
//+------------------------------------------------------------------+
|
|
void CPositionManager::UpdateTradeStats(bool isWin, double profit) {
|
|
if(!m_useAdaptiveSizing) return;
|
|
|
|
m_tradeCount++;
|
|
|
|
// Update win rate
|
|
double winIncrement = (isWin ? 1.0 : 0.0 - m_winRate) / MathMin(m_tradeCount, 100);
|
|
m_winRate = MathMin(MathMax(m_winRate + winIncrement, 0.01), 0.99);
|
|
|
|
// Update profit factor (simplified)
|
|
static double totalProfit = 0, totalLoss = 0.01; // Avoid division by zero
|
|
if(profit > 0) totalProfit += profit;
|
|
else totalLoss += MathAbs(profit);
|
|
|
|
m_profitFactor = totalProfit / totalLoss;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate adaptive position size |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateAdaptiveVolume(double baseVolume, double confidence = 1.0) {
|
|
if(!m_useAdaptiveSizing || m_tradeCount < 5) return baseVolume;
|
|
|
|
// Kelly criterion for position sizing
|
|
double kelly = CalculateKellyCriterion(m_winRate, m_profitFactor);
|
|
|
|
// Adjust based on confidence
|
|
double adjustedVolume = baseVolume * kelly * confidence;
|
|
|
|
// Apply limits
|
|
double minVolume = m_symbol.LotsMin();
|
|
double maxVolume = m_symbol.LotsMax();
|
|
|
|
return MathMin(MathMax(adjustedVolume, minVolume), maxVolume);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate optimal fraction using Kelly Criterion |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateKellyCriterion(double winRate, double winLossRatio) {
|
|
// Kelly % = W - [(1 - W) / R]
|
|
// Where: W = Win probability, R = Win/loss ratio
|
|
return winRate - ((1.0 - winRate) / winLossRatio);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set trailing drawdown exit |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetTrailingDrawdown(bool enable, double drawdownPercent) {
|
|
m_useTrailingDrawdown = enable;
|
|
if(enable) {
|
|
m_trailingDrawdown = MathMin(MathMax(drawdownPercent, 1.0), 50.0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set volatility-based exit |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetVolatilityExit(bool enable, double volatilityThreshold) {
|
|
m_useVolatilityExit = enable;
|
|
if(enable) {
|
|
m_volatilityExitLevel = MathMax(volatilityThreshold, 0.5);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check advanced exit conditions |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckAdvancedExitConditions(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
// Check trailing drawdown
|
|
if(m_useTrailingDrawdown) {
|
|
double openPrice = m_position.PriceOpen();
|
|
double currentPrice = (m_position.PositionType() == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
|
|
double profit = m_position.Profit() + m_position.Swap() + m_position.Commission();
|
|
|
|
if(profit > 0) {
|
|
double maxProfit = MathMax(m_position.Profit() + m_position.Swap() + m_position.Commission(), 0.01);
|
|
double currentDrawdown = (maxProfit - profit) / maxProfit * 100.0;
|
|
|
|
if(currentDrawdown >= m_trailingDrawdown) {
|
|
return ClosePosition(ticket, "Trailing drawdown hit");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check volatility exit
|
|
if(m_useVolatilityExit) {
|
|
double atr = iATR(m_symbol.Name(), PERIOD_CURRENT, m_volatilityPeriod, 0);
|
|
double atrPct = (atr / m_symbol.Ask()) * 100.0;
|
|
|
|
if(atrPct >= m_volatilityExitLevel) {
|
|
return ClosePosition(ticket, "High volatility exit");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set position clustering |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetPositionClustering(bool enable, double clusterDistance, int maxClusters) {
|
|
m_usePositionClustering = enable;
|
|
if(enable) {
|
|
m_clusterDistance = MathMax(clusterDistance, 1.0) * m_symbol.Point() * 10; // Convert pips to points
|
|
m_maxClusters = MathMax(maxClusters, 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if price is in existing cluster |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::IsInExistingCluster(const string symbol, double price, ENUM_POSITION_TYPE type) {
|
|
if(!m_usePositionClustering) return false;
|
|
|
|
// This is a simplified implementation
|
|
// In a real system, you'd track clusters more precisely
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i) && m_position.Symbol() == symbol &&
|
|
m_position.PositionType() == type) {
|
|
double distance = MathAbs(m_position.PriceOpen() - price) / (m_symbol.Point() * 10);
|
|
if(distance <= m_clusterDistance) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set machine learning integration |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetMLIntegration(bool enable, double confidenceThreshold) {
|
|
m_useMLPredictions = enable;
|
|
if(enable) {
|
|
m_mlConfidenceThreshold = MathMin(MathMax(confidenceThreshold, 0.5), 0.95);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get ML prediction for symbol/timeframe |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetMLPrediction(const string symbol, ENUM_TIMEFRAMES timeframe) {
|
|
if(!m_useMLPredictions) return 0.0;
|
|
|
|
// This is a placeholder - implement actual ML prediction
|
|
// Return values: >0 for buy, <0 for sell, magnitude = confidence
|
|
|
|
return 0.0; // No prediction available
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set market regime filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetRegimeFilter(bool enable, int period, double trendThreshold, double rangeThreshold) {
|
|
m_useRegimeFilter = enable;
|
|
if(enable) {
|
|
m_regimePeriod = MathMax(period, 10);
|
|
m_trendThreshold = MathMin(MathMax(trendThreshold, 0.1), 0.5);
|
|
m_rangeThreshold = MathMin(MathMax(rangeThreshold, 0.05), 0.3);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detect current market regime |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_MARKET_REGIME CPositionManager::DetectMarketRegime() {
|
|
if(!m_useRegimeFilter) return REGIME_TRENDING;
|
|
|
|
// Calculate ADX for trend strength
|
|
double adx = iADX(m_symbol.Name(), PERIOD_CURRENT, m_regimePeriod, PRICE_CLOSE, MODE_MAIN, 0);
|
|
|
|
// Calculate ATR for volatility
|
|
double atr = iATR(m_symbol.Name(), PERIOD_CURRENT, m_regimePeriod, 0);
|
|
double atrPct = (atr / m_symbol.Ask()) * 100.0;
|
|
|
|
// Determine regime
|
|
if(adx > 25) {
|
|
return REGIME_TRENDING;
|
|
} else if(atrPct > m_rangeThreshold * 2) {
|
|
return REGIME_VOLATILE;
|
|
} else {
|
|
return REGIME_RANGING;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if position type is favorable for current regime |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::IsFavorableRegime(ENUM_POSITION_TYPE type, ENUM_MARKET_REGIME regime) {
|
|
if(!m_useRegimeFilter) return true;
|
|
|
|
// This is a simplified implementation
|
|
switch(regime) {
|
|
case REGIME_TRENDING:
|
|
return true; // All positions okay in trends
|
|
case REGIME_RANGING:
|
|
return false; // Avoid new positions in ranging markets
|
|
case REGIME_VOLATILE:
|
|
return (type == POSITION_TYPE_BUY); // Prefer long in high volatility
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate position score based on multiple factors |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculatePositionScore(const string symbol, ENUM_POSITION_TYPE type) {
|
|
double score = 0.0;
|
|
|
|
// 1. Trend strength (higher is better)
|
|
double adx = iADX(symbol, PERIOD_CURRENT, 14, PRICE_CLOSE, MODE_MAIN, 0);
|
|
score += NormalizeDouble(adx / 50.0, 2); // Normalize to 0-1 range
|
|
|
|
// 2. Volatility (moderate is best)
|
|
double atr = iATR(symbol, PERIOD_CURRENT, 14, 0);
|
|
double atrPct = (atr / SymbolInfoDouble(symbol, SYMBOL_ASK)) * 100.0;
|
|
score += 1.0 - MathAbs(0.5 - MathMin(atrPct, 1.0)); // Best at 0.5% ATR
|
|
|
|
// 3. Volume (higher is better)
|
|
double volume[];
|
|
if(CopyTickVolume(symbol, PERIOD_CURRENT, 0, 5, volume) == 5) {
|
|
double avgVolume = (volume[0] + volume[1] + volume[2] + volume[3] + volume[4]) / 5.0;
|
|
score += MathMin(avgVolume / 1000.0, 1.0); // Normalize
|
|
}
|
|
|
|
// 4. ML prediction (if available)
|
|
if(m_useMLPredictions) {
|
|
double mlSignal = GetMLPrediction(symbol, PERIOD_CURRENT);
|
|
if((type == POSITION_TYPE_BUY && mlSignal > 0) ||
|
|
(type == POSITION_TYPE_SELL && mlSignal < 0)) {
|
|
score += MathAbs(mlSignal);
|
|
}
|
|
}
|
|
|
|
// 5. Market regime
|
|
ENUM_MARKET_REGIME regime = DetectMarketRegime();
|
|
if(IsFavorableRegime(type, regime)) {
|
|
score += 0.5;
|
|
}
|
|
|
|
return NormalizeDouble(score, 2);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Place bracket order with advanced features |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::PlaceBracketOrder(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double entryPrice, double sl, double tp, double breakEvenAt,
|
|
double trailingStop, datetime expiration, const string comment) {
|
|
// Implement bracket order logic
|
|
// This would include entry order, stop loss, take profit, and optional break-even/trailing
|
|
|
|
// Placeholder implementation
|
|
return OpenPosition(symbol, (type == ORDER_TYPE_BUY) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL,
|
|
volume, entryPrice, sl, tp, comment);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Place OCO (One-Cancels-Other) orders |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::PlaceOCOOrders(const string symbol, ENUM_ORDER_TYPE type, double volume,
|
|
double price1, double price2, double sl, double tp, const string comment) {
|
|
// Implement OCO order logic
|
|
// This would place two orders where if one is filled, the other is cancelled
|
|
|
|
// Placeholder implementation
|
|
return OpenPosition(symbol, (type == ORDER_TYPE_BUY) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL,
|
|
volume, (type == ORDER_TYPE_BUY) ? price1 : price2, sl, tp, comment + " OCO");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Monitor position and apply advanced management |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::MonitorPosition(ulong ticket) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
// Update position statistics
|
|
UpdatePositionStats(ticket);
|
|
|
|
// Check if position needs adjustment
|
|
if(NeedsAdjustment(ticket)) {
|
|
// Apply adjustments (trailing stop, take profit, etc.)
|
|
return true;
|
|
}
|
|
|
|
// Check advanced exit conditions
|
|
return CheckAdvancedExitConditions(ticket);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update position statistics |
|
|
//+------------------------------------------------------------------+
|
|
void CPositionManager::UpdatePositionStats(ulong ticket) {
|
|
// Update statistics like time in trade, drawdown, etc.
|
|
// This would be called periodically for each open position
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if position needs adjustment |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::NeedsAdjustment(ulong ticket) {
|
|
// Check if position needs any adjustments
|
|
// (e.g., moving stop to break-even, trailing stop, etc.)
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for volatility spikes |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckVolatilitySpike() {
|
|
// Check if current volatility is significantly higher than average
|
|
double currentAtr = iATR(m_symbol.Name(), PERIOD_CURRENT, 14, 0);
|
|
double avgAtr = iATR(m_symbol.Name(), PERIOD_CURRENT, 100, 0);
|
|
|
|
return (currentAtr > avgAtr * 2.0); // 2x average volatility
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check market liquidity |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckLiquidity(const string symbol) {
|
|
// Check if there's sufficient liquidity for the trade
|
|
// This is a simplified check
|
|
|
|
double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * m_symbol.Point();
|
|
double avgSpread = iATR(symbol, PERIOD_CURRENT, 100, 0) / 10.0; // Simplified
|
|
|
|
return (spread <= avgSpread * 2.0); // Allow up to 2x average spread
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current time is within trading hours |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckMarketHours() {
|
|
// Check if current time is within allowed trading hours
|
|
// This is a placeholder - implement based on your trading strategy
|
|
|
|
MqlDateTime time;
|
|
TimeToStruct(TimeCurrent(), time);
|
|
|
|
// Example: Only trade during main session hours (8 AM to 4 PM server time)
|
|
return (time.hour >= 8 && time.hour < 16);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set scaling profile |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetScalingProfile(ENUM_SCALING_PROFILE profile) {
|
|
switch(profile) {
|
|
case SCALING_AGGRESSIVE:
|
|
m_scaleInFactor = 1.5;
|
|
m_scaleOutFactor = 0.5;
|
|
m_scaleInAt = 0.5; // Scale in at 0.5R drawdown
|
|
m_scaleOutAt = 1.5; // Scale out at 1.5R profit
|
|
break;
|
|
|
|
case SCALING_MODERATE:
|
|
m_scaleInFactor = 1.2;
|
|
m_scaleOutFactor = 0.7;
|
|
m_scaleInAt = 0.7;
|
|
m_scaleOutAt = 2.0;
|
|
break;
|
|
|
|
case SCALING_CONSERVATIVE:
|
|
m_scaleInFactor = 1.0;
|
|
m_scaleOutFactor = 1.0;
|
|
m_scaleInAt = 1.0;
|
|
m_scaleOutAt = 3.0;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate optimal scale-in volume |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateOptimalScaleInVolume(double baseVolume, int scaleLevel, double drawdownPct) {
|
|
if(scaleLevel <= 0) return 0.0;
|
|
|
|
// Reduce volume as drawdown increases
|
|
double drawdownFactor = 1.0 - (drawdownPct / 100.0);
|
|
drawdownFactor = MathMax(drawdownFactor, 0.1); // Minimum 10% of base volume
|
|
|
|
// Scale volume based on level and drawdown
|
|
double scaleFactor = MathPow(m_scaleInFactor, scaleLevel) * drawdownFactor;
|
|
|
|
// Apply position limits
|
|
double minVolume = m_symbol.LotsMin();
|
|
double maxVolume = m_symbol.LotsMax();
|
|
double stepVolume = m_symbol.LotsStep();
|
|
|
|
double volume = baseVolume * scaleFactor;
|
|
volume = MathFloor(volume / stepVolume) * stepVolume;
|
|
|
|
return MathMin(MathMax(volume, minVolume), maxVolume);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for scaling opportunities |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckScalingOpportunity(ulong ticket, double &volume) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
double currentPrice = (m_position.PositionType() == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
|
|
double openPrice = m_position.PriceOpen();
|
|
double sl = m_position.StopLoss();
|
|
double tp = m_position.TakeProfit();
|
|
|
|
if(sl == 0.0 || tp == 0.0) return false; // No scaling without proper stops
|
|
|
|
// Calculate price movement in pips
|
|
double priceDiff = (currentPrice - openPrice) / m_symbol.Point();
|
|
if(m_position.PositionType() == POSITION_TYPE_SELL) priceDiff = -priceDiff;
|
|
|
|
// Check if we should scale in
|
|
if(priceDiff <= -m_scaleInAt * 10) { // Convert to pips
|
|
int scaleLevel = (int)(-priceDiff / (m_scaleInAt * 10));
|
|
double drawdownPct = MathAbs(priceDiff / ((openPrice - sl) / m_symbol.Point())) * 100.0;
|
|
|
|
volume = CalculateOptimalScaleInVolume(m_position.Volume(), scaleLevel, drawdownPct);
|
|
return (volume > 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Scale into a position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ScaleInPosition(ulong ticket, double volume) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
string symbol = m_position.Symbol();
|
|
ENUM_POSITION_TYPE type = m_position.PositionType();
|
|
double openPrice = (type == POSITION_TYPE_BUY) ? m_symbol.Ask() : m_symbol.Bid();
|
|
|
|
// Calculate new average price and position size
|
|
double currentVolume = m_position.Volume();
|
|
double currentPrice = m_position.PriceOpen();
|
|
double newVolume = currentVolume + volume;
|
|
double newPrice = (currentPrice * currentVolume + openPrice * volume) / newVolume;
|
|
|
|
// Close existing position
|
|
if(!ClosePosition(ticket, "Scaling in")) return false;
|
|
|
|
// Open new position with updated size and price
|
|
return OpenPosition(symbol, type, newVolume, newPrice, m_position.StopLoss(),
|
|
m_position.TakeProfit(), m_position.Comment() + " Scaled In");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Scale out of a position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::ScaleOutPosition(ulong ticket, double volume) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
double currentVolume = m_position.Volume();
|
|
if(volume >= currentVolume) return ClosePosition(ticket, "Full close");
|
|
|
|
// Close partial position
|
|
if(!ClosePartial(ticket, volume, "Scale out")) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set exit profile |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::SetExitProfile(ENUM_EXIT_PROFILE profile) {
|
|
switch(profile) {
|
|
case EXIT_AGGRESSIVE:
|
|
m_trailingStop = 20; // 20 pips trailing stop
|
|
m_breakEvenAt = 15; // Move to breakeven at 15 pips
|
|
m_useTimeExit = true;
|
|
m_maxHoldBars = 50; // Close after 50 bars
|
|
break;
|
|
|
|
case EXIT_MODERATE:
|
|
m_trailingStop = 30;
|
|
m_breakEvenAt = 20;
|
|
m_useTimeExit = true;
|
|
m_maxHoldBars = 100;
|
|
break;
|
|
|
|
case EXIT_CONSERVATIVE:
|
|
m_trailingStop = 50;
|
|
m_breakEvenAt = 30;
|
|
m_useTimeExit = false;
|
|
m_maxHoldBars = 0; // No time-based exit
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check exit conditions |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::CheckExitConditions(ulong ticket, double &closePrice, string &reason) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
// Check standard exit conditions
|
|
if(CheckAdvancedExitConditions(ticket)) {
|
|
reason = "Advanced exit condition";
|
|
return true;
|
|
}
|
|
|
|
// Check time-based exit
|
|
if(m_useTimeExit && UseTimeExit(ticket, m_position.Time())) {
|
|
reason = "Time-based exit";
|
|
return true;
|
|
}
|
|
|
|
// Check volatility-based exit
|
|
if(m_useVolatilityExit) {
|
|
double atr = iATR(m_symbol.Name(), PERIOD_CURRENT, m_volatilityPeriod, 0);
|
|
double atrPct = (atr / m_symbol.Ask()) * 100.0;
|
|
|
|
if(atrPct >= m_volatilityExitLevel) {
|
|
reason = "High volatility exit";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check pyramiding exit
|
|
double closeVolume = 0.0;
|
|
if(UsePyramidingExit(ticket, closeVolume)) {
|
|
reason = "Pyramiding exit";
|
|
closePrice = (m_position.PositionType() == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Use pyramiding exit strategy |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::UsePyramidingExit(ulong ticket, double &closeVolume) {
|
|
if(!m_position.SelectByTicket(ticket)) return false;
|
|
|
|
// Get all positions for this symbol
|
|
int totalPositions = 0;
|
|
double totalVolume = 0.0;
|
|
double totalProfit = 0.0;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(m_position.SelectByIndex(i) && m_position.Symbol() == m_symbol.Name() &&
|
|
m_position.PositionType() == m_position.PositionType()) {
|
|
totalPositions++;
|
|
totalVolume += m_position.Volume();
|
|
totalProfit += m_position.Profit() + m_position.Swap() + m_position.Commission();
|
|
}
|
|
}
|
|
|
|
// If we have multiple positions and the total profit is positive, consider closing some
|
|
if(totalPositions > 1 && totalProfit > 0) {
|
|
// Close 50% of the smallest positions
|
|
closeVolume = totalVolume * 0.5;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if time-based exit is needed |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::UseTimeExit(ulong ticket, datetime openTime) {
|
|
if(!m_useTimeExit || m_maxHoldBars <= 0) return false;
|
|
|
|
datetime currentTime = TimeCurrent();
|
|
int barsHeld = iBarShift(m_symbol.Name(), PERIOD_CURRENT, openTime, true);
|
|
|
|
return (barsHeld >= m_maxHoldBars);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if volatility-based exit is needed |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::UseVolatilityExit(ulong ticket, double currentVolatility) {
|
|
if(!m_useVolatilityExit) return false;
|
|
|
|
double atr = iATR(m_symbol.Name(), PERIOD_CURRENT, m_volatilityPeriod, 0);
|
|
double atrPct = (atr / m_symbol.Ask()) * 100.0;
|
|
|
|
return (atrPct >= m_volatilityExitLevel);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate correlation-adjusted volume |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::CalculateCorrelationAdjustedVolume(const string symbol, double baseVolume) {
|
|
if(!m_initialized) return baseVolume;
|
|
|
|
// Get correlation with existing portfolio
|
|
double correlation = GetPortfolioCorrelation(symbol);
|
|
|
|
// Reduce position size for highly correlated positions
|
|
if(correlation > 0.7) {
|
|
return baseVolume * (1.0 - (correlation - 0.7) * 0.5);
|
|
}
|
|
|
|
return baseVolume;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get portfolio correlation for a symbol |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetPortfolioCorrelation(const string symbol) {
|
|
if(PositionsTotal() == 0) return 0.0;
|
|
|
|
double totalCorrelation = 0.0;
|
|
int count = 0;
|
|
|
|
// Calculate average correlation with existing positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
string posSymbol = m_position.Symbol();
|
|
if(posSymbol != symbol) {
|
|
double corr = GetMarketCorrelation(symbol, posSymbol);
|
|
totalCorrelation += MathAbs(corr);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (count > 0) ? (totalCorrelation / count) : 0.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get market correlation between two symbols |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetMarketCorrelation(const string symbol1, const string symbol2, int period = 100) {
|
|
if(symbol1 == symbol2) return 1.0;
|
|
|
|
// Get close prices for both symbols
|
|
double prices1[], prices2[];
|
|
ArraySetAsSeries(prices1, true);
|
|
ArraySetAsSeries(prices2, true);
|
|
|
|
if(CopyClose(symbol1, PERIOD_CURRENT, 0, period, prices1) != period ||
|
|
CopyClose(symbol2, PERIOD_CURRENT, 0, period, prices2) != period) {
|
|
Print("Error getting price data for correlation");
|
|
return 0.0;
|
|
}
|
|
|
|
// Calculate correlation coefficient
|
|
return MathCorrelation(prices1, prices2, period);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Load machine learning model |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::LoadMLModel(const string modelFile) {
|
|
// Placeholder for ML model loading
|
|
// In a real implementation, this would load a pre-trained model
|
|
|
|
Print("Loading ML model: ", modelFile);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get ML-based position score |
|
|
//+------------------------------------------------------------------+
|
|
double CPositionManager::GetMLPositionScore(const string symbol, ENUM_POSITION_TYPE type) {
|
|
if(!m_useMLPredictions) return 0.0;
|
|
|
|
// Placeholder for ML prediction
|
|
// In a real implementation, this would use the loaded model to predict
|
|
|
|
// Return a random score for demonstration
|
|
return MathRand() / 32767.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update ML model with new data |
|
|
//+------------------------------------------------------------------+
|
|
bool CPositionManager::UpdateMLModel(const MqlRates &rates[], const double &features[]) {
|
|
if(!m_useMLPredictions) return false;
|
|
|
|
// Placeholder for model updating
|
|
// In a real implementation, this would update the model with new data
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update position manager state |
|
|
//+------------------------------------------------------------------+
|
|
void CPositionManager::Update() {
|
|
// Update trailing stops if enabled
|
|
if(m_trailingStop > 0) {
|
|
CheckTrailingStop();
|
|
}
|
|
|
|
// Update break-even if enabled
|
|
if(m_autoBreakEven) {
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
double profit = m_position.Profit() + m_position.Swap() + m_position.Commission();
|
|
double openPrice = m_position.PriceOpen();
|
|
double currentPrice = (m_position.PositionType() == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
|
|
|
|
// Check if we should move to break-even
|
|
if((m_position.PositionType() == POSITION_TYPE_BUY &&
|
|
currentPrice - openPrice >= m_breakEvenAt) ||
|
|
(m_position.PositionType() == POSITION_TYPE_SELL &&
|
|
openPrice - currentPrice >= m_breakEvenAt)) {
|
|
MoveToBreakEven(m_position.Ticket());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check time-based exits
|
|
if(m_useTimeExit) {
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
// Check position age in bars if enabled
|
|
if(m_maxHoldBars > 0) {
|
|
int barsHeld = (int)((currentTime - m_position.Time()) / PeriodSeconds(PERIOD_CURRENT));
|
|
if(barsHeld >= m_maxHoldBars) {
|
|
ClosePosition(m_position.Ticket(), "Max hold time reached");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check expiry time if set
|
|
if(m_expiryTime > 0 && currentTime >= m_expiryTime) {
|
|
ClosePosition(m_position.Ticket(), "Position expired");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check scaling opportunities
|
|
if(m_scaleIn || m_scaleOut) {
|
|
for(int i = 0; i < PositionsTotal(); i++) {
|
|
if(m_position.SelectByIndex(i)) {
|
|
double openPrice = m_position.PriceOpen();
|
|
double currentPrice = (m_position.PositionType() == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
|
|
double priceDiff = MathAbs(currentPrice - openPrice);
|
|
|
|
// Check for scale-in opportunities
|
|
if(m_scaleIn && priceDiff >= m_scaleInAt) {
|
|
ScaleInPosition(m_position.Ticket());
|
|
}
|
|
|
|
// Check for scale-out opportunities
|
|
if(m_scaleOut && priceDiff >= m_scaleOutAt) {
|
|
ScaleOutPosition(m_position.Ticket());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|