mql5/Experts/Advisors/Modules_optimised/Trade_Manager_Optimised.mqh

1101 lines
No EOL
35 KiB
MQL5

//+------------------------------------------------------------------+
//| TradeManager.mqh v2.0 |
//| Optimized Trade Management Module |
//| Enhanced with multi-symbol support |
//+------------------------------------------------------------------+
#ifndef TRADE_MANAGER_MQH
#define TRADE_MANAGER_MQH
#include "DataTypes.mqh"
#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/OrderInfo.mqh>
//+------------------------------------------------------------------+
//| Trade Manager Class - Optimized Version |
//+------------------------------------------------------------------+
class CTradeManager
{
private:
//--- Configuration
TradeManagerConfig m_config;
int m_magic_number;
//--- Trade operations
CTrade m_trade;
CPositionInfo m_position;
COrderInfo m_order;
//--- Symbol cache for multi-symbol support
struct SymbolCache
{
string symbol;
CSymbolInfo info;
double min_stop_distance;
double point_value;
int digits;
datetime last_update;
};
SymbolCache m_symbol_cache[];
int m_cache_size;
//--- Partial close levels
PartialCloseLevel m_partial_levels[10];
int m_partial_count;
//--- Performance tracking
int m_trades_executed;
int m_trades_failed;
double m_total_slippage;
//--- 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(string symbol);
bool ValidateStopLevels(string symbol, double price, double sl, double tp);
CSymbolInfo* GetSymbolInfo(string symbol);
void UpdateSymbolCache(string symbol);
double CalculateTrailingDistance(ManagedTrade &trade, const MarketConditions &market);
bool ExecuteTradeWithRetry(TradeRequest &request, int max_retries = 3);
void LogTradeResult(string operation, bool success, string details = "");
public:
CTradeManager();
~CTradeManager();
//--- Initialization
bool Initialize(const TradeManagerConfig &config, int magic);
void UpdateConfiguration(const TradeManagerConfig &config);
//--- Trade operations
ulong OpenPosition(const TradeRequest &request);
bool ClosePosition(ulong ticket, double volume = 0, string reason = "");
bool ModifyPosition(ulong ticket, double sl, double tp);
//--- Batch operations
int CloseAllPositions(string symbol = NULL, ENUM_POSITION_TYPE type = -1);
int BreakevenAllPositions(string symbol = NULL);
//--- Trade management
void ManageTrade(ManagedTrade &trade, const MarketConditions &market);
void ApplyBreakeven(ManagedTrade &trade);
void ApplyTrailingStop(ManagedTrade &trade, const MarketConditions &market);
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);
ValidationResult ValidateExternalTrade(ManagedTrade &trade);
//--- Utility functions
double GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type);
double CalculateCommission(string symbol, double volume);
bool IsMarketOpen(string symbol);
//--- Performance metrics
int GetTradesExecuted() { return m_trades_executed; }
int GetTradesFailed() { return m_trades_failed; }
double GetAverageSlippage() { return (m_trades_executed > 0) ? m_total_slippage / m_trades_executed : 0; }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTradeManager::CTradeManager()
{
m_magic_number = 0;
m_partial_count = 0;
m_cache_size = 0;
m_trades_executed = 0;
m_trades_failed = 0;
m_total_slippage = 0;
//--- Pre-allocate symbol cache
ArrayResize(m_symbol_cache, 20);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CTradeManager::~CTradeManager()
{
//--- Clean up symbol cache
for(int i = 0; i < m_cache_size; i++)
{
// CSymbolInfo doesn't need explicit cleanup
}
}
//+------------------------------------------------------------------+
//| 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);
//--- 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;
}
}
//--- Initialize symbol cache with current symbol
UpdateSymbolCache(_Symbol);
Print("TradeManager initialized: TP=", EnumToString(m_config.tp_mode),
", SL=", EnumToString(m_config.sl_mode));
return true;
}
//+------------------------------------------------------------------+
//| Open new position with retry logic |
//+------------------------------------------------------------------+
ulong CTradeManager::OpenPosition(const TradeRequest &request)
{
//--- Get symbol info
CSymbolInfo *symbol_info = GetSymbolInfo(request.symbol);
if(symbol_info == NULL)
{
LogTradeResult("OpenPosition", false, "Invalid symbol: " + request.symbol);
return 0;
}
//--- Refresh rates
symbol_info.RefreshRates();
//--- Calculate actual prices
double price = 0;
double sl = 0;
double tp = 0;
if(request.type == ORDER_TYPE_BUY)
{
price = symbol_info.Ask();
if(request.sl_distance > 0)
sl = NormalizeDouble(price - request.sl_distance, symbol_info.Digits());
if(request.tp_distance > 0)
tp = NormalizeDouble(price + request.tp_distance, symbol_info.Digits());
}
else if(request.type == ORDER_TYPE_SELL)
{
price = symbol_info.Bid();
if(request.sl_distance > 0)
sl = NormalizeDouble(price + request.sl_distance, symbol_info.Digits());
if(request.tp_distance > 0)
tp = NormalizeDouble(price - request.tp_distance, symbol_info.Digits());
}
else
{
LogTradeResult("OpenPosition", false, "Invalid order type");
return 0;
}
//--- Validate stop levels
if(!ValidateStopLevels(request.symbol, price, sl, tp))
{
LogTradeResult("OpenPosition", false, "Invalid stop levels");
return 0;
}
//--- Prepare modified request for retry
TradeRequest retry_request = request;
retry_request.price = price;
//--- Execute with retry
bool result = false;
ulong ticket = 0;
int retries = 0;
while(retries < 3 && !result)
{
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)
{
ticket = m_trade.ResultOrder();
//--- Track slippage
double executed_price = m_trade.ResultPrice();
double slippage = MathAbs(executed_price - price) / symbol_info.Point();
m_total_slippage += slippage;
m_trades_executed++;
LogTradeResult("OpenPosition", true,
StringFormat("#%d %s %.2f @ %.5f (slippage: %.1f points)",
ticket, (request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL",
request.volume, executed_price, slippage));
}
else
{
//--- Handle specific errors
int error = GetLastError();
switch(error)
{
case ERR_TRADE_PRICE_INVALID:
case ERR_TRADE_PRICE_OFF:
// Refresh and retry
symbol_info.RefreshRates();
if(request.type == ORDER_TYPE_BUY)
price = symbol_info.Ask();
else
price = symbol_info.Bid();
break;
case ERR_TRADE_CONTEXT_BUSY:
// Wait and retry
Sleep(500);
break;
default:
// Don't retry on other errors
retries = 3;
break;
}
retries++;
if(retries < 3)
{
Print("TradeManager: Retrying order (attempt ", retries + 1, "/3)");
}
}
}
if(!result)
{
m_trades_failed++;
LogTradeResult("OpenPosition", false,
"Failed after " + IntegerToString(retries) + " attempts: " +
m_trade.ResultComment());
}
return ticket;
}
//+------------------------------------------------------------------+
//| Close position with partial support |
//+------------------------------------------------------------------+
bool CTradeManager::ClosePosition(ulong ticket, double volume, string reason)
{
if(!m_position.SelectByTicket(ticket))
{
LogTradeResult("ClosePosition", false, "Position not found: " + IntegerToString(ticket));
return false;
}
string symbol = m_position.Symbol();
CSymbolInfo *symbol_info = GetSymbolInfo(symbol);
if(symbol_info == NULL) return false;
symbol_info.RefreshRates();
bool result = false;
if(volume == 0 || volume >= m_position.Volume())
{
//--- Full close
result = m_trade.PositionClose(ticket);
if(result)
{
LogTradeResult("ClosePosition", true,
StringFormat("Closed #%d %s (Reason: %s)",
ticket, symbol, reason));
}
}
else
{
//--- Partial close
volume = NormalizeLotSize(volume, symbol);
if(volume >= symbol_info.LotsMin() &&
m_position.Volume() - volume >= symbol_info.LotsMin())
{
result = m_trade.PositionClosePartial(ticket, volume);
if(result)
{
LogTradeResult("ClosePosition", true,
StringFormat("Partially closed %.2f lots of #%d (Reason: %s)",
volume, ticket, reason));
}
}
else
{
LogTradeResult("ClosePosition", false, "Invalid partial volume");
}
}
if(!result)
{
LogTradeResult("ClosePosition", false,
"Failed to close #" + IntegerToString(ticket) + ": " +
m_trade.ResultComment());
}
return result;
}
//+------------------------------------------------------------------+
//| Modify position stops |
//+------------------------------------------------------------------+
bool CTradeManager::ModifyPosition(ulong ticket, double sl, double tp)
{
if(!m_position.SelectByTicket(ticket))
{
LogTradeResult("ModifyPosition", false, "Position not found: " + IntegerToString(ticket));
return false;
}
//--- Check if modification needed
if(NormalizeDouble(sl - m_position.StopLoss(), _Digits) == 0 &&
NormalizeDouble(tp - m_position.TakeProfit(), _Digits) == 0)
{
return true; // No change needed
}
//--- Validate new levels
string symbol = m_position.Symbol();
double current_price = GetCurrentPrice(symbol, m_position.PositionType());
if(!ValidateStopLevels(symbol, current_price, sl, tp))
{
LogTradeResult("ModifyPosition", false, "Invalid stop levels for modification");
return false;
}
//--- Execute modification
bool result = m_trade.PositionModify(ticket, sl, tp);
if(result)
{
LogTradeResult("ModifyPosition", true,
StringFormat("Modified #%d: SL=%.5f TP=%.5f", ticket, sl, tp));
}
else
{
LogTradeResult("ModifyPosition", false,
"Failed to modify #" + IntegerToString(ticket) + ": " +
m_trade.ResultComment());
}
return result;
}
//+------------------------------------------------------------------+
//| Main trade management function - Optimized |
//+------------------------------------------------------------------+
void CTradeManager::ManageTrade(ManagedTrade &trade, const MarketConditions &market)
{
//--- Get current price efficiently
double current_price = GetCurrentPrice(trade.symbol, trade.type);
if(current_price <= 0) return;
//--- Update trade metrics
double points_moved = (trade.type == POSITION_TYPE_BUY) ?
(current_price - trade.open_price) :
(trade.open_price - current_price);
//--- Check if position improved
bool position_improved = points_moved > 0;
//--- Force stops on external trades if configured
if(trade.is_external && trade.magic != m_magic_number)
{
if(m_config.force_sl && trade.sl == 0)
{
double atr = market.volatility;
if(atr > 0)
{
ForceStopLoss(trade, atr * 2.0);
}
}
if(m_config.force_tp && trade.tp == 0)
{
double atr = market.volatility;
if(atr > 0)
{
ForceTakeProfit(trade, atr * 3.0);
}
}
}
//--- Management sequence (optimized order)
//--- 1. Breakeven (first priority when profitable)
if(m_config.enable_breakeven && !trade.be_activated && position_improved)
{
if(CheckBreakeven(trade, current_price))
{
trade.be_activated = true;
return; // One modification per tick
}
}
//--- 2. Partial closing (before trailing to lock profits)
if(m_config.enable_partial && trade.partial_count < m_partial_count && position_improved)
{
if(CheckPartialClose(trade, current_price))
{
return; // Position modified
}
}
//--- 3. Trailing stop (after breakeven)
if(m_config.enable_trailing && trade.be_activated)
{
if(CheckTrailingStop(trade, current_price))
{
trade.trailing_activated = true;
return; // One modification per tick
}
}
//--- 4. Dynamic targets (adaptive mode)
if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_HYBRID ||
m_config.sl_mode == SL_DYNAMIC)
{
ApplyDynamicTargets(trade, market);
}
//--- 5. Time-based exits
if(m_config.tp_mode == TP_TIME_BASED || m_config.time_exit_bars > 0)
{
CheckTimeBasedExit(trade);
}
//--- Update bars in trade
trade.bars_in_trade = iBars(trade.symbol, PERIOD_CURRENT) -
iBarShift(trade.symbol, PERIOD_CURRENT, trade.open_time);
}
//+------------------------------------------------------------------+
//| Optimized breakeven check |
//+------------------------------------------------------------------+
bool CTradeManager::CheckBreakeven(ManagedTrade &trade, double current_price)
{
if(trade.risk_amount <= 0) return false;
//--- Calculate trigger distance based on initial risk
double risk_distance = MathAbs(trade.open_price - trade.sl);
if(risk_distance <= 0) return false;
double trigger_distance = risk_distance * m_config.breakeven_trigger;
double price_moved = (trade.type == POSITION_TYPE_BUY) ?
(current_price - trade.open_price) :
(trade.open_price - current_price);
//--- Check if trigger reached
if(price_moved >= trigger_distance)
{
//--- Calculate new stop loss
CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol);
if(symbol_info == NULL) return false;
double buffer = m_config.breakeven_buffer * symbol_info.Point();
if(buffer <= 0) buffer = symbol_info.Point() * 10; // Default 10 points
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
bool should_modify = false;
if(trade.type == POSITION_TYPE_BUY && new_sl > trade.sl)
should_modify = true;
else if(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl)
should_modify = true;
if(should_modify)
{
if(ModifyPosition(trade.ticket, new_sl, trade.tp))
{
trade.sl = new_sl;
Print("TradeManager: Breakeven activated for #", trade.ticket,
" at ", DoubleToString(new_sl, symbol_info.Digits()));
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Optimized trailing stop with multiple modes |
//+------------------------------------------------------------------+
bool CTradeManager::CheckTrailingStop(ManagedTrade &trade, double current_price)
{
//--- Calculate trailing distance
double trail_distance = CalculateTrailingDistance(trade, g_MarketConditions);
if(trail_distance <= 0) return false;
//--- Get symbol info
CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol);
if(symbol_info == NULL) 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;
//--- Apply step if configured
if(m_config.trailing_step > 0)
{
double step_size = m_config.trailing_step * symbol_info.Point();
double sl_change = MathAbs(new_sl - trade.sl);
if(sl_change < step_size)
return false; // Not enough movement
}
//--- Ensure trailing only improves position
bool should_trail = false;
if(trade.type == POSITION_TYPE_BUY && new_sl > trade.sl)
should_trail = true;
else if(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl)
should_trail = true;
if(should_trail)
{
//--- Validate stop distance
double min_stop = GetMinStopDistance(trade.symbol);
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;
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Calculate dynamic trailing distance |
//+------------------------------------------------------------------+
double CTradeManager::CalculateTrailingDistance(ManagedTrade &trade, const MarketConditions &market)
{
double trail_distance = 0;
switch(m_config.sl_mode)
{
case SL_TRAILING_ATR:
{
//--- ATR-based with multiplier
double multiplier = m_config.use_atr_multiplier ? 1.5 : 2.0;
//--- Adjust for market conditions
if(market.condition == MARKET_VOLATILE)
multiplier *= 1.2;
else if(market.condition == MARKET_QUIET)
multiplier *= 0.8;
trail_distance = market.volatility * multiplier;
break;
}
case SL_PARABOLIC_SAR:
{
//--- Get SAR value
double sar_buffer[1];
int sar_handle = iSAR(trade.symbol, PERIOD_CURRENT, 0.02, 0.2);
if(sar_handle != INVALID_HANDLE &&
CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0)
{
if(trade.type == POSITION_TYPE_BUY)
trail_distance = GetCurrentPrice(trade.symbol, trade.type) - sar_buffer[0];
else
trail_distance = sar_buffer[0] - GetCurrentPrice(trade.symbol, trade.type);
}
IndicatorRelease(sar_handle);
break;
}
case SL_SWING_POINTS:
{
//--- Find recent swing point
int lookback = 20;
double swing_point = 0;
if(trade.type == POSITION_TYPE_BUY)
{
//--- Find recent swing low
int lowest_bar = iLowest(trade.symbol, PERIOD_CURRENT, MODE_LOW, lookback, 1);
if(lowest_bar >= 0)
swing_point = iLow(trade.symbol, PERIOD_CURRENT, lowest_bar);
}
else
{
//--- Find recent swing high
int highest_bar = iHighest(trade.symbol, PERIOD_CURRENT, MODE_HIGH, lookback, 1);
if(highest_bar >= 0)
swing_point = iHigh(trade.symbol, PERIOD_CURRENT, highest_bar);
}
if(swing_point > 0)
{
double current = GetCurrentPrice(trade.symbol, trade.type);
trail_distance = MathAbs(current - swing_point);
}
break;
}
default:
{
//--- Default: percentage of profit
double profit_distance = MathAbs(GetCurrentPrice(trade.symbol, trade.type) - trade.open_price);
trail_distance = profit_distance * 0.5; // Trail at 50% of profit
//--- Minimum trail distance
double min_trail = market.volatility * 0.5;
trail_distance = MathMax(trail_distance, min_trail);
break;
}
}
return trail_distance;
}
//+------------------------------------------------------------------+
//| Check and apply partial close - Optimized |
//+------------------------------------------------------------------+
bool CTradeManager::CheckPartialClose(ManagedTrade &trade, double current_price)
{
//--- Find next unexecuted level
int next_level = -1;
for(int i = 0; i < m_partial_count; i++)
{
if(!m_partial_levels[i].executed)
{
next_level = i;
break;
}
}
if(next_level < 0) return false;
//--- Calculate current R-multiple
double current_r = 0;
if(trade.sl > 0)
{
double risk_distance = MathAbs(trade.open_price - trade.sl);
if(risk_distance > 0)
{
double profit_distance = (trade.type == POSITION_TYPE_BUY) ?
(current_price - trade.open_price) :
(trade.open_price - current_price);
current_r = profit_distance / risk_distance;
}
}
//--- Check if target reached
if(current_r >= m_partial_levels[next_level].r_multiple)
{
//--- Calculate volume to close
double close_volume = trade.current_volume *
(m_partial_levels[next_level].close_percent / 100.0);
//--- Get symbol info for lot normalization
CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol);
if(symbol_info == NULL) return false;
close_volume = NormalizeLotSize(close_volume, trade.symbol);
//--- Ensure minimum lot size and remaining volume
if(close_volume >= symbol_info.LotsMin() &&
trade.current_volume - close_volume >= symbol_info.LotsMin())
{
string reason = StringFormat("Partial close at %.1fR (%.0f%%)",
m_partial_levels[next_level].r_multiple,
m_partial_levels[next_level].close_percent);
if(ClosePosition(trade.ticket, close_volume, reason))
{
m_partial_levels[next_level].executed = true;
m_partial_levels[next_level].execution_time = TimeCurrent();
trade.partial_count++;
trade.current_volume -= close_volume;
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Get symbol info with caching |
//+------------------------------------------------------------------+
CSymbolInfo* CTradeManager::GetSymbolInfo(string symbol)
{
//--- Search in cache
for(int i = 0; i < m_cache_size; i++)
{
if(m_symbol_cache[i].symbol == symbol)
{
//--- Update if cache is old (> 60 seconds)
if(TimeCurrent() - m_symbol_cache[i].last_update > 60)
{
UpdateSymbolCache(symbol);
}
return &m_symbol_cache[i].info;
}
}
//--- Not in cache, add it
UpdateSymbolCache(symbol);
//--- Search again
for(int i = 0; i < m_cache_size; i++)
{
if(m_symbol_cache[i].symbol == symbol)
return &m_symbol_cache[i].info;
}
return NULL;
}
//+------------------------------------------------------------------+
//| Update symbol cache |
//+------------------------------------------------------------------+
void CTradeManager::UpdateSymbolCache(string symbol)
{
//--- Find existing or get free slot
int index = -1;
for(int i = 0; i < m_cache_size; i++)
{
if(m_symbol_cache[i].symbol == symbol)
{
index = i;
break;
}
}
if(index < 0)
{
if(m_cache_size < ArraySize(m_symbol_cache))
{
index = m_cache_size;
m_cache_size++;
}
else
{
//--- Cache full, replace oldest
datetime oldest_time = TimeCurrent();
index = 0;
for(int i = 0; i < m_cache_size; i++)
{
if(m_symbol_cache[i].last_update < oldest_time)
{
oldest_time = m_symbol_cache[i].last_update;
index = i;
}
}
}
}
//--- Update cache entry
m_symbol_cache[index].symbol = symbol;
m_symbol_cache[index].info.Name(symbol);
m_symbol_cache[index].info.RefreshRates();
//--- Cache frequently used values
m_symbol_cache[index].min_stop_distance = m_symbol_cache[index].info.StopsLevel() *
m_symbol_cache[index].info.Point() * 1.1;
m_symbol_cache[index].point_value = m_symbol_cache[index].info.Point();
m_symbol_cache[index].digits = m_symbol_cache[index].info.Digits();
m_symbol_cache[index].last_update = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Get current price for symbol and direction |
//+------------------------------------------------------------------+
double CTradeManager::GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type)
{
CSymbolInfo *symbol_info = GetSymbolInfo(symbol);
if(symbol_info == NULL) return 0;
symbol_info.RefreshRates();
return (type == POSITION_TYPE_BUY) ? symbol_info.Bid() : symbol_info.Ask();
}
//+------------------------------------------------------------------+
//| Get minimum stop distance with cache |
//+------------------------------------------------------------------+
double CTradeManager::GetMinStopDistance(string symbol)
{
//--- Try cache first
for(int i = 0; i < m_cache_size; i++)
{
if(m_symbol_cache[i].symbol == symbol)
{
return m_symbol_cache[i].min_stop_distance;
}
}
//--- Not cached, calculate
CSymbolInfo *symbol_info = GetSymbolInfo(symbol);
if(symbol_info == NULL) return 0;
return symbol_info.StopsLevel() * symbol_info.Point() * 1.1;
}
//+------------------------------------------------------------------+
//| Apply default stops to external trades |
//+------------------------------------------------------------------+
void CTradeManager::ApplyDefaultStops(ManagedTrade &trade, double atr_value)
{
if(atr_value <= 0) return;
bool needs_update = false;
double new_sl = trade.sl;
double new_tp = trade.tp;
//--- Add stop loss if missing
if(trade.sl == 0 && m_config.force_sl)
{
double sl_distance = atr_value * 2.0; // 2 ATR default
if(trade.type == POSITION_TYPE_BUY)
new_sl = trade.open_price - sl_distance;
else
new_sl = trade.open_price + sl_distance;
needs_update = true;
}
//--- Add take profit if missing
if(trade.tp == 0 && m_config.force_tp)
{
double tp_distance = atr_value * 3.0; // 3 ATR default
if(trade.type == POSITION_TYPE_BUY)
new_tp = trade.open_price + tp_distance;
else
new_tp = trade.open_price - tp_distance;
needs_update = true;
}
//--- Apply modifications
if(needs_update)
{
if(ModifyPosition(trade.ticket, new_sl, new_tp))
{
trade.sl = new_sl;
trade.tp = new_tp;
Print("TradeManager: Applied default stops to external trade #", trade.ticket,
" SL=", DoubleToString(new_sl, _Digits),
" TP=", DoubleToString(new_tp, _Digits));
}
}
}
//+------------------------------------------------------------------+
//| Validate stop levels |
//+------------------------------------------------------------------+
bool CTradeManager::ValidateStopLevels(string symbol, double price, double sl, double tp)
{
double min_stop = GetMinStopDistance(symbol);
//--- 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;
}
//+------------------------------------------------------------------+
//| Parse partial close levels string |
//+------------------------------------------------------------------+
bool CTradeManager::ParsePartialLevels(string levels_string)
{
m_partial_count = 0;
//--- Reset all levels
for(int i = 0; i < 10; i++)
{
m_partial_levels[i].r_multiple = 0;
m_partial_levels[i].close_percent = 0;
m_partial_levels[i].executed = false;
m_partial_levels[i].execution_time = 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++)
{
//--- Trim spaces
StringTrimLeft(parts[i]);
StringTrimRight(parts[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++;
}
}
//--- Sort levels by R-multiple (ascending)
for(int i = 0; i < m_partial_count - 1; i++)
{
for(int j = i + 1; j < m_partial_count; j++)
{
if(m_partial_levels[i].r_multiple > m_partial_levels[j].r_multiple)
{
PartialCloseLevel temp = m_partial_levels[i];
m_partial_levels[i] = m_partial_levels[j];
m_partial_levels[j] = temp;
}
}
}
Print("TradeManager: Parsed ", m_partial_count, " partial close levels");
return (m_partial_count > 0);
}
//+------------------------------------------------------------------+
//| Log trade operation results |
//+------------------------------------------------------------------+
void CTradeManager::LogTradeResult(string operation, bool success, string details)
{
ENUM_LOG_LEVEL level = success ? LOG_INFO : LOG_ERROR;
string prefix = success ? "SUCCESS" : "FAILED";
string message = StringFormat("TradeManager::%s [%s] %s",
operation, prefix, details);
Print(message);
}
//+------------------------------------------------------------------+
//| Close all positions |
//+------------------------------------------------------------------+
int CTradeManager::CloseAllPositions(string symbol, ENUM_POSITION_TYPE type)
{
int closed = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(m_position.SelectByIndex(i))
{
//--- Check filters
if(symbol != NULL && m_position.Symbol() != symbol)
continue;
if(type != -1 && m_position.PositionType() != type)
continue;
//--- Close position
if(ClosePosition(m_position.Ticket(), 0, "Close All Command"))
closed++;
}
}
return closed;
}
//+------------------------------------------------------------------+
//| Apply breakeven to all positions |
//+------------------------------------------------------------------+
int CTradeManager::BreakevenAllPositions(string symbol)
{
int modified = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(m_position.SelectByIndex(i))
{
//--- Check filter
if(symbol != NULL && m_position.Symbol() != symbol)
continue;
//--- Create temporary trade structure
ManagedTrade temp_trade;
temp_trade.ticket = m_position.Ticket();
temp_trade.symbol = m_position.Symbol();
temp_trade.type = m_position.PositionType();
temp_trade.open_price = m_position.PriceOpen();
temp_trade.sl = m_position.StopLoss();
temp_trade.tp = m_position.TakeProfit();
temp_trade.be_activated = false;
//--- Apply breakeven
double current_price = GetCurrentPrice(temp_trade.symbol, temp_trade.type);
if(CheckBreakeven(temp_trade, current_price))
modified++;
}
}
return modified;
}
#endif // TRADE_MANAGER_MQH
//+------------------------------------------------------------------+