184 lines
5.7 KiB
Plaintext
184 lines
5.7 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)
|
|
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)
|
|
{
|
|
double break_even_trigger = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
|
|
g_break_even_profit);
|
|
double break_even_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
|
|
g_break_even_sl);
|
|
|
|
// Break-even only if profit threshold is reached
|
|
if(profit < break_even_trigger)
|
|
return false;
|
|
|
|
// For BUY: move SL to BE (open price + distance)
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
double be_sl = open_price + break_even_distance;
|
|
be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl);
|
|
|
|
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;
|
|
}
|
|
}
|
|
// For SELL: move SL to BE (open price - distance)
|
|
else if(pos_type == POSITION_TYPE_SELL)
|
|
{
|
|
double be_sl = open_price - break_even_distance;
|
|
be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl);
|
|
|
|
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)
|
|
{
|
|
double bid = mp_market_data.GetBid();
|
|
double ask = mp_market_data.GetAsk();
|
|
double trail_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(),
|
|
g_trailing_stop_points);
|
|
|
|
// For BUY: trailing stop follows price from below
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
double candidate_sl = bid - trail_distance;
|
|
candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl);
|
|
|
|
// Only move SL up (never down)
|
|
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;
|
|
}
|
|
}
|
|
// For SELL: trailing stop follows price from above
|
|
else if(pos_type == POSITION_TYPE_SELL)
|
|
{
|
|
double candidate_sl = ask + trail_distance;
|
|
candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl);
|
|
|
|
// Only move SL down (never up)
|
|
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__
|