mql5/Experts/Advisors/Modules/TradeManager.mqh

767 lines
25 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| TradeManager.mqh |
//| Trade Management Module |
//+------------------------------------------------------------------+
#ifndef TRADE_MANAGER_MQH
#define TRADE_MANAGER_MQH
#include "DataTypes.mqh"
#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
//+------------------------------------------------------------------+
//| Trade Manager Class |
//+------------------------------------------------------------------+
class CTradeManager
{
private:
//--- Configuration
TradeManagerConfig m_config;
int m_magic_number;
//--- Trade operations
CTrade m_trade;
CSymbolInfo m_symbol;
//--- Partial close levels
PartialCloseLevel m_partial_levels[10];
int m_partial_count;
//--- Helper methods
bool ParsePartialLevels(string levels_string);
bool CheckBreakeven(ManagedTrade &trade, double current_price);
bool CheckTrailingStop(ManagedTrade &trade, double current_price);
bool CheckPartialClose(ManagedTrade &trade, double current_price);
bool CheckTimeBasedExit(ManagedTrade &trade);
double CalculateDynamicTP(ManagedTrade &trade, const MarketConditions &market);
double CalculateDynamicSL(ManagedTrade &trade, const MarketConditions &market);
double GetMinStopDistance();
bool ValidateStopLevels(double price, double sl, double tp);
public:
CTradeManager();
~CTradeManager();
//--- Initialization
bool Initialize(const TradeManagerConfig &config, int magic);
//--- Trade operations
ulong OpenPosition(const TradeRequest &request);
bool ClosePosition(ulong ticket, double volume = 0);
bool ModifyPosition(ulong ticket, double sl, double tp);
//--- Trade management
void ManageTrade(ManagedTrade &trade, const MarketConditions &market);
void ApplyBreakeven(ManagedTrade &trade);
void ApplyTrailingStop(ManagedTrade &trade);
void ApplyPartialClose(ManagedTrade &trade);
void ApplyDynamicTargets(ManagedTrade &trade, const MarketConditions &market);
//--- External trade management
void ApplyDefaultStops(ManagedTrade &trade, double atr_value);
bool ForceStopLoss(ManagedTrade &trade, double stop_distance);
bool ForceTakeProfit(ManagedTrade &trade, double tp_distance);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTradeManager::CTradeManager()
{
m_magic_number = 0;
m_partial_count = 0;
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CTradeManager::~CTradeManager()
{
}
//+------------------------------------------------------------------+
//| Initialize trade manager |
//+------------------------------------------------------------------+
bool CTradeManager::Initialize(const TradeManagerConfig &config, int magic)
{
m_config = config;
m_magic_number = magic;
//--- Initialize trade object
m_trade.SetExpertMagicNumber(magic);
m_trade.SetDeviationInPoints(10);
m_trade.SetTypeFilling(ORDER_FILLING_RETURN);
m_trade.SetAsyncMode(false);
//--- Initialize symbol
if(!m_symbol.Name(_Symbol))
{
Print("TradeManager: Failed to initialize symbol");
return false;
}
//--- Parse partial close levels
if(m_config.enable_partial)
{
if(!ParsePartialLevels(m_config.partial_levels))
{
Print("TradeManager: Failed to parse partial levels");
m_config.enable_partial = false;
}
}
Print("TradeManager initialized: TP=", EnumToString(m_config.tp_mode),
", SL=", EnumToString(m_config.sl_mode));
return true;
}
//+------------------------------------------------------------------+
//| Open new position |
//+------------------------------------------------------------------+
ulong CTradeManager::OpenPosition(const TradeRequest &request)
{
//--- Refresh symbol data
m_symbol.RefreshRates();
//--- Calculate actual prices
double price = 0;
double sl = 0;
double tp = 0;
if(request.type == ORDER_TYPE_BUY)
{
price = m_symbol.Ask();
if(request.sl_distance > 0)
sl = NormalizeDouble(price - request.sl_distance, m_symbol.Digits());
if(request.tp_distance > 0)
tp = NormalizeDouble(price + request.tp_distance, m_symbol.Digits());
}
else if(request.type == ORDER_TYPE_SELL)
{
price = m_symbol.Bid();
if(request.sl_distance > 0)
sl = NormalizeDouble(price + request.sl_distance, m_symbol.Digits());
if(request.tp_distance > 0)
tp = NormalizeDouble(price - request.tp_distance, m_symbol.Digits());
}
else
{
Print("TradeManager: Invalid order type");
return 0;
}
//--- Validate stop levels
if(!ValidateStopLevels(price, sl, tp))
{
Print("TradeManager: Invalid stop levels");
return 0;
}
//--- Open position
bool result = false;
if(request.type == ORDER_TYPE_BUY)
result = m_trade.Buy(request.volume, request.symbol, price, sl, tp, request.comment);
else
result = m_trade.Sell(request.volume, request.symbol, price, sl, tp, request.comment);
if(result)
{
ulong ticket = m_trade.ResultOrder();
Print("TradeManager: Opened ", (request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL",
" position #", ticket, " at ", price);
return ticket;
}
else
{
Print("TradeManager: Failed to open position - ", m_trade.ResultComment());
return 0;
}
}
//+------------------------------------------------------------------+
//| Close position |
//+------------------------------------------------------------------+
bool CTradeManager::ClosePosition(ulong ticket, double volume)
{
if(volume == 0)
{
//--- Full close
if(m_trade.PositionClose(ticket))
{
Print("TradeManager: Closed position #", ticket);
return true;
}
}
else
{
//--- Partial close
if(m_trade.PositionClosePartial(ticket, volume))
{
Print("TradeManager: Partially closed ", volume, " lots of position #", ticket);
return true;
}
}
Print("TradeManager: Failed to close position #", ticket, " - ", m_trade.ResultComment());
return false;
}
//+------------------------------------------------------------------+
//| Modify position stops |
//+------------------------------------------------------------------+
bool CTradeManager::ModifyPosition(ulong ticket, double sl, double tp)
{
if(m_trade.PositionModify(ticket, sl, tp))
{
Print("TradeManager: Modified position #", ticket, " SL=", sl, " TP=", tp);
return true;
}
Print("TradeManager: Failed to modify position #", ticket, " - ", m_trade.ResultComment());
return false;
}
//+------------------------------------------------------------------+
//| Main trade management function |
//+------------------------------------------------------------------+
void CTradeManager::ManageTrade(ManagedTrade &trade, const MarketConditions &market)
{
//--- Get current price
m_symbol.RefreshRates();
double current_price = (trade.type == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
//--- Force stops on external trades if configured
if(trade.magic != m_magic_number)
{
if(m_config.force_sl && trade.sl == 0)
{
double atr = market.volatility;
ForceStopLoss(trade, atr * 2.0);
}
if(m_config.force_tp && trade.tp == 0)
{
double atr = market.volatility;
ForceTakeProfit(trade, atr * 3.0);
}
}
//--- Apply management strategies
if(m_config.enable_breakeven && !trade.be_activated)
{
CheckBreakeven(trade, current_price);
}
if(m_config.enable_trailing && trade.be_activated)
{
CheckTrailingStop(trade, current_price);
}
if(m_config.enable_partial && trade.partial_count < m_partial_count)
{
CheckPartialClose(trade, current_price);
}
//--- Apply dynamic targets based on TP mode
if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_HYBRID)
{
ApplyDynamicTargets(trade, market);
}
//--- Check time-based exits
if(m_config.tp_mode == TP_TIME_BASED)
{
CheckTimeBasedExit(trade);
}
//--- Update bars in trade
trade.bars_in_trade = iBars(_Symbol, PERIOD_CURRENT) -
iBarShift(_Symbol, PERIOD_CURRENT, trade.open_time);
}
//+------------------------------------------------------------------+
//| Check and apply breakeven |
//+------------------------------------------------------------------+
bool CTradeManager::CheckBreakeven(ManagedTrade &trade, double current_price)
{
if(trade.risk_amount <= 0) return false;
//--- Calculate breakeven trigger price
double trigger_distance = MathAbs(trade.open_price - trade.sl) * m_config.breakeven_trigger;
double trigger_price;
if(trade.type == POSITION_TYPE_BUY)
trigger_price = trade.open_price + trigger_distance;
else
trigger_price = trade.open_price - trigger_distance;
//--- Check if price reached trigger
bool triggered = false;
if(trade.type == POSITION_TYPE_BUY && current_price >= trigger_price)
triggered = true;
else if(trade.type == POSITION_TYPE_SELL && current_price <= trigger_price)
triggered = true;
if(triggered)
{
//--- Calculate new stop loss (breakeven + buffer)
double buffer = m_symbol.Point() * 10; // 10 point buffer
double new_sl;
if(trade.type == POSITION_TYPE_BUY)
new_sl = trade.open_price + buffer;
else
new_sl = trade.open_price - buffer;
//--- Ensure new SL is better than current
if((trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) ||
(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl))
{
if(ModifyPosition(trade.ticket, new_sl, trade.tp))
{
trade.sl = new_sl;
trade.be_activated = true;
Print("TradeManager: Breakeven activated for #", trade.ticket);
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Check and apply trailing stop |
//+------------------------------------------------------------------+
bool CTradeManager::CheckTrailingStop(ManagedTrade &trade, double current_price)
{
//--- Calculate trailing distance based on SL mode
double trail_distance = 0;
switch(m_config.sl_mode)
{
case SL_TRAILING_ATR:
trail_distance = iATR(_Symbol, PERIOD_CURRENT, 14) * 1.5;
break;
case SL_PARABOLIC_SAR:
{
double sar_buffer[1];
int sar_handle = iSAR(_Symbol, PERIOD_CURRENT, 0.02, 0.2);
if(CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0)
{
if(trade.type == POSITION_TYPE_BUY)
trail_distance = current_price - sar_buffer[0];
else
trail_distance = sar_buffer[0] - current_price;
}
IndicatorRelease(sar_handle);
break;
}
default:
trail_distance = MathAbs(trade.open_price - trade.sl) * 0.5;
break;
}
if(trail_distance <= 0) return false;
//--- Calculate new stop loss
double new_sl;
if(trade.type == POSITION_TYPE_BUY)
new_sl = current_price - trail_distance;
else
new_sl = current_price + trail_distance;
//--- Ensure new SL is better than current
if((trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) ||
(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl))
{
//--- Ensure minimum stop distance
double min_stop = GetMinStopDistance();
double actual_distance = MathAbs(current_price - new_sl);
if(actual_distance >= min_stop)
{
if(ModifyPosition(trade.ticket, new_sl, trade.tp))
{
trade.sl = new_sl;
trade.trailing_activated = true;
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Check and apply partial close |
//+------------------------------------------------------------------+
bool CTradeManager::CheckPartialClose(ManagedTrade &trade, double current_price)
{
//--- Check each partial level
for(int i = 0; i < m_partial_count; i++)
{
if(m_partial_levels[i].executed) continue;
//--- Calculate target price
double target_distance = MathAbs(trade.open_price - trade.sl) * m_partial_levels[i].r_multiple;
double target_price;
if(trade.type == POSITION_TYPE_BUY)
target_price = trade.open_price + target_distance;
else
target_price = trade.open_price - target_distance;
//--- Check if target reached
bool reached = false;
if(trade.type == POSITION_TYPE_BUY && current_price >= target_price)
reached = true;
else if(trade.type == POSITION_TYPE_SELL && current_price <= target_price)
reached = true;
if(reached)
{
//--- Calculate volume to close
double close_volume = trade.current_volume * (m_partial_levels[i].close_percent / 100.0);
close_volume = NormalizeDouble(close_volume, 2);
//--- Ensure minimum lot size
double min_lot = m_symbol.LotsMin();
if(close_volume >= min_lot && trade.current_volume - close_volume >= min_lot)
{
if(ClosePosition(trade.ticket, close_volume))
{
m_partial_levels[i].executed = true;
trade.partial_count++;
trade.current_volume -= close_volume;
Print("TradeManager: Partial close ", close_volume,
" lots at ", m_partial_levels[i].r_multiple, "R for #", trade.ticket);
return true;
}
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Apply dynamic targets based on market conditions |
//+------------------------------------------------------------------+
void CTradeManager::ApplyDynamicTargets(ManagedTrade &trade, const MarketConditions &market)
{
bool need_modify = false;
double new_sl = trade.sl;
double new_tp = trade.tp;
//--- Dynamic stop loss
if(m_config.sl_mode == SL_DYNAMIC)
{
new_sl = CalculateDynamicSL(trade, market);
if(new_sl != trade.sl)
need_modify = true;
}
//--- Dynamic take profit
if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_VOLATILITY)
{
new_tp = CalculateDynamicTP(trade, market);
if(new_tp != trade.tp)
need_modify = true;
}
//--- Apply modifications if needed
if(need_modify && ValidateStopLevels(trade.open_price, new_sl, new_tp))
{
if(ModifyPosition(trade.ticket, new_sl, new_tp))
{
trade.sl = new_sl;
trade.tp = new_tp;
}
}
}
//+------------------------------------------------------------------+
//| Calculate dynamic take profit |
//+------------------------------------------------------------------+
double CTradeManager::CalculateDynamicTP(ManagedTrade &trade, const MarketConditions &market)
{
double tp_distance = 0;
switch(m_config.tp_mode)
{
case TP_VOLATILITY:
{
//--- ATR-based dynamic TP
double atr_multiplier = 3.0;
if(market.condition == MARKET_VOLATILE)
atr_multiplier = 4.0; // Wider TP in volatile markets
else if(market.condition == MARKET_QUIET)
atr_multiplier = 2.0; // Tighter TP in quiet markets
tp_distance = market.volatility * atr_multiplier;
break;
}
case TP_ADAPTIVE:
{
//--- Adaptive TP based on multiple factors
double base_distance = MathAbs(trade.open_price - trade.sl) * 2.0;
//--- Adjust for market conditions
if(market.condition == MARKET_TRENDING_UP && trade.type == POSITION_TYPE_BUY)
base_distance *= 1.5; // Extend TP in trending market
else if(market.condition == MARKET_TRENDING_DOWN && trade.type == POSITION_TYPE_SELL)
base_distance *= 1.5;
else if(market.condition == MARKET_RANGING)
base_distance *= 0.8; // Reduce TP in ranging market
//--- Adjust for momentum
if(market.momentum > 70 && trade.type == POSITION_TYPE_BUY)
base_distance *= 1.2;
else if(market.momentum < 30 && trade.type == POSITION_TYPE_SELL)
base_distance *= 1.2;
tp_distance = base_distance;
break;
}
default:
return trade.tp; // No change
}
//--- Calculate new TP
double new_tp;
if(trade.type == POSITION_TYPE_BUY)
new_tp = trade.open_price + tp_distance;
else
new_tp = trade.open_price - tp_distance;
return NormalizeDouble(new_tp, m_symbol.Digits());
}
//+------------------------------------------------------------------+
//| Calculate dynamic stop loss |
//+------------------------------------------------------------------+
double CTradeManager::CalculateDynamicSL(ManagedTrade &trade, const MarketConditions &market)
{
//--- Find nearest support/resistance
double nearest_level = 0;
double min_distance = DBL_MAX;
for(int i = 0; i < market.sr_count; i++)
{
double distance = MathAbs(trade.open_price - market.sr_levels[i].price);
//--- For buy trades, look for support below
if(trade.type == POSITION_TYPE_BUY &&
market.sr_levels[i].price < trade.open_price &&
market.sr_levels[i].type == "support")
{
if(distance < min_distance)
{
min_distance = distance;
nearest_level = market.sr_levels[i].price;
}
}
//--- For sell trades, look for resistance above
else if(trade.type == POSITION_TYPE_SELL &&
market.sr_levels[i].price > trade.open_price &&
market.sr_levels[i].type == "resistance")
{
if(distance < min_distance)
{
min_distance = distance;
nearest_level = market.sr_levels[i].price;
}
}
}
//--- If found suitable level
if(nearest_level > 0)
{
double buffer = market.volatility * 0.2; // 20% ATR buffer
if(trade.type == POSITION_TYPE_BUY)
return nearest_level - buffer;
else
return nearest_level + buffer;
}
return trade.sl; // No change
}
//+------------------------------------------------------------------+
//| Check time-based exit |
//+------------------------------------------------------------------+
bool CTradeManager::CheckTimeBasedExit(ManagedTrade &trade)
{
//--- Time-based exit rules
int bars_limit = 100; // Exit after 100 bars
int time_limit = 86400; // Exit after 24 hours
//--- Check bars in trade
if(trade.bars_in_trade > bars_limit)
{
if(ClosePosition(trade.ticket))
{
Print("TradeManager: Time exit (bars) for #", trade.ticket);
return true;
}
}
//--- Check time in trade
if(TimeCurrent() - trade.open_time > time_limit)
{
if(ClosePosition(trade.ticket))
{
Print("TradeManager: Time exit (hours) for #", trade.ticket);
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Force stop loss on external trade |
//+------------------------------------------------------------------+
bool CTradeManager::ForceStopLoss(ManagedTrade &trade, double stop_distance)
{
if(stop_distance <= 0) return false;
double new_sl;
m_symbol.RefreshRates();
if(trade.type == POSITION_TYPE_BUY)
new_sl = m_symbol.Bid() - stop_distance;
else
new_sl = m_symbol.Ask() + stop_distance;
//--- Ensure minimum stop distance
double min_stop = GetMinStopDistance();
double current_price = (trade.type == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
double actual_distance = MathAbs(current_price - new_sl);
if(actual_distance < min_stop)
{
if(trade.type == POSITION_TYPE_BUY)
new_sl = current_price - min_stop;
else
new_sl = current_price + min_stop;
}
if(ModifyPosition(trade.ticket, new_sl, trade.tp))
{
trade.sl = new_sl;
Print("TradeManager: Forced SL on external trade #", trade.ticket);
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Force take profit on external trade |
//+------------------------------------------------------------------+
bool CTradeManager::ForceTakeProfit(ManagedTrade &trade, double tp_distance)
{
if(tp_distance <= 0) return false;
double new_tp;
if(trade.type == POSITION_TYPE_BUY)
new_tp = trade.open_price + tp_distance;
else
new_tp = trade.open_price - tp_distance;
if(ModifyPosition(trade.ticket, trade.sl, new_tp))
{
trade.tp = new_tp;
Print("TradeManager: Forced TP on external trade #", trade.ticket);
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Parse partial close levels string |
//+------------------------------------------------------------------+
bool CTradeManager::ParsePartialLevels(string levels_string)
{
m_partial_count = 0;
//--- Split by comma
string parts[];
int count = StringSplit(levels_string, ',', parts);
if(count == 0) return false;
for(int i = 0; i < count && i < 10; i++)
{
//--- Split each part by colon (R:Percent)
string level_parts[];
if(StringSplit(parts[i], ':', level_parts) == 2)
{
m_partial_levels[m_partial_count].r_multiple = StringToDouble(level_parts[0]);
m_partial_levels[m_partial_count].close_percent = StringToDouble(level_parts[1]);
m_partial_levels[m_partial_count].executed = false;
m_partial_count++;
}
}
Print("TradeManager: Parsed ", m_partial_count, " partial close levels");
return (m_partial_count > 0);
}
//+------------------------------------------------------------------+
//| Get minimum stop distance |
//+------------------------------------------------------------------+
double CTradeManager::GetMinStopDistance()
{
int stops_level = (int)m_symbol.StopsLevel();
double min_stop = stops_level * m_symbol.Point();
//--- Add buffer
return min_stop * 1.1;
}
//+------------------------------------------------------------------+
//| Validate stop levels |
//+------------------------------------------------------------------+
bool CTradeManager::ValidateStopLevels(double price, double sl, double tp)
{
double min_stop = GetMinStopDistance();
//--- Check stop loss
if(sl > 0)
{
double sl_distance = MathAbs(price - sl);
if(sl_distance < min_stop)
{
Print("TradeManager: Stop loss too close - ", sl_distance, " < ", min_stop);
return false;
}
}
//--- Check take profit
if(tp > 0)
{
double tp_distance = MathAbs(price - tp);
if(tp_distance < min_stop)
{
Print("TradeManager: Take profit too close - ", tp_distance, " < ", min_stop);
return false;
}
}
return true;
}
#endif // TRADE_MANAGER_MQH
//+------------------------------------------------------------------+