Files
2026-05-28 19:06:07 +03:00

291 lines
10 KiB
Plaintext

//+------------------------------------------------------------------+
//| Execution/TradeManager.mqh |
//| Trade Lifecycle: Partial Close, BE, Trailing Stop, Time Exit |
//| MODIFIED: Added TP2 Full Close support |
//+------------------------------------------------------------------+
#ifndef __TRADE_MANAGER_MQH__
#define __TRADE_MANAGER_MQH__
#include <Trade\Trade.mqh>
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Core/Logger.mqh"
#include "../Data/Volatility.mqh"
#include "OrderManager.mqh"
extern CLogger g_logger;
extern CVolatility g_volatility;
extern EAState g_state;
class CTradeManager
{
private:
CTrade m_trade;
AssetProfile m_profile;
COrderManager *m_orderMgr;
struct TradeTracking
{
ulong ticket;
datetime openTime;
double entryPrice;
double tp1Price;
double tp2Price;
double initialSL;
double partialLot;
bool tp1Hit;
bool tp2Hit;
bool beSet;
bool trailingActive;
ENUM_REGIME openRegime;
};
TradeTracking m_trades[];
int m_tradeCount;
public:
bool Init(const AssetProfile &profile, COrderManager &orderMgr)
{
m_profile = profile;
m_orderMgr = GetPointer(orderMgr);
m_tradeCount = 0;
ArrayResize(m_trades, 10);
Print("[TradeManager] Lifecycle manager initialized (v2.0 with TP2)");
return true;
}
void ManageOpenPositions(EAState &state, CVolatility &vol)
{
int posTotal = PositionsTotal();
if(posTotal == 0) { state.openPositions = 0; return; }
double atr = vol.GetATR();
if(atr <= 0) atr = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * 10;
for(int i = posTotal - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
ulong ticket = PositionGetInteger(POSITION_TICKET);
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double lots = PositionGetDouble(POSITION_VOLUME);
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
int type = (int)PositionGetInteger(POSITION_TYPE);
int idx = FindTradeIndex(ticket);
if(idx < 0) idx = RegisterTrade(ticket, entry, tp, sl, openTime);
double currentPrice = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(!m_trades[idx].tp1Hit && m_trades[idx].tp1Price > 0)
{
bool hitTP1 = (type == POSITION_TYPE_BUY && currentPrice >= m_trades[idx].tp1Price) ||
(type == POSITION_TYPE_SELL && currentPrice <= m_trades[idx].tp1Price);
if(hitTP1) { m_trades[idx].tp1Hit = true; PartialClose(idx, lots, ticket); }
}
if(m_trades[idx].tp1Hit && !m_trades[idx].tp2Hit && m_trades[idx].tp2Price > 0)
{
bool hitTP2 = (type == POSITION_TYPE_BUY && currentPrice >= m_trades[idx].tp2Price) ||
(type == POSITION_TYPE_SELL && currentPrice <= m_trades[idx].tp2Price);
if(hitTP2)
{
m_trades[idx].tp2Hit = true;
ClosePosition(ticket, EXIT_TP2);
g_logger.LogEvent("TRADE", StringFormat("TP2 Full Close ticket %llu at %.5f", ticket, currentPrice));
RemoveTrade(idx);
continue;
}
}
if(m_trades[idx].tp1Hit && !m_trades[idx].beSet)
SetBreakEven(idx, entry, sl, type, atr);
if(m_trades[idx].beSet && m_trades[idx].trailingActive)
UpdateTrailingStop(idx, currentPrice, type, atr, sl);
if(m_trades[idx].openRegime == REGIME_RANGE)
{
int elapsed = (int)(TimeCurrent() - openTime);
if(elapsed >= m_profile.maxTradeDuration * 60)
{
g_logger.LogEvent("TRADE", StringFormat("Time exit ticket %llu after %d min", ticket, elapsed/60));
ClosePosition(ticket, EXIT_TIME);
RemoveTrade(idx);
continue;
}
}
}
state.openPositions = CountOurPositions();
}
void CheckClosedTrades(EAState &state)
{
for(int i = m_tradeCount - 1; i >= 0; i--)
{
if(!PositionSelectByTicket(m_trades[i].ticket))
{
state.lastTradeClose = TimeCurrent();
state.totalTradesToday++;
state.totalTradesWeek++;
RemoveTrade(i);
}
}
}
void CloseAllPositions(EAState &state, ENUM_EXIT_REASON reason)
{
int posTotal = PositionsTotal();
for(int i = posTotal - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
ulong ticket = PositionGetInteger(POSITION_TICKET);
ClosePosition(ticket, reason);
}
ArrayResize(m_trades, 10);
m_tradeCount = 0;
state.openPositions = 0;
}
void CloseRangeTrades(EAState &state)
{
for(int i = m_tradeCount - 1; i >= 0; i--)
{
if(m_trades[i].openRegime == REGIME_RANGE)
{
if(PositionSelectByTicket(m_trades[i].ticket))
ClosePosition(m_trades[i].ticket, EXIT_REGIME_CHANGE);
RemoveTrade(i);
}
}
}
void TightenStops(EAState &state)
{
double atr = g_volatility.GetATR();
for(int i = 0; i < m_tradeCount; i++)
{
if(!PositionSelectByTicket(m_trades[i].ticket)) continue;
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
int type = (int)PositionGetInteger(POSITION_TYPE);
double newSL;
double buffer = atr * 0.5;
if(type == POSITION_TYPE_BUY)
{
newSL = entry + buffer;
if(newSL > currentSL || currentSL == 0)
m_trade.PositionModify(m_trades[i].ticket, newSL, PositionGetDouble(POSITION_TP));
}
else
{
newSL = entry - buffer;
if(newSL < currentSL || currentSL == 0)
m_trade.PositionModify(m_trades[i].ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
}
private:
int FindTradeIndex(ulong ticket) const
{
for(int i = 0; i < m_tradeCount; i++)
if(m_trades[i].ticket == ticket) return i;
return -1;
}
int RegisterTrade(ulong ticket, double entry, double tp1, double sl, datetime time)
{
if(m_tradeCount >= ArraySize(m_trades)) ArrayResize(m_trades, ArraySize(m_trades) + 10);
int idx = m_tradeCount++;
m_trades[idx].ticket = ticket;
m_trades[idx].entryPrice = entry;
m_trades[idx].tp1Price = tp1;
m_trades[idx].initialSL = sl;
m_trades[idx].openTime = time;
m_trades[idx].tp1Hit = false;
m_trades[idx].tp2Hit = false;
m_trades[idx].beSet = false;
m_trades[idx].trailingActive = true;
m_trades[idx].openRegime = g_state.currentRegime;
m_trades[idx].partialLot = 0;
return idx;
}
void RemoveTrade(int idx)
{
if(idx < 0 || idx >= m_tradeCount) return;
for(int i = idx; i < m_tradeCount - 1; i++)
m_trades[i] = m_trades[i + 1];
m_tradeCount--;
}
void PartialClose(int idx, double totalLots, ulong ticket)
{
double closeLots = NormalizeDouble(totalLots * m_profile.partialCloseRatio, 2);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(closeLots < minLot) closeLots = minLot;
if(closeLots >= totalLots) closeLots = totalLots * 0.5;
m_trades[idx].partialLot = closeLots;
if(m_trade.PositionClosePartial(ticket, closeLots))
g_logger.LogEvent("TRADE", StringFormat("Partial close %.2f lots ticket %llu", closeLots, ticket));
else
g_logger.LogEvent("TRADE", StringFormat("Partial close FAILED ticket %llu", ticket));
}
void SetBreakEven(int idx, double entry, double currentSL, int type, double atr)
{
double buffer = atr * BE_BUFFER_ATR_MULT;
double newSL;
if(type == POSITION_TYPE_BUY) newSL = entry + buffer;
else newSL = entry - buffer;
bool shouldMove = (type == POSITION_TYPE_BUY && (newSL > currentSL || currentSL == 0)) ||
(type == POSITION_TYPE_SELL && (newSL < currentSL || currentSL == 0));
if(shouldMove)
{
double currentTP = PositionGetDouble(POSITION_TP);
if(m_trade.PositionModify(m_trades[idx].ticket, newSL, currentTP))
{
m_trades[idx].beSet = true;
g_logger.LogEvent("TRADE", StringFormat("BE set ticket %llu at %.5f", m_trades[idx].ticket, newSL));
}
}
}
void UpdateTrailingStop(int idx, double currentPrice, int type, double atr, double currentSL)
{
double trailDist = atr * m_profile.trailingATRMult;
double newSL;
if(type == POSITION_TYPE_BUY)
{
newSL = currentPrice - trailDist;
if(newSL > currentSL)
{
double currentTP = PositionGetDouble(POSITION_TP);
m_trade.PositionModify(m_trades[idx].ticket, newSL, currentTP);
}
}
else
{
newSL = currentPrice + trailDist;
if(newSL < currentSL || currentSL == 0)
{
double currentTP = PositionGetDouble(POSITION_TP);
m_trade.PositionModify(m_trades[idx].ticket, newSL, currentTP);
}
}
}
void ClosePosition(ulong ticket, ENUM_EXIT_REASON reason)
{
if(m_trade.PositionClose(ticket))
g_logger.LogEvent("TRADE", StringFormat("Closed ticket %llu. Reason: %s", ticket, EnumToString(reason)));
}
int CountOurPositions() const
{
int count = 0;
int total = PositionsTotal();
for(int i = 0; i < total; i++)
if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
count++;
return count;
}
};
#endif // __TRADE_MANAGER_MQH__