| //+------------------------------------------------------------------+
|
| //| 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());
|
| }
|
| }
|
| }
|
| }
|
| }
|
| //+------------------------------------------------------------------+
|