255 lines
8.1 KiB
Plaintext
255 lines
8.1 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| TrailingStop.mqh - Trailing stop and break-even management |
|
|
//| Modifies SL based on profit targets |
|
|
//+------------------------------------------------------------------+
|
|
|
|
#ifndef __TRAILINGSTOP_MQH__
|
|
#define __TRAILINGSTOP_MQH__
|
|
|
|
#include "Config.mqh"
|
|
#include "Logger.mqh"
|
|
#include "MarketData.mqh"
|
|
#include "Utilities.mqh"
|
|
#include "TradeManager.mqh"
|
|
|
|
class CTrailingStop
|
|
{
|
|
private:
|
|
CMarketData *mp_market_data;
|
|
CLogger *mp_logger;
|
|
CTradeManager *mp_trade_manager;
|
|
|
|
public:
|
|
// Constructor
|
|
CTrailingStop(CMarketData *market_data, CLogger *logger, CTradeManager *trade_manager)
|
|
{
|
|
mp_market_data = market_data;
|
|
mp_logger = logger;
|
|
mp_trade_manager = trade_manager;
|
|
}
|
|
|
|
// Update trailing stop for all positions
|
|
void UpdateAllPositions()
|
|
{
|
|
if(!g_use_trailing_stop && !g_use_break_even && !g_use_trailing_stop_atr && !g_use_break_even_atr)
|
|
return;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(PositionSelectByTicket(PositionGetTicket(i)))
|
|
{
|
|
if(PositionGetString(POSITION_SYMBOL) == mp_market_data.GetSymbol() &&
|
|
PositionGetInteger(POSITION_MAGIC) == g_magic_number)
|
|
{
|
|
UpdatePosition(PositionGetTicket(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update trailing stop for a single position
|
|
void UpdatePosition(ulong ticket)
|
|
{
|
|
if(ticket == 0)
|
|
return;
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
return;
|
|
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
double current_tp = PositionGetDouble(POSITION_TP);
|
|
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
double new_sl = current_sl;
|
|
double new_tp = current_tp;
|
|
bool need_modify = false;
|
|
|
|
// Apply break-even logic first
|
|
if(g_use_break_even)
|
|
{
|
|
if(ApplyBreakEven(pos_type, open_price, current_sl, profit, new_sl))
|
|
{
|
|
need_modify = true;
|
|
}
|
|
}
|
|
|
|
// Apply trailing stop logic
|
|
if(g_use_trailing_stop)
|
|
{
|
|
if(ApplyTrailingStop(pos_type, current_sl, new_sl))
|
|
{
|
|
need_modify = true;
|
|
}
|
|
}
|
|
|
|
// Only modify if SL changed
|
|
if(need_modify && new_sl != current_sl)
|
|
{
|
|
mp_trade_manager.ModifyPosition(ticket, new_sl, new_tp);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Apply break-even logic
|
|
bool ApplyBreakEven(ENUM_POSITION_TYPE pos_type, double open_price,
|
|
double current_sl, double profit, double &new_sl)
|
|
{
|
|
string symbol = mp_market_data.GetSymbol();
|
|
double current_price = (pos_type == POSITION_TYPE_BUY) ? mp_market_data.GetBid() : mp_market_data.GetAsk();
|
|
double price_move = 0.0;
|
|
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
price_move = current_price - open_price;
|
|
else if(pos_type == POSITION_TYPE_SELL)
|
|
price_move = open_price - current_price;
|
|
else
|
|
return false;
|
|
|
|
if(current_sl <= 0.0)
|
|
return false;
|
|
|
|
double initial_risk = (pos_type == POSITION_TYPE_BUY)
|
|
? (open_price - current_sl)
|
|
: (current_sl - open_price);
|
|
if(initial_risk <= 0.0)
|
|
return false;
|
|
|
|
double trigger_distance = g_break_even_trigger_r * initial_risk;
|
|
if(price_move < trigger_distance)
|
|
return false;
|
|
|
|
double buffer = CUtilities::PointsToPrice(symbol, g_break_even_buffer_points);
|
|
double point = CUtilities::GetPoint(symbol);
|
|
long min_stop_points = (long)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
double min_stop_distance = min_stop_points * point;
|
|
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
double be_sl = CUtilities::NormalizePrice(symbol, open_price + buffer);
|
|
if((mp_market_data.GetBid() - be_sl) < min_stop_distance)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info("Break-even skipped: would violate broker minimal stop distance");
|
|
return false;
|
|
}
|
|
|
|
if(be_sl > current_sl)
|
|
{
|
|
new_sl = be_sl;
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Break-even applied: BUY SL moved to %.5f", be_sl));
|
|
return true;
|
|
}
|
|
}
|
|
else if(pos_type == POSITION_TYPE_SELL)
|
|
{
|
|
double be_sl = CUtilities::NormalizePrice(symbol, open_price - buffer);
|
|
if((be_sl - mp_market_data.GetAsk()) < min_stop_distance)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info("Break-even skipped: would violate broker minimal stop distance");
|
|
return false;
|
|
}
|
|
|
|
if(be_sl < current_sl)
|
|
{
|
|
new_sl = be_sl;
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Break-even applied: SELL SL moved to %.5f", be_sl));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Apply trailing stop logic
|
|
bool ApplyTrailingStop(ENUM_POSITION_TYPE pos_type, double current_sl, double &new_sl)
|
|
{
|
|
string symbol = mp_market_data.GetSymbol();
|
|
double bid = mp_market_data.GetBid();
|
|
double ask = mp_market_data.GetAsk();
|
|
|
|
if(current_sl <= 0.0)
|
|
return false;
|
|
|
|
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double initial_risk = (pos_type == POSITION_TYPE_BUY)
|
|
? (open_price - current_sl)
|
|
: (current_sl - open_price);
|
|
if(initial_risk <= 0.0)
|
|
return false;
|
|
|
|
double price = (pos_type == POSITION_TYPE_BUY) ? bid : ask;
|
|
double price_move = (pos_type == POSITION_TYPE_BUY) ? (price - open_price) : (open_price - price);
|
|
double trail_start_distance = g_trail_start_r * initial_risk;
|
|
if(price_move < trail_start_distance)
|
|
return false;
|
|
|
|
int atr_handle = iATR(symbol, g_strategy_entry_timeframe, g_atr_period);
|
|
if(atr_handle == INVALID_HANDLE)
|
|
return false;
|
|
|
|
double atr_value[];
|
|
ArraySetAsSeries(atr_value, true);
|
|
ArrayResize(atr_value, 1);
|
|
if(CopyBuffer(atr_handle, 0, 1, 1, atr_value) <= 0)
|
|
{
|
|
IndicatorRelease(atr_handle);
|
|
return false;
|
|
}
|
|
IndicatorRelease(atr_handle);
|
|
|
|
double trail_distance = atr_value[0] * g_trail_distance_atr_multiplier;
|
|
if(trail_distance <= 0.0)
|
|
return false;
|
|
|
|
double point = CUtilities::GetPoint(symbol);
|
|
long min_stop_points = (long)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
double min_stop_distance = min_stop_points * point;
|
|
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
double candidate_sl = CUtilities::NormalizePrice(symbol, bid - trail_distance);
|
|
if((bid - candidate_sl) < min_stop_distance)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info("Trailing stop skipped: candidate SL too close to price (broker stop level)");
|
|
return false;
|
|
}
|
|
|
|
if(candidate_sl > current_sl)
|
|
{
|
|
new_sl = candidate_sl;
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Trailing stop applied: BUY SL moved to %.5f", candidate_sl));
|
|
return true;
|
|
}
|
|
}
|
|
else if(pos_type == POSITION_TYPE_SELL)
|
|
{
|
|
double candidate_sl = CUtilities::NormalizePrice(symbol, ask + trail_distance);
|
|
if((candidate_sl - ask) < min_stop_distance)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info("Trailing stop skipped: candidate SL too close to price (broker stop level)");
|
|
return false;
|
|
}
|
|
|
|
if(candidate_sl < current_sl)
|
|
{
|
|
new_sl = candidate_sl;
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Trailing stop applied: SELL SL moved to %.5f", candidate_sl));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
#endif //__TRAILINGSTOP_MQH__
|