2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 21:53:01 +01:00
|
|
|
//| ExternalTradeManager.mqh v1.3 |
|
|
|
|
//| Optimized Trade Management Module |
|
|
|
|
//| Fixed: Compatibility with ERMT v6.8, pointers to structures |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
#ifndef EXTERNAL_TRADE_MANAGER_MQH
|
|
|
|
#define EXTERNAL_TRADE_MANAGER_MQH
|
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
#include <Object.mqh>
|
|
|
|
#include <Arrays/ArrayObj.mqh>
|
2025-08-27 14:21:02 +01:00
|
|
|
#include <Trade/Trade.mqh>
|
2025-08-27 15:49:38 +01:00
|
|
|
#include "DataTypes.mqh"
|
|
|
|
#include "Utilities.mqh"
|
2025-08-27 14:21:02 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| External Trade Manager Class |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
class CExternalTradeManager : public CObject
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
private:
|
|
|
|
// Dependencies
|
|
|
|
CUtilities* m_utils;
|
|
|
|
CTrade m_trade;
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
// External trade tracking - using array of structures
|
2025-08-27 19:51:34 +01:00
|
|
|
ExternalTradeRecord m_external_trades[];
|
2025-08-27 15:49:38 +01:00
|
|
|
int m_external_count;
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
int m_magic_number;
|
|
|
|
bool m_manage_external;
|
2025-08-27 21:53:01 +01:00
|
|
|
double m_external_risk_limit;
|
|
|
|
double m_external_profit; // Added missing member
|
2025-08-27 15:49:38 +01:00
|
|
|
ENUM_MANAGEMENT_MODE m_default_mode;
|
|
|
|
|
|
|
|
// Statistics
|
|
|
|
int m_total_detected;
|
|
|
|
int m_total_managed;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
// Helper methods
|
|
|
|
bool IsExternalTrade(ulong ticket);
|
|
|
|
bool UpdateExternalRecord(int index);
|
2025-08-27 21:53:01 +01:00
|
|
|
int FindTradeIndex(ulong ticket);
|
2025-08-27 15:49:38 +01:00
|
|
|
|
|
|
|
public:
|
|
|
|
// Constructor/Destructor
|
|
|
|
CExternalTradeManager();
|
|
|
|
~CExternalTradeManager();
|
|
|
|
|
|
|
|
// Initialization
|
|
|
|
bool Initialize(CUtilities* utils, int magic);
|
|
|
|
void SetManagementMode(ENUM_MANAGEMENT_MODE mode) { m_default_mode = mode; }
|
|
|
|
void SetExternalRiskLimit(double limit) { m_external_risk_limit = limit; }
|
|
|
|
|
|
|
|
// Core functionality
|
|
|
|
bool DetectExternalTrades();
|
|
|
|
bool ManageExternalTrade(ulong ticket);
|
|
|
|
bool ApplyRiskRulesToExternal(ulong ticket);
|
|
|
|
bool CloseExternalTrade(ulong ticket, string reason);
|
2025-08-27 21:53:01 +01:00
|
|
|
bool AddExternalTrade(const ExternalTradeRecord &record);
|
2025-08-27 15:49:38 +01:00
|
|
|
|
|
|
|
// Stop management
|
|
|
|
bool UpdateExternalStops(ulong ticket, double sl, double tp);
|
|
|
|
bool ApplyTrailingToExternal(ulong ticket, double trail_points);
|
|
|
|
bool MoveExternalToBreakeven(ulong ticket);
|
|
|
|
|
|
|
|
// Information methods
|
|
|
|
int GetExternalTradeCount() const { return m_external_count; }
|
|
|
|
bool GetExternalTradeInfo(ulong ticket, ExternalTradeRecord &record);
|
|
|
|
double GetTotalExternalExposure();
|
|
|
|
double GetExternalProfit();
|
|
|
|
|
|
|
|
// Monitoring
|
|
|
|
void MonitorExternalTrades();
|
|
|
|
bool IsTicketExternal(ulong ticket);
|
|
|
|
ENUM_EXTERNAL_STATUS GetExternalStatus(ulong ticket);
|
2025-08-27 14:21:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Constructor |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
CExternalTradeManager::CExternalTradeManager()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
m_utils = NULL;
|
|
|
|
m_magic_number = 0;
|
|
|
|
m_manage_external = true;
|
|
|
|
m_external_risk_limit = 5.0; // 5% default
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_profit = 0;
|
2025-08-27 15:49:38 +01:00
|
|
|
m_default_mode = MODE_STOPS_ONLY;
|
|
|
|
m_external_count = 0;
|
2025-08-27 21:53:01 +01:00
|
|
|
ArrayResize(m_external_trades, 10); // Initial size
|
2025-08-27 15:49:38 +01:00
|
|
|
m_total_detected = 0;
|
|
|
|
m_total_managed = 0;
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Destructor |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
CExternalTradeManager::~CExternalTradeManager()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
ArrayFree(m_external_trades);
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Initialize |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::Initialize(CUtilities* utils, int magic)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(utils == NULL) return false;
|
|
|
|
|
|
|
|
m_utils = utils;
|
|
|
|
m_magic_number = magic;
|
|
|
|
|
|
|
|
m_utils.Log("External Trade Manager initialized", LOG_INFO);
|
|
|
|
return true;
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Detect External Trades |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::DetectExternalTrades()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!m_manage_external) return true;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
int total_positions = PositionsTotal();
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
for(int i = 0; i < total_positions; i++)
|
|
|
|
{
|
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
if(ticket == 0) continue;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
|
|
|
// Check if this is an external trade (not our magic number)
|
2025-08-27 15:49:38 +01:00
|
|
|
if(PositionGetInteger(POSITION_MAGIC) == m_magic_number) continue;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
|
|
|
// Check if we're already tracking this trade
|
2025-08-27 15:49:38 +01:00
|
|
|
if(IsTicketExternal(ticket)) continue;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
|
|
|
// Create new external trade record
|
2025-08-27 19:51:34 +01:00
|
|
|
ExternalTradeRecord new_record;
|
2025-08-27 15:49:38 +01:00
|
|
|
new_record.ticket = ticket;
|
|
|
|
new_record.symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
new_record.type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
new_record.volume = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
new_record.entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
new_record.original_sl = PositionGetDouble(POSITION_SL);
|
|
|
|
new_record.original_tp = PositionGetDouble(POSITION_TP);
|
2025-08-27 21:53:01 +01:00
|
|
|
new_record.managed_sl = 0;
|
|
|
|
new_record.managed_tp = 0;
|
2025-08-27 15:49:38 +01:00
|
|
|
new_record.detection_time = TimeCurrent();
|
|
|
|
new_record.last_update = TimeCurrent();
|
|
|
|
new_record.status = EXTERNAL_DETECTED;
|
|
|
|
new_record.management_mode = m_default_mode;
|
2025-08-27 21:53:01 +01:00
|
|
|
new_record.max_profit = 0;
|
|
|
|
new_record.max_loss = 0;
|
|
|
|
new_record.notes = "";
|
|
|
|
|
|
|
|
// Add to tracking array
|
2025-08-27 19:51:34 +01:00
|
|
|
if(AddExternalTrade(new_record))
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
|
|
|
m_total_detected++;
|
2025-08-27 21:53:01 +01:00
|
|
|
m_utils.Log(StringFormat("Detected external trade: Ticket %d, Symbol %s",
|
|
|
|
ticket, new_record.symbol), LOG_INFO);
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
}
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
return true;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Add External Trade to Array |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CExternalTradeManager::AddExternalTrade(const ExternalTradeRecord &record)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
// Check if we need to resize the array
|
|
|
|
if(m_external_count >= ArraySize(m_external_trades))
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
ArrayResize(m_external_trades, m_external_count + 10);
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
2025-08-27 21:53:01 +01:00
|
|
|
|
|
|
|
// Add the record
|
2025-08-27 19:51:34 +01:00
|
|
|
m_external_trades[m_external_count] = record;
|
2025-08-27 15:49:38 +01:00
|
|
|
m_external_count++;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
return true;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 21:53:01 +01:00
|
|
|
//| Find Trade Index |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 21:53:01 +01:00
|
|
|
int CExternalTradeManager::FindTradeIndex(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
for(int i = 0; i < m_external_count; i++)
|
|
|
|
{
|
|
|
|
if(m_external_trades[i].ticket == ticket)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Check if Trade is External |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::IsExternalTrade(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
return (PositionGetInteger(POSITION_MAGIC) != m_magic_number);
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Check if Ticket is Being Tracked |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::IsTicketExternal(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
return (FindTradeIndex(ticket) >= 0);
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get External Trade Status |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
ENUM_EXTERNAL_STATUS CExternalTradeManager::GetExternalStatus(ulong ticket)
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
|
|
|
return m_external_trades[index].status;
|
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
return EXTERNAL_PENDING;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Manage External Trade |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::ManageExternalTrade(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!m_manage_external) return false;
|
|
|
|
|
|
|
|
// Find the external trade record
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
2025-08-27 15:49:38 +01:00
|
|
|
if(index == -1) return false;
|
|
|
|
|
|
|
|
// Select the position
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
{
|
|
|
|
// Position closed
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].status = EXTERNAL_CLOSED;
|
2025-08-27 15:49:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update current values
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].last_update = TimeCurrent();
|
2025-08-27 15:49:38 +01:00
|
|
|
double current_profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
|
|
|
|
// Track max profit/loss
|
2025-08-27 21:53:01 +01:00
|
|
|
if(current_profit > m_external_trades[index].max_profit)
|
|
|
|
m_external_trades[index].max_profit = current_profit;
|
|
|
|
if(current_profit < m_external_trades[index].max_loss)
|
|
|
|
m_external_trades[index].max_loss = current_profit;
|
2025-08-27 15:49:38 +01:00
|
|
|
|
|
|
|
// Apply management based on mode
|
2025-08-27 21:53:01 +01:00
|
|
|
switch(m_external_trades[index].management_mode)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
|
|
|
case MODE_FULL_CONTROL:
|
|
|
|
// Full management including entries/exits
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_external_trades[index].status == EXTERNAL_DETECTED)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].status = EXTERNAL_MANAGED;
|
2025-08-27 15:49:38 +01:00
|
|
|
m_total_managed++;
|
|
|
|
}
|
|
|
|
// Apply all management rules
|
|
|
|
ApplyRiskRulesToExternal(ticket);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MODE_STOPS_ONLY:
|
|
|
|
// Only manage stop losses
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_external_trades[index].managed_sl == 0 &&
|
|
|
|
m_external_trades[index].original_sl == 0)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
// Apply initial stop if none exists
|
2025-08-27 15:49:38 +01:00
|
|
|
double atr = m_utils.GetATR(14);
|
|
|
|
double stop_distance = atr * 2.0;
|
2025-08-27 21:53:01 +01:00
|
|
|
double new_sl = (m_external_trades[index].type == ORDER_TYPE_BUY) ?
|
|
|
|
m_external_trades[index].entry_price - stop_distance :
|
|
|
|
m_external_trades[index].entry_price + stop_distance;
|
|
|
|
UpdateExternalStops(ticket, new_sl, m_external_trades[index].original_tp);
|
|
|
|
m_external_trades[index].managed_sl = new_sl;
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MODE_MONITOR_ONLY:
|
|
|
|
// Just monitor, no modifications
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].status = EXTERNAL_MONITORED;
|
2025-08-27 15:49:38 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MODE_EMERGENCY_ONLY:
|
|
|
|
// Only intervene in emergency situations
|
|
|
|
if(current_profit < -(AccountInfoDouble(ACCOUNT_BALANCE) * m_external_risk_limit / 100))
|
|
|
|
{
|
|
|
|
CloseExternalTrade(ticket, "Emergency: Risk limit exceeded");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Apply Risk Rules to External Trade |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::ApplyRiskRulesToExternal(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
|
|
|
|
double profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
double risk_percent = MathAbs(profit) / balance * 100;
|
|
|
|
|
|
|
|
// Check if trade exceeds risk limit
|
|
|
|
if(risk_percent > m_external_risk_limit)
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("External trade %d exceeds risk limit: %.2f%%",
|
|
|
|
ticket, risk_percent), LOG_WARNING);
|
|
|
|
|
|
|
|
// Consider closing or reducing position
|
|
|
|
if(risk_percent > m_external_risk_limit * 1.5)
|
|
|
|
{
|
|
|
|
return CloseExternalTrade(ticket, "Risk limit exceeded");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Update External Trade Stops |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::UpdateExternalStops(ulong ticket, double sl, double tp)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
|
|
|
|
// Validate stops
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
|
|
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
|
|
int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
|
|
double min_stop = stops_level * point;
|
|
|
|
|
|
|
|
// Validate stop loss
|
|
|
|
if(sl > 0)
|
|
|
|
{
|
|
|
|
if(type == ORDER_TYPE_BUY && sl > bid - min_stop)
|
|
|
|
sl = bid - min_stop;
|
|
|
|
else if(type == ORDER_TYPE_SELL && sl < ask + min_stop)
|
|
|
|
sl = ask + min_stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate take profit
|
|
|
|
if(tp > 0)
|
|
|
|
{
|
|
|
|
if(type == ORDER_TYPE_BUY && tp < bid + min_stop)
|
|
|
|
tp = bid + min_stop;
|
|
|
|
else if(type == ORDER_TYPE_SELL && tp > ask - min_stop)
|
|
|
|
tp = ask - min_stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modify position
|
|
|
|
m_trade.PositionModify(ticket, sl, tp);
|
|
|
|
|
|
|
|
// Update record
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].managed_sl = sl;
|
|
|
|
m_external_trades[index].managed_tp = tp;
|
|
|
|
m_external_trades[index].status = EXTERNAL_MODIFIED;
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Apply Trailing Stop to External Trade |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::ApplyTrailingToExternal(ulong ticket, double trail_points)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
|
|
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
double trail_distance = trail_points * point;
|
|
|
|
|
|
|
|
double new_sl = 0;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = current_price - trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl > current_sl)
|
|
|
|
{
|
|
|
|
return UpdateExternalStops(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(type == ORDER_TYPE_SELL)
|
|
|
|
{
|
|
|
|
new_sl = current_price + trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl < current_sl)
|
|
|
|
{
|
|
|
|
return UpdateExternalStops(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Move External Trade to Breakeven |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::MoveExternalToBreakeven(ulong ticket)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
|
|
|
|
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
2025-08-27 21:53:01 +01:00
|
|
|
int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
|
|
|
double spread_points = spread * point;
|
2025-08-27 15:49:38 +01:00
|
|
|
|
|
|
|
// Add small buffer for breakeven
|
2025-08-27 21:53:01 +01:00
|
|
|
double buffer = spread_points * 2;
|
2025-08-27 15:49:38 +01:00
|
|
|
double breakeven_price = 0;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
breakeven_price = entry_price + buffer;
|
|
|
|
if(current_sl < breakeven_price)
|
|
|
|
{
|
|
|
|
return UpdateExternalStops(ticket, breakeven_price, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(type == ORDER_TYPE_SELL)
|
|
|
|
{
|
|
|
|
breakeven_price = entry_price - buffer;
|
|
|
|
if(current_sl == 0 || current_sl > breakeven_price)
|
|
|
|
{
|
|
|
|
return UpdateExternalStops(ticket, breakeven_price, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Close External Trade |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
bool CExternalTradeManager::CloseExternalTrade(ulong ticket, string reason)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
|
|
|
|
// Update status
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].status = EXTERNAL_CLOSING;
|
|
|
|
m_external_trades[index].notes = reason;
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close the position
|
|
|
|
bool result = m_trade.PositionClose(ticket);
|
|
|
|
|
|
|
|
if(result)
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Closed external trade %d: %s", ticket, reason), LOG_INFO);
|
|
|
|
|
|
|
|
// Update final status
|
2025-08-27 21:53:01 +01:00
|
|
|
if(index >= 0)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[index].status = EXTERNAL_CLOSED;
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get External Trade Info |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CExternalTradeManager::GetExternalTradeInfo(ulong ticket, ExternalTradeRecord &record)
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
record = m_external_trades[index];
|
|
|
|
return true;
|
2025-08-27 15:49:38 +01:00
|
|
|
}
|
|
|
|
return false;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Get Total External Exposure |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
double CExternalTradeManager::GetTotalExternalExposure()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
double total_exposure = 0;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_external_count; i++)
|
|
|
|
{
|
|
|
|
if(m_external_trades[i].status == EXTERNAL_CLOSED) continue;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
if(PositionSelectByTicket(m_external_trades[i].ticket))
|
|
|
|
{
|
|
|
|
total_exposure += PositionGetDouble(POSITION_VOLUME);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return total_exposure;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Get External Profit |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
double CExternalTradeManager::GetExternalProfit()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:49:38 +01:00
|
|
|
double total_profit = 0;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_external_count; i++)
|
|
|
|
{
|
|
|
|
if(m_external_trades[i].status == EXTERNAL_CLOSED) continue;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 15:49:38 +01:00
|
|
|
if(PositionSelectByTicket(m_external_trades[i].ticket))
|
|
|
|
{
|
|
|
|
total_profit += PositionGetDouble(POSITION_PROFIT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_external_profit = total_profit;
|
|
|
|
return total_profit;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
//| Monitor External Trades |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:49:38 +01:00
|
|
|
void CExternalTradeManager::MonitorExternalTrades()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
// Update status of all tracked trades
|
2025-08-27 15:49:38 +01:00
|
|
|
for(int i = m_external_count - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if(!PositionSelectByTicket(m_external_trades[i].ticket))
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
// Trade closed
|
|
|
|
if(m_external_trades[i].status != EXTERNAL_CLOSED)
|
2025-08-27 15:49:38 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_external_trades[i].status = EXTERNAL_CLOSED;
|
|
|
|
m_external_trades[i].last_update = TimeCurrent();
|
|
|
|
m_utils.Log(StringFormat("External trade %d closed",
|
|
|
|
m_external_trades[i].ticket), LOG_INFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove from array after some time
|
|
|
|
if(TimeCurrent() - m_external_trades[i].last_update > 3600) // Keep for 1 hour
|
|
|
|
{
|
|
|
|
// Shift array elements
|
2025-08-27 15:49:38 +01:00
|
|
|
for(int j = i; j < m_external_count - 1; j++)
|
|
|
|
{
|
|
|
|
m_external_trades[j] = m_external_trades[j + 1];
|
|
|
|
}
|
|
|
|
m_external_count--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update statistics
|
|
|
|
GetExternalProfit();
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Update External Record |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CExternalTradeManager::UpdateExternalRecord(int index)
|
|
|
|
{
|
|
|
|
if(index < 0 || index >= m_external_count) return false;
|
|
|
|
|
|
|
|
if(!PositionSelectByTicket(m_external_trades[index].ticket))
|
|
|
|
{
|
|
|
|
m_external_trades[index].status = EXTERNAL_CLOSED;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update current values
|
|
|
|
m_external_trades[index].last_update = TimeCurrent();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2025-08-27 14:21:02 +01:00
|
|
|
#endif // EXTERNAL_TRADE_MANAGER_MQH
|