| //+------------------------------------------------------------------+
|
| //| PositionReviewer.mqh |
|
| //| Purpose: Continuous position re-evaluation and adaptive mgmt |
|
| //| Features: Review open positions every 5 min, close/adjust |
|
| //+------------------------------------------------------------------+
|
| #ifndef __POSITIONREVIEWER_MQH__
|
| #define __POSITIONREVIEWER_MQH__
|
|
|
| #include "AdaptiveSignalOptimizer.mqh"
|
| #include "GateSystemAutoLearning.mqh"
|
| #include <Trade/Trade.mqh>
|
|
|
| // Position review decision
|
| enum PositionAction
|
| {
|
| PA_HOLD, // Keep position as-is
|
| PA_CLOSE, // Close position immediately
|
| PA_ADJUST_SL, // Tighten/widen stop loss
|
| PA_ADJUST_TP, // Adjust take profit
|
| PA_SCALE_OUT_HALF, // Close 50% of position
|
| PA_REVERSE // Close and open opposite position
|
| };
|
|
|
| // Review result
|
| struct PositionReview
|
| {
|
| string position_id;
|
| datetime review_time;
|
| PositionAction action;
|
| string reason;
|
| double current_pnl;
|
| double current_pnl_pct;
|
|
|
| // Signal re-evaluation
|
| double new_signal_confidence;
|
| string new_signal_direction; // "same", "opposite", "neutral"
|
|
|
| // Market condition changes
|
| string current_regime;
|
| double current_volatility;
|
| bool conditions_deteriorated;
|
|
|
| // Adjustment details
|
| double new_sl;
|
| double new_tp;
|
| double close_volume;
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Position Reviewer - Living Trade Management |
|
| //+------------------------------------------------------------------+
|
| class CPositionReviewer
|
| {
|
| private:
|
| CEfficientGateManagerEnhanced* m_gate_manager;
|
| CAdaptiveSignalOptimizer* m_optimizer;
|
|
|
| int m_review_interval_seconds;
|
| datetime m_last_review_time;
|
|
|
| // Statistics
|
| int m_total_reviews;
|
| int m_positions_closed;
|
| int m_positions_adjusted;
|
| int m_positions_held;
|
|
|
| // Review thresholds
|
| double m_min_pnl_to_close; // Close if profit exceeds this
|
| double m_max_loss_to_close; // Close if loss exceeds this
|
| double m_signal_reversal_threshold; // Close if confidence flips
|
|
|
| // Evaluate current market conditions for a position
|
| bool EvaluateMarketConditions(const string symbol, const int timeframe,
|
| string ®ime, double &volatility)
|
| {
|
| // Get current regime
|
| regime = GetMarketRegimeForReview(symbol, timeframe);
|
|
|
| // Get current volatility
|
| int atr_handle = iATR(symbol, (ENUM_TIMEFRAMES)timeframe, 14);
|
| double atr_array[1];
|
| if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) != 1)
|
| return false;
|
|
|
| double price = 0;
|
| if(!SymbolInfoDouble(symbol, SYMBOL_BID, price))
|
| return false;
|
|
|
| volatility = atr_array[0] / price;
|
| return true;
|
| }
|
|
|
| // Simple regime detection for review
|
| string GetMarketRegimeForReview(const string symbol, const int timeframe)
|
| {
|
| int adx_handle = iADX(symbol, (ENUM_TIMEFRAMES)timeframe, 14);
|
| double adx_array[1];
|
|
|
| if(CopyBuffer(adx_handle, 0, 0, 1, adx_array) == 1)
|
| {
|
| double adx = adx_array[0];
|
| if(adx > 25.0)
|
| return "trending";
|
| else
|
| return "ranging";
|
| }
|
|
|
| return "unknown";
|
| }
|
|
|
| public:
|
| CPositionReviewer(CEfficientGateManagerEnhanced* gate_mgr, CAdaptiveSignalOptimizer* optimizer,
|
| int review_interval_seconds = 300)
|
| {
|
| m_gate_manager = gate_mgr;
|
| m_optimizer = optimizer;
|
| m_review_interval_seconds = review_interval_seconds;
|
| m_last_review_time = 0;
|
|
|
| // Initialize statistics
|
| m_total_reviews = 0;
|
| m_positions_closed = 0;
|
| m_positions_adjusted = 0;
|
| m_positions_held = 0;
|
|
|
| // Default thresholds
|
| m_min_pnl_to_close = 2.0; // Close if +2% profit
|
| m_max_loss_to_close = -1.0; // Close if -1% loss
|
| m_signal_reversal_threshold = 0.4; // Close if confidence drops below 0.4
|
|
|
| PrintFormat("🔄 PositionReviewer initialized: Review every %d seconds (%.1f min)",
|
| m_review_interval_seconds, m_review_interval_seconds / 60.0);
|
| }
|
|
|
| // Check if it's time to review positions
|
| bool IsReviewTime()
|
| {
|
| datetime now = TimeCurrent();
|
| if(now - m_last_review_time >= m_review_interval_seconds)
|
| {
|
| m_last_review_time = now;
|
| return true;
|
| }
|
| return false;
|
| }
|
|
|
| // NOTE: ReviewPaperPosition removed - now using real MT5 positions
|
| // Use PositionGetDouble(), PositionSelectByTicket() instead
|
| /*
|
| PositionReview ReviewPaperPosition_DEPRECATED(void* position,
|
| IStrategy* strategy,
|
| const string strategy_name)
|
| {
|
| PositionReview review;
|
| review.position_id = IntegerToString(position.ticket);
|
| review.review_time = TimeCurrent();
|
| review.action = PA_HOLD; // Default action
|
| review.reason = "Position OK";
|
|
|
| m_total_reviews++;
|
|
|
| // Calculate current PnL
|
| double current_price = 0;
|
| if(position.type == ORDER_TYPE_BUY)
|
| SymbolInfoDouble(position.symbol, SYMBOL_BID, current_price);
|
| else
|
| SymbolInfoDouble(position.symbol, SYMBOL_ASK, current_price);
|
|
|
| double pnl = (position.type == ORDER_TYPE_BUY) ?
|
| (current_price - position.open_price) * position.volume :
|
| (position.open_price - current_price) * position.volume;
|
|
|
| double pnl_pct = (pnl / (position.open_price * position.volume)) * 100.0;
|
|
|
| review.current_pnl = pnl;
|
| review.current_pnl_pct = pnl_pct;
|
|
|
| // Evaluate market conditions
|
| string current_regime;
|
| double current_volatility;
|
| EvaluateMarketConditions(position.symbol, position.timeframe,
|
| current_regime, current_volatility);
|
|
|
| review.current_regime = current_regime;
|
| review.current_volatility = current_volatility;
|
|
|
| // DECISION LOGIC
|
|
|
| // 1. Profit target reached?
|
| if(pnl_pct >= m_min_pnl_to_close)
|
| {
|
| review.action = PA_CLOSE;
|
| review.reason = StringFormat("Profit target reached: %.2f%%", pnl_pct);
|
| m_positions_closed++;
|
| return review;
|
| }
|
|
|
| // 2. Stop loss hit?
|
| if(pnl_pct <= m_max_loss_to_close)
|
| {
|
| review.action = PA_CLOSE;
|
| review.reason = StringFormat("Stop loss hit: %.2f%%", pnl_pct);
|
| m_positions_closed++;
|
| return review;
|
| }
|
|
|
| // 3. Re-evaluate signal using strategy
|
| if(strategy != NULL)
|
| {
|
| TradingSignal new_signal;
|
| new_signal.Init(); // CRITICAL: Initialize all fields to safe defaults
|
| new_signal.symbol = position.symbol;
|
| new_signal.timeframe = position.timeframe;
|
| new_signal.timestamp = TimeCurrent();
|
|
|
| // Generate fresh signal
|
| bool should_trade = false;
|
| int signal_type = -1;
|
| double confidence = 0.0;
|
|
|
| should_trade = strategy.ShouldTrade(signal_type, confidence);
|
|
|
| review.new_signal_confidence = confidence;
|
|
|
| if(!should_trade || confidence < m_signal_reversal_threshold)
|
| {
|
| review.action = PA_CLOSE;
|
| review.reason = StringFormat("Signal confidence dropped: %.2f (was: %.2f)",
|
| confidence, position.confidence);
|
| review.new_signal_direction = "weak";
|
| m_positions_closed++;
|
| return review;
|
| }
|
|
|
| // Check if signal reversed direction
|
| bool signal_reversed = false;
|
| if(position.type == ORDER_TYPE_BUY && signal_type == 1) // Was buy, now sell
|
| signal_reversed = true;
|
| if(position.type == ORDER_TYPE_SELL && signal_type == 0) // Was sell, now buy
|
| signal_reversed = true;
|
|
|
| if(signal_reversed)
|
| {
|
| review.action = PA_CLOSE;
|
| review.reason = "Signal reversed direction";
|
| review.new_signal_direction = "opposite";
|
| m_positions_closed++;
|
| return review;
|
| }
|
|
|
| review.new_signal_direction = "same";
|
| }
|
|
|
| // 4. Volatility spike?
|
| if(current_volatility > 0.03) // 3% volatility
|
| {
|
| review.action = PA_ADJUST_SL;
|
| review.reason = "High volatility detected, tightening stop";
|
|
|
| // Tighten stop loss by 20%
|
| double sl_distance = MathAbs(current_price - position.sl);
|
| review.new_sl = (position.type == ORDER_TYPE_BUY) ?
|
| current_price - (sl_distance * 0.8) :
|
| current_price + (sl_distance * 0.8);
|
| review.new_tp = position.tp;
|
|
|
| m_positions_adjusted++;
|
| return review;
|
| }
|
|
|
| // 5. Regime change?
|
| if(review.current_regime != position.regime && review.current_regime != "unknown")
|
| {
|
| // Regime changed - adjust expectations
|
| if(current_regime == "ranging" && pnl_pct > 0.5)
|
| {
|
| review.action = PA_SCALE_OUT_HALF;
|
| review.reason = "Regime changed to ranging, taking partial profit";
|
| review.close_volume = position.volume * 0.5;
|
| m_positions_adjusted++;
|
| return review;
|
| }
|
| }
|
|
|
| // 6. Position holding well - trail stop if in profit
|
| if(pnl_pct > 1.0) // 1% profit
|
| {
|
| review.action = PA_ADJUST_SL;
|
| review.reason = StringFormat("Trailing stop - profit: %.2f%%", pnl_pct);
|
|
|
| // Trail at 50% of current profit
|
| double trail_distance = MathAbs(current_price - position.open_price) * 0.5;
|
| review.new_sl = (position.type == ORDER_TYPE_BUY) ?
|
| current_price - trail_distance :
|
| current_price + trail_distance;
|
| review.new_tp = position.tp;
|
|
|
| m_positions_adjusted++;
|
| return review;
|
| }
|
|
|
| // Default: HOLD
|
| m_positions_held++;
|
| review.action = PA_HOLD;
|
| review.reason = StringFormat("Position healthy: PnL=%.2f%%, Conf=%.2f",
|
| pnl_pct, review.new_signal_confidence);
|
|
|
| return review;
|
| }
|
| */ // End of deprecated CPaperPosition functions
|
|
|
| // NOTE: ApplyReviewDecision removed - now using real MT5 position modifications
|
| // Use TradeManager::ModifyPosition() instead
|
| /*
|
| bool ApplyReviewDecision_DEPRECATED(void* position, const PositionReview &review)
|
| {
|
| if(position == NULL) return false;
|
|
|
| switch(review.action)
|
| {
|
| case PA_CLOSE:
|
| // Close position
|
| position.status = "closed";
|
| position.close_time = TimeCurrent();
|
|
|
| double close_price = 0;
|
| if(position.type == ORDER_TYPE_BUY)
|
| SymbolInfoDouble(position.symbol, SYMBOL_BID, close_price);
|
| else
|
| SymbolInfoDouble(position.symbol, SYMBOL_ASK, close_price);
|
|
|
| position.close_price = close_price;
|
| position.pnl = review.current_pnl;
|
|
|
| PrintFormat("🔴 CLOSED Position #%d: %s (PnL: %.2f%%)",
|
| position.ticket, review.reason, review.current_pnl_pct);
|
| return true;
|
|
|
| case PA_ADJUST_SL:
|
| position.sl = review.new_sl;
|
| PrintFormat("📊 ADJUSTED SL Position #%d: %.5f → %.5f (%s)",
|
| position.ticket, position.sl, review.new_sl, review.reason);
|
| return true;
|
|
|
| case PA_ADJUST_TP:
|
| position.tp = review.new_tp;
|
| PrintFormat("📊 ADJUSTED TP Position #%d: %.5f → %.5f (%s)",
|
| position.ticket, position.tp, review.new_tp, review.reason);
|
| return true;
|
|
|
| case PA_SCALE_OUT_HALF:
|
| position.volume *= 0.5;
|
| PrintFormat("📉 SCALED OUT 50%% Position #%d: %s",
|
| position.ticket, review.reason);
|
| return true;
|
|
|
| case PA_HOLD:
|
| // Do nothing
|
| return true;
|
|
|
| default:
|
| return false;
|
| }
|
| }
|
| */ // End of deprecated ApplyReviewDecision
|
|
|
| // Get statistics
|
| void GetStatistics(int &total, int &closed, int &adjusted, int &held)
|
| {
|
| total = m_total_reviews;
|
| closed = m_positions_closed;
|
| adjusted = m_positions_adjusted;
|
| held = m_positions_held;
|
| }
|
|
|
| // Print review report
|
| void PrintReport()
|
| {
|
| PrintFormat("\n=== 🔄 Position Review Report ===");
|
| PrintFormat("Total Reviews: %d", m_total_reviews);
|
| PrintFormat("Positions Closed: %d (%.1f%%)", m_positions_closed,
|
| m_total_reviews > 0 ? (double)m_positions_closed/m_total_reviews*100 : 0);
|
| PrintFormat("Positions Adjusted: %d (%.1f%%)", m_positions_adjusted,
|
| m_total_reviews > 0 ? (double)m_positions_adjusted/m_total_reviews*100 : 0);
|
| PrintFormat("Positions Held: %d (%.1f%%)", m_positions_held,
|
| m_total_reviews > 0 ? (double)m_positions_held/m_total_reviews*100 : 0);
|
| PrintFormat("================================\n");
|
| }
|
|
|
| // Configuration methods
|
| void SetProfitTarget(double pct) { m_min_pnl_to_close = pct; }
|
| void SetStopLoss(double pct) { m_max_loss_to_close = pct; }
|
| void SetSignalThreshold(double threshold) { m_signal_reversal_threshold = threshold; }
|
| };
|
|
|
| #endif
|