#ifndef HIT_POSITION_LIFECYCLE_MANAGER_MQH #define HIT_POSITION_LIFECYCLE_MANAGER_MQH #include // Ticket-based position lifecycle manager for hedging accounts. class CHITPositionLifecycleManager { private: string m_symbol; ulong m_magic; ulong m_deviation_points; int m_close_after_seconds; CTrade m_trade; public: CHITPositionLifecycleManager() { m_symbol = _Symbol; m_magic = 0; m_deviation_points = 10; m_close_after_seconds = 0; } bool Init(const string symbol, const ulong magic, const ulong deviation_points, const int close_after_seconds) { m_symbol = symbol; m_magic = magic; m_deviation_points = deviation_points; m_close_after_seconds = close_after_seconds; if(m_symbol == "") { Print("HITPositionLifecycleManager init failed: symbol is empty."); return false; } ResetLastError(); if(!SymbolSelect(m_symbol, true)) { Print("HITPositionLifecycleManager init failed: symbol=", m_symbol, " err=", GetLastError()); return false; } m_trade.SetExpertMagicNumber(m_magic); m_trade.SetDeviationInPoints((int)m_deviation_points); m_trade.SetTypeFillingBySymbol(m_symbol); return true; } void SetCloseAfterSeconds(const int close_after_seconds) { m_close_after_seconds = close_after_seconds; } int CountTargetPositions() { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; --i) { const ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(!IsTargetSelectedPosition()) continue; count++; } return count; } bool CloseExpiredPositions() { if(m_close_after_seconds <= 0) return false; bool result = false; const datetime current_time = TimeCurrent(); for(int i = PositionsTotal() - 1; i >= 0; --i) { const ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; ResetLastError(); if(!PositionSelectByTicket(ticket)) { Print("HITPositionLifecycleManager select failed: ticket=", ticket, " err=", GetLastError()); continue; } if(!IsTargetSelectedPosition()) continue; const datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); if(entry_time <= 0) continue; if(current_time - entry_time < m_close_after_seconds) continue; if(CloseSelectedPosition(ticket, "time limit")) result = true; } return result; } private: bool IsTargetSelectedPosition() { if(PositionGetString(POSITION_SYMBOL) != m_symbol) return false; if((ulong)PositionGetInteger(POSITION_MAGIC) != m_magic) return false; return true; } string PositionTypeText(const ENUM_POSITION_TYPE type) { if(type == POSITION_TYPE_BUY) return "BUY"; if(type == POSITION_TYPE_SELL) return "SELL"; return "UNKNOWN"; } bool CloseSelectedPosition(const ulong ticket, const string reason) { const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); const double volume = PositionGetDouble(POSITION_VOLUME); ResetLastError(); if(!m_trade.PositionClose(ticket, m_deviation_points)) { Print("HITPositionLifecycleManager close failed: ticket=", ticket, " symbol=", m_symbol, " magic=", m_magic, " type=", PositionTypeText(type), " volume=", DoubleToString(volume, 2), " reason=", reason, " retcode=", m_trade.ResultRetcode(), " err=", GetLastError()); return false; } Print("HITPositionLifecycleManager closed position: ticket=", ticket, " symbol=", m_symbol, " magic=", m_magic, " type=", PositionTypeText(type), " volume=", DoubleToString(volume, 2), " reason=", reason, " retcode=", m_trade.ResultRetcode()); return true; } }; #endif