2- Compile and debug - Critical error on exit - OnDeinit 3- Fix for Entry System - Add to OnTick(): 4 - ENUM_TECHNICAL_LEVEL enum in DataTypes.mqh -> other Enums and structures DataTypes.mqh v3 The Complete DataTypes Module provides the robust foundation needed for your institutional-grade risk management system, offering better trade tracking, risk analysis, and technical integration capabilities essential for professional algorithmic trading. Moved modules to inside Advisors post-compile 1 debug; became unversioned 2 -
767 lines
No EOL
25 KiB
MQL5
767 lines
No EOL
25 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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
|
|
//+------------------------------------------------------------------+ |