1101 lines
35 KiB
MQL5
1101 lines
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
|
||
|
//+------------------------------------------------------------------+
|