2025-10-03 01:38:36 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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"
|
2026-02-05 23:31:20 -05:00 | | | #include "GateSystemAutoLearning.mqh"
|
2025-10-03 01:38:36 -04:00 | | | #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:
|
2026-02-05 23:31:20 -05:00 | | | CEfficientGateManagerEnhanced* m_gate_manager;
|
2025-10-03 01:38:36 -04:00 | | | 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:
|
2026-02-05 23:31:20 -05:00 | | | CPositionReviewer(CEfficientGateManagerEnhanced* gate_mgr, CAdaptiveSignalOptimizer* optimizer,
|
2025-10-03 01:38:36 -04:00 | | | 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;
|
| | | }
|
| | |
|
2025-10-16 18:03:12 -04:00 | | | // NOTE: ReviewPaperPosition removed - now using real MT5 positions
|
| | | // Use PositionGetDouble(), PositionSelectByTicket() instead
|
| | | /*
|
| | | PositionReview ReviewPaperPosition_DEPRECATED(void* position,
|
2025-10-03 01:38:36 -04:00 | | | 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;
|
2026-01-14 13:37:28 -05:00 | | | new_signal.Init(); // CRITICAL: Initialize all fields to safe defaults
|
2025-10-03 01:38:36 -04:00 | | | 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;
|
| | | }
|
2025-10-16 18:03:12 -04:00 | | | */ // End of deprecated CPaperPosition functions
|
2025-10-03 01:38:36 -04:00 | | |
|
2025-10-16 18:03:12 -04:00 | | | // NOTE: ApplyReviewDecision removed - now using real MT5 position modifications
|
| | | // Use TradeManager::ModifyPosition() instead
|
| | | /*
|
| | | bool ApplyReviewDecision_DEPRECATED(void* position, const PositionReview &review)
|
2025-10-03 01:38:36 -04:00 | | | {
|
| | | 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;
|
| | | }
|
| | | }
|
2025-10-16 18:03:12 -04:00 | | | */ // End of deprecated ApplyReviewDecision
|
2025-10-03 01:38:36 -04:00 | | |
|
| | | // 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
|