396 linhas
13 KiB
MQL5
396 linhas
13 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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
|