Initialize project in MT5 Experts directory
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Execution/OrderManager.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef __ORDER_MANAGER_MQH__
|
||||
#define __ORDER_MANAGER_MQH__
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../Core/Config.mqh"
|
||||
#include "../Core/State.mqh"
|
||||
#include "../Core/Logger.mqh"
|
||||
|
||||
extern CLogger g_logger;
|
||||
|
||||
class COrderManager
|
||||
{
|
||||
private:
|
||||
CTrade m_trade;
|
||||
ulong m_magic;
|
||||
AssetProfile m_profile;
|
||||
|
||||
public:
|
||||
bool Init(ulong magic, const AssetProfile &profile)
|
||||
{
|
||||
m_magic = magic;
|
||||
m_profile = profile;
|
||||
m_trade.SetExpertMagicNumber(magic);
|
||||
m_trade.SetDeviationInPoints(10);
|
||||
m_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
m_trade.SetAsyncMode(false);
|
||||
Print("[OrderManager] Execution layer initialized. Magic: ", magic);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExecuteOrder(const SignalData &signal, const TradeParams ¶ms,
|
||||
EAState &state, ulong &outTicket)
|
||||
{
|
||||
outTicket = 0;
|
||||
if(!ValidateOrder(signal, params)) return false;
|
||||
bool useLimit = ShouldUseLimitOrder(signal, state);
|
||||
if(useLimit) return ExecuteLimitOrder(signal, params, state, outTicket);
|
||||
else return ExecuteMarketOrder(signal, params, state, outTicket);
|
||||
}
|
||||
|
||||
bool ExecuteMarketOrder(const SignalData &signal, const TradeParams ¶ms,
|
||||
EAState &state, ulong &outTicket)
|
||||
{
|
||||
outTicket = 0;
|
||||
int slippage = CalculateSlippage(signal.atrValue);
|
||||
m_trade.SetDeviationInPoints(slippage);
|
||||
bool success = false;
|
||||
int retries = 0;
|
||||
while(retries <= MAX_RETRIES && !success)
|
||||
{
|
||||
if(retries > 0)
|
||||
{
|
||||
int delayMs = RETRY_BASE_MS * (1 << (retries - 1));
|
||||
g_logger.LogEvent("ORDER", StringFormat("Retry %d/%d after %d ms", retries, MAX_RETRIES, delayMs));
|
||||
Sleep(delayMs);
|
||||
}
|
||||
if(signal.isBuy)
|
||||
success = m_trade.Buy(params.lotSize, _Symbol, signal.entryPrice, signal.slPrice, signal.tp1Price, InpEALabel);
|
||||
else
|
||||
success = m_trade.Sell(params.lotSize, _Symbol, signal.entryPrice, signal.slPrice, signal.tp1Price, InpEALabel);
|
||||
if(!success)
|
||||
{
|
||||
int err = GetLastError();
|
||||
g_logger.LogError("OrderManager", err, GetErrorDescription(err), retries);
|
||||
if(!IsRetriableError(err)) { g_logger.LogEvent("ORDER", "Non-retriable error. Aborting."); break; }
|
||||
if(err == TRADE_RETCODE_INVALID_STOPS)
|
||||
{
|
||||
SignalData mutableSignal = signal;
|
||||
AdjustStops(mutableSignal);
|
||||
}
|
||||
else if(err == TRADE_RETCODE_NO_MONEY) { g_logger.LogEvent("ORDER", "No margin. Aborting."); break; }
|
||||
else if(err == TRADE_RETCODE_MARKET_CLOSED) { g_logger.LogEvent("ORDER", "Market closed."); break; }
|
||||
}
|
||||
else outTicket = m_trade.ResultOrder();
|
||||
retries++;
|
||||
}
|
||||
if(success && outTicket > 0)
|
||||
{
|
||||
if(PositionSelectByTicket(outTicket))
|
||||
{
|
||||
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double lots = PositionGetDouble(POSITION_VOLUME);
|
||||
g_logger.LogEvent("ORDER", StringFormat("MARKET ORDER Ticket=%llu Price=%.5f Lots=%.2f", outTicket, openPrice, lots));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExecuteLimitOrder(const SignalData &signal, const TradeParams ¶ms,
|
||||
EAState &state, ulong &outTicket)
|
||||
{
|
||||
outTicket = 0;
|
||||
double limitPrice = CalculateLimitPrice(signal);
|
||||
double currentPrice = signal.isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double maxDistance = signal.atrValue * 0.3;
|
||||
if(signal.isBuy && limitPrice > currentPrice + maxDistance)
|
||||
return ExecuteMarketOrder(signal, params, state, outTicket);
|
||||
if(!signal.isBuy && limitPrice < currentPrice - maxDistance)
|
||||
return ExecuteMarketOrder(signal, params, state, outTicket);
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result = {};
|
||||
request.action = TRADE_ACTION_PENDING;
|
||||
request.symbol = _Symbol;
|
||||
request.volume = params.lotSize;
|
||||
request.price = limitPrice;
|
||||
request.sl = signal.slPrice;
|
||||
request.tp = signal.tp1Price;
|
||||
request.deviation = CalculateSlippage(signal.atrValue);
|
||||
request.magic = m_magic;
|
||||
request.comment = InpEALabel + "_LIMIT";
|
||||
request.type = signal.isBuy ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT;
|
||||
request.type_filling = ORDER_FILLING_IOC;
|
||||
request.expiration = ORDER_TIME_GTC;
|
||||
bool success = OrderSend(request, result);
|
||||
if(success && result.retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
outTicket = result.order;
|
||||
g_logger.LogEvent("ORDER", StringFormat("LIMIT ORDER Ticket=%llu Price=%.5f Lots=%.2f", outTicket, limitPrice, params.lotSize));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int err = GetLastError();
|
||||
g_logger.LogError("OrderManager", err, "Limit order failed", 0);
|
||||
return ExecuteMarketOrder(signal, params, state, outTicket);
|
||||
}
|
||||
}
|
||||
|
||||
void CancelStaleOrders(int maxAgeMinutes = 30)
|
||||
{
|
||||
int total = OrdersTotal();
|
||||
datetime now = TimeCurrent();
|
||||
for(int i = total - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
|
||||
if(OrderGetInteger(ORDER_MAGIC) != m_magic) continue;
|
||||
datetime orderTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
|
||||
int ageMinutes = (int)((now - orderTime) / 60);
|
||||
if(ageMinutes > maxAgeMinutes)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result = {};
|
||||
request.action = TRADE_ACTION_REMOVE;
|
||||
request.order = ticket;
|
||||
if(OrderSend(request, result))
|
||||
g_logger.LogEvent("ORDER", StringFormat("Cancelled stale order %llu (age: %d min)", ticket, ageMinutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool ShouldUseLimitOrder(const SignalData &signal, const EAState &state)
|
||||
{
|
||||
if(state.currentRegime == REGIME_RANGE && InpUseLimitOrders) return true;
|
||||
if(signal.pattern == PATTERN_PIN_BAR || signal.pattern == PATTERN_INSIDE_BAR) return InpUseLimitOrders;
|
||||
return false;
|
||||
}
|
||||
|
||||
double CalculateLimitPrice(const SignalData &signal)
|
||||
{
|
||||
double currentPrice = signal.isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double offset = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * 5;
|
||||
if(signal.isBuy) return currentPrice - offset;
|
||||
else return currentPrice + offset;
|
||||
}
|
||||
|
||||
bool ValidateOrder(const SignalData &signal, const TradeParams ¶ms)
|
||||
{
|
||||
int stopsLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double minDist = stopsLevel * _Point;
|
||||
double slDist = MathAbs(signal.entryPrice - signal.slPrice);
|
||||
double tpDist = MathAbs(signal.entryPrice - signal.tp1Price);
|
||||
if(slDist < minDist || tpDist < minDist)
|
||||
{
|
||||
g_logger.LogEvent("ORDER", "VALIDATION FAIL: SL/TP too close");
|
||||
return false;
|
||||
}
|
||||
int freezeLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
if(freezeLevel > 0)
|
||||
{
|
||||
double currentPrice = signal.isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(MathAbs(signal.entryPrice - currentPrice) > freezeLevel * _Point * 2)
|
||||
{
|
||||
g_logger.LogEvent("ORDER", "VALIDATION FAIL: Entry too far");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||||
if(params.lotSize < minLot || params.lotSize > maxLot)
|
||||
{
|
||||
g_logger.LogEvent("ORDER", StringFormat("VALIDATION FAIL: Lot %.2f outside range", params.lotSize));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int CalculateSlippage(double atrValue) const
|
||||
{
|
||||
double slippagePrice = atrValue * SLIPPAGE_ATR_MULT;
|
||||
int slippagePoints = (int)MathRound(slippagePrice / _Point);
|
||||
return MathMax(MIN_SLIPPAGE_PTS, MathMin(MAX_SLIPPAGE_PTS, slippagePoints));
|
||||
}
|
||||
|
||||
bool IsRetriableError(int err) const
|
||||
{
|
||||
switch(err)
|
||||
{
|
||||
case TRADE_RETCODE_REQUOTE:
|
||||
case TRADE_RETCODE_REJECT:
|
||||
case TRADE_RETCODE_CANCEL:
|
||||
case TRADE_RETCODE_TIMEOUT:
|
||||
case TRADE_RETCODE_INVALID:
|
||||
case TRADE_RETCODE_INVALID_VOLUME:
|
||||
case TRADE_RETCODE_INVALID_PRICE:
|
||||
case TRADE_RETCODE_INVALID_STOPS:
|
||||
case TRADE_RETCODE_TRADE_DISABLED:
|
||||
case TRADE_RETCODE_PRICE_OFF:
|
||||
case TRADE_RETCODE_CONNECTION:
|
||||
case TRADE_RETCODE_PRICE_CHANGED:
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AdjustStops(SignalData &signal)
|
||||
{
|
||||
int stopsLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double minDist = stopsLevel * _Point + _Point * 2;
|
||||
if(signal.isBuy)
|
||||
{
|
||||
signal.slPrice = signal.entryPrice - minDist;
|
||||
if(signal.tp1Price <= signal.entryPrice + minDist)
|
||||
signal.tp1Price = signal.entryPrice + minDist * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
signal.slPrice = signal.entryPrice + minDist;
|
||||
if(signal.tp1Price >= signal.entryPrice - minDist)
|
||||
signal.tp1Price = signal.entryPrice - minDist * 2;
|
||||
}
|
||||
}
|
||||
|
||||
string GetErrorDescription(int err) const
|
||||
{
|
||||
switch(err)
|
||||
{
|
||||
case TRADE_RETCODE_REQUOTE: return "Requote";
|
||||
case TRADE_RETCODE_REJECT: return "Rejected";
|
||||
case TRADE_RETCODE_CANCEL: return "Canceled";
|
||||
case TRADE_RETCODE_DONE: return "Done";
|
||||
case TRADE_RETCODE_DONE_PARTIAL: return "Partial";
|
||||
case TRADE_RETCODE_ERROR: return "Error";
|
||||
case TRADE_RETCODE_TIMEOUT: return "Timeout";
|
||||
case TRADE_RETCODE_INVALID: return "Invalid";
|
||||
case TRADE_RETCODE_INVALID_VOLUME: return "Invalid Volume";
|
||||
case TRADE_RETCODE_INVALID_PRICE: return "Invalid Price";
|
||||
case TRADE_RETCODE_INVALID_STOPS: return "Invalid Stops";
|
||||
case TRADE_RETCODE_TRADE_DISABLED: return "Trade Disabled";
|
||||
case TRADE_RETCODE_MARKET_CLOSED: return "Market Closed";
|
||||
case TRADE_RETCODE_NO_MONEY: return "No Money";
|
||||
case TRADE_RETCODE_PRICE_OFF: return "Price Off";
|
||||
case TRADE_RETCODE_CONNECTION: return "No Connection";
|
||||
case TRADE_RETCODE_PRICE_CHANGED: return "Price Changed";
|
||||
default: return "Unknown " + IntegerToString(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __ORDER_MANAGER_MQH__
|
||||
@@ -0,0 +1,290 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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__
|
||||
Reference in New Issue
Block a user