2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| TradeManager.mqh v2.2 |
|
2025-08-08 20:32:34 +01:00
|
|
|
//| Optimized Trade Management Module |
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Fixed: Compatibility with ERMT 6.8 |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
#ifndef TRADE_MANAGER_MQH
|
|
|
|
#define TRADE_MANAGER_MQH
|
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
#include <Object.mqh>
|
|
|
|
#include <Trade/Trade.mqh>
|
|
|
|
#include <Arrays/ArrayObj.mqh>
|
2025-07-20 22:38:24 +01:00
|
|
|
#include "DataTypes.mqh"
|
2025-08-27 19:51:34 +01:00
|
|
|
#include "Utilities.mqh"
|
|
|
|
#include "RiskManager.mqh"
|
|
|
|
#include "TechnicalAnalysis.mqh"
|
2025-08-27 14:21:02 +01:00
|
|
|
#include "ExternalTradeManager.mqh"
|
2025-08-22 17:46:10 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Trade Manager Class |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
class CTradeManager : public CObject
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
// Dependencies
|
|
|
|
CUtilities* m_utils;
|
|
|
|
CRiskManager* m_risk;
|
|
|
|
CTechnicalAnalysis* m_tech;
|
|
|
|
CExternalTradeManager* m_external;
|
|
|
|
|
|
|
|
// Trade execution
|
|
|
|
CTrade m_trade;
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
int m_magic_number;
|
|
|
|
string m_comment;
|
|
|
|
double m_slippage;
|
|
|
|
ENUM_POSITION_SIZING m_sizing_method;
|
|
|
|
double m_fixed_lot_size;
|
|
|
|
double m_atr_multiplier_sl;
|
|
|
|
double m_atr_multiplier_tp;
|
|
|
|
|
|
|
|
// Trailing stop settings
|
|
|
|
ENUM_TRAILING_METHOD m_trailing_method;
|
|
|
|
double m_trail_start;
|
|
|
|
double m_trail_step;
|
|
|
|
double m_trail_distance;
|
|
|
|
bool m_breakeven_enabled;
|
|
|
|
double m_breakeven_trigger;
|
|
|
|
double m_breakeven_offset;
|
|
|
|
|
|
|
|
// Trade tracking - using arrays of structures
|
|
|
|
TradeInfo m_managed_trades[];
|
|
|
|
int m_trade_count;
|
|
|
|
|
|
|
|
// Performance tracking
|
|
|
|
PerformanceMetrics m_performance;
|
|
|
|
double m_peak_balance;
|
|
|
|
datetime m_last_update;
|
|
|
|
|
|
|
|
// Trade history
|
|
|
|
TradeInfo m_history[];
|
|
|
|
int m_history_count;
|
|
|
|
|
|
|
|
// Helper methods
|
|
|
|
bool UpdateTradeInfo(int index);
|
|
|
|
void RecordTrade(const TradeInfo &trade);
|
|
|
|
void UpdatePerformanceMetrics();
|
|
|
|
bool ExecuteMarketOrder(MqlTradeRequest &request, MqlTradeResult &result);
|
|
|
|
double CalculateStopLoss(ENUM_ORDER_TYPE type, double entry, double atr);
|
|
|
|
double CalculateTakeProfit(ENUM_ORDER_TYPE type, double entry, double sl);
|
2025-08-27 21:53:01 +01:00
|
|
|
int FindTradeIndex(ulong ticket);
|
2025-08-27 19:51:34 +01:00
|
|
|
|
|
|
|
public:
|
|
|
|
// Constructor/Destructor
|
|
|
|
CTradeManager();
|
|
|
|
~CTradeManager();
|
|
|
|
|
|
|
|
// Initialization
|
|
|
|
bool Initialize(CUtilities* utils, CRiskManager* risk,
|
|
|
|
CTechnicalAnalysis* tech,
|
|
|
|
CExternalTradeManager* external,
|
|
|
|
int magic);
|
|
|
|
|
|
|
|
// Configuration
|
|
|
|
void SetSizingMethod(ENUM_POSITION_SIZING method) { m_sizing_method = method; }
|
|
|
|
void SetFixedLotSize(double size) { m_fixed_lot_size = size; }
|
|
|
|
void SetATRMultipliers(double sl_mult, double tp_mult);
|
|
|
|
void SetTrailingMethod(ENUM_TRAILING_METHOD method) { m_trailing_method = method; }
|
|
|
|
void SetTrailingParams(double start, double step, double distance);
|
|
|
|
void SetBreakevenEnabled(bool enabled) { m_breakeven_enabled = enabled; }
|
|
|
|
void SetBreakevenParams(double trigger, double offset);
|
|
|
|
void SetSlippage(double slippage) { m_slippage = slippage; }
|
|
|
|
void SetComment(string comment) { m_comment = comment; }
|
|
|
|
|
|
|
|
// Trade execution
|
|
|
|
ulong OpenTrade(MqlTradeRequest &request);
|
|
|
|
bool CloseTrade(ulong ticket, string reason = "");
|
|
|
|
bool PartialClose(ulong ticket, double volume, string reason = "");
|
|
|
|
bool ModifyTrade(ulong ticket, double sl, double tp);
|
|
|
|
|
|
|
|
// Position management
|
|
|
|
bool ApplyTrailingStop(ulong ticket);
|
|
|
|
bool CheckBreakeven(ulong ticket);
|
|
|
|
bool MoveToBreakeven(ulong ticket);
|
|
|
|
bool ScaleOut(ulong ticket, double percent, string reason);
|
|
|
|
bool ScaleIn(ulong ticket, double additional_lots);
|
|
|
|
|
|
|
|
// Trade information
|
|
|
|
int GetManagedTrades(TradeInfo &trades[]);
|
|
|
|
int GetOpenTradeCount() const { return m_trade_count; }
|
|
|
|
bool GetTradeInfo(ulong ticket, TradeInfo &trade);
|
|
|
|
double GetTotalExposure();
|
|
|
|
double GetTotalProfit();
|
|
|
|
|
|
|
|
// Performance metrics
|
|
|
|
void GetPerformanceMetrics(PerformanceMetrics &metrics);
|
|
|
|
double GetWinRate() const { return m_performance.win_rate; }
|
|
|
|
double GetProfitFactor() const { return m_performance.profit_factor; }
|
|
|
|
double GetAverageWin() const { return m_performance.average_win; }
|
|
|
|
double GetAverageLoss() const { return m_performance.average_loss; }
|
|
|
|
double GetMaxDrawdown() const { return m_performance.max_drawdown; }
|
|
|
|
|
|
|
|
// Trade monitoring
|
|
|
|
void UpdateAllTrades();
|
|
|
|
void CheckStopLevels();
|
|
|
|
void CheckTakeProfitLevels();
|
|
|
|
|
|
|
|
// Utility
|
|
|
|
void CleanupHistory();
|
|
|
|
void ExportHistory(string filename);
|
|
|
|
void ResetPerformance();
|
|
|
|
};
|
2025-08-22 17:46:10 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Constructor |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
CTradeManager::CTradeManager()
|
|
|
|
{
|
|
|
|
m_utils = NULL;
|
|
|
|
m_risk = NULL;
|
|
|
|
m_tech = NULL;
|
|
|
|
m_external = NULL;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
m_magic_number = 0;
|
|
|
|
m_comment = "";
|
|
|
|
m_slippage = 3;
|
|
|
|
m_sizing_method = SIZING_FIXED_PERCENT;
|
|
|
|
m_fixed_lot_size = 0.1;
|
|
|
|
m_atr_multiplier_sl = 2.0;
|
|
|
|
m_atr_multiplier_tp = 3.0;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
m_trailing_method = TRAIL_NONE;
|
|
|
|
m_trail_start = 30;
|
|
|
|
m_trail_step = 10;
|
|
|
|
m_trail_distance = 20;
|
|
|
|
m_breakeven_enabled = false;
|
|
|
|
m_breakeven_trigger = 20;
|
|
|
|
m_breakeven_offset = 2;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
m_trade_count = 0;
|
|
|
|
ArrayResize(m_managed_trades, 10);
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
m_history_count = 0;
|
|
|
|
ArrayResize(m_history, 100);
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
m_peak_balance = 0;
|
|
|
|
m_last_update = 0;
|
2025-08-27 21:53:01 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
// Initialize performance metrics
|
|
|
|
ZeroMemory(m_performance);
|
|
|
|
}
|
2025-08-20 12:53:47 +01:00
|
|
|
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Destructor |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
CTradeManager::~CTradeManager()
|
2025-08-20 12:53:47 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
ArrayFree(m_managed_trades);
|
|
|
|
ArrayFree(m_history);
|
2025-08-20 12:53:47 +01:00
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Initialize |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::Initialize(CUtilities* utils, CRiskManager* risk,
|
|
|
|
CTechnicalAnalysis* tech,
|
|
|
|
CExternalTradeManager* external, int magic)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(utils == NULL || risk == NULL || tech == NULL) return false;
|
|
|
|
|
|
|
|
m_utils = utils;
|
|
|
|
m_risk = risk;
|
|
|
|
m_tech = tech;
|
|
|
|
m_external = external;
|
|
|
|
m_magic_number = magic;
|
|
|
|
|
|
|
|
// Set trade parameters
|
|
|
|
m_trade.SetExpertMagicNumber(m_magic_number);
|
|
|
|
m_trade.SetDeviationInPoints((ulong)m_slippage);
|
|
|
|
m_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
|
|
|
m_trade.SetAsyncMode(false);
|
|
|
|
|
|
|
|
// Initialize performance tracking
|
|
|
|
m_peak_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
m_last_update = TimeCurrent();
|
|
|
|
|
|
|
|
// Load existing positions
|
|
|
|
UpdateAllTrades();
|
|
|
|
|
|
|
|
m_utils.Log("Trade Manager initialized successfully", LOG_INFO);
|
|
|
|
m_utils.Log(StringFormat("Managing positions with magic: %d", m_magic_number), LOG_INFO);
|
|
|
|
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Set ATR Multipliers |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::SetATRMultipliers(double sl_mult, double tp_mult)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
m_atr_multiplier_sl = sl_mult;
|
|
|
|
m_atr_multiplier_tp = tp_mult;
|
|
|
|
m_utils.Log(StringFormat("ATR multipliers set: SL=%.1f, TP=%.1f",
|
|
|
|
sl_mult, tp_mult), LOG_DEBUG);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Set Trailing Parameters |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::SetTrailingParams(double start, double step, double distance)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
m_trail_start = start;
|
|
|
|
m_trail_step = step;
|
|
|
|
m_trail_distance = distance;
|
|
|
|
m_utils.Log(StringFormat("Trailing params: Start=%.0f, Step=%.0f, Distance=%.0f",
|
|
|
|
start, step, distance), LOG_DEBUG);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Set Breakeven Parameters |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::SetBreakevenParams(double trigger, double offset)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
m_breakeven_trigger = trigger;
|
|
|
|
m_breakeven_offset = offset;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Open Trade |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
ulong CTradeManager::OpenTrade(MqlTradeRequest &request)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
MqlTradeResult result;
|
2025-08-27 21:53:01 +01:00
|
|
|
MqlTradeCheckResult check_result;
|
2025-08-27 19:51:34 +01:00
|
|
|
ZeroMemory(result);
|
2025-08-27 21:53:01 +01:00
|
|
|
ZeroMemory(check_result);
|
2025-08-27 19:51:34 +01:00
|
|
|
|
|
|
|
// Ensure proper filling type
|
|
|
|
request.type_filling = ORDER_FILLING_RETURN;
|
|
|
|
request.deviation = (ulong)m_slippage;
|
|
|
|
|
|
|
|
// Set magic and comment if not already set
|
|
|
|
if(request.magic == 0) request.magic = m_magic_number;
|
|
|
|
if(request.comment == "") request.comment = m_comment;
|
|
|
|
|
|
|
|
// Validate stops
|
2025-08-27 21:53:01 +01:00
|
|
|
if(!m_trade.OrderCheck(request, check_result))
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_utils.Log(StringFormat("Order check failed: %s", check_result.comment), LOG_ERROR);
|
2025-08-27 19:51:34 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute order
|
|
|
|
if(!ExecuteMarketOrder(request, result))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create trade info record
|
|
|
|
if(result.order > 0)
|
|
|
|
{
|
|
|
|
// Wait for position to appear
|
|
|
|
Sleep(100);
|
|
|
|
|
|
|
|
// Find the position
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++)
|
|
|
|
{
|
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
if(PositionGetInteger(POSITION_MAGIC) == m_magic_number &&
|
|
|
|
PositionGetInteger(POSITION_IDENTIFIER) == result.order)
|
|
|
|
{
|
|
|
|
// Create new trade record
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_trade_count >= ArraySize(m_managed_trades))
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
ArrayResize(m_managed_trades, m_trade_count + 10);
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
TradeInfo trade;
|
2025-08-27 19:51:34 +01:00
|
|
|
trade.ticket = ticket;
|
|
|
|
trade.symbol = request.symbol;
|
|
|
|
trade.type = request.type;
|
|
|
|
trade.volume = request.volume;
|
|
|
|
trade.entry_price = result.price;
|
|
|
|
trade.current_price = result.price;
|
|
|
|
trade.sl = request.sl;
|
|
|
|
trade.tp = request.tp;
|
|
|
|
trade.open_time = TimeCurrent();
|
|
|
|
trade.last_update = TimeCurrent();
|
|
|
|
trade.status = TRADE_STATUS_ACTIVE;
|
|
|
|
trade.comment = request.comment;
|
|
|
|
trade.magic = m_magic_number;
|
|
|
|
trade.is_external = false;
|
|
|
|
trade.management_mode = MODE_FULL_CONTROL;
|
2025-08-27 21:53:01 +01:00
|
|
|
trade.profit = 0;
|
|
|
|
trade.swap = 0;
|
|
|
|
trade.commission = 0;
|
|
|
|
trade.close_requested = false;
|
|
|
|
trade.close_reason = "";
|
|
|
|
trade.partial_close_volume = 0;
|
|
|
|
trade.partial_close_reason = "";
|
2025-08-27 19:51:34 +01:00
|
|
|
|
|
|
|
// Calculate risk
|
|
|
|
double stop_distance = MathAbs(result.price - request.sl);
|
|
|
|
trade.risk_amount = m_risk.CalculateTradeRisk(request.volume,
|
|
|
|
stop_distance,
|
|
|
|
request.symbol);
|
|
|
|
trade.risk_percent = (trade.risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100;
|
2025-08-27 21:53:01 +01:00
|
|
|
trade.current_rr = 0;
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[m_trade_count] = trade;
|
2025-08-27 19:51:34 +01:00
|
|
|
m_trade_count++;
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
m_utils.LogTrade(trade);
|
2025-08-27 19:51:34 +01:00
|
|
|
|
|
|
|
return ticket;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.order;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Execute Market Order |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::ExecuteMarketOrder(MqlTradeRequest &request, MqlTradeResult &result)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
// Try to execute order
|
|
|
|
if(!m_trade.OrderSend(request, result))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("OrderSend failed: %d - %s",
|
|
|
|
m_trade.ResultRetcode(),
|
|
|
|
m_trade.ResultComment()), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check result
|
|
|
|
if(result.retcode != TRADE_RETCODE_DONE)
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Order failed: Retcode %d - %s",
|
|
|
|
result.retcode, result.comment), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_utils.Log(StringFormat("Order executed: #%d at %.5f",
|
|
|
|
result.order, result.price), LOG_INFO);
|
|
|
|
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Find Trade Index |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CTradeManager::FindTradeIndex(ulong ticket)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
|
|
|
if(m_managed_trades[i].ticket == ticket)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Close Trade |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::CloseTrade(ulong ticket, string reason)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close position
|
|
|
|
if(!m_trade.PositionClose(ticket, (ulong)m_slippage))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Failed to close position %d: %s",
|
|
|
|
ticket, m_trade.ResultComment()), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update trade record
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].status = TRADE_STATUS_CLOSED;
|
|
|
|
m_managed_trades[index].close_reason = reason;
|
|
|
|
m_managed_trades[index].last_update = TimeCurrent();
|
|
|
|
|
|
|
|
// Record in history
|
|
|
|
RecordTrade(m_managed_trades[index]);
|
|
|
|
|
|
|
|
// Remove from active trades by shifting array
|
|
|
|
for(int j = index; j < m_trade_count - 1; j++)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[j] = m_managed_trades[j + 1];
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
2025-08-27 21:53:01 +01:00
|
|
|
m_trade_count--;
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
m_utils.Log(StringFormat("Position %d closed: %s", ticket, reason), LOG_INFO);
|
|
|
|
|
|
|
|
// Update performance
|
|
|
|
UpdatePerformanceMetrics();
|
|
|
|
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Partial Close |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::PartialClose(ulong ticket, double volume, string reason)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
double current_volume = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
|
|
|
|
if(volume >= current_volume)
|
|
|
|
{
|
|
|
|
// Close entire position
|
|
|
|
return CloseTrade(ticket, reason);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Partial close
|
|
|
|
if(!m_trade.PositionClosePartial(ticket, volume, (ulong)m_slippage))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Failed to partial close %d: %s",
|
|
|
|
ticket, m_trade.ResultComment()), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update trade record
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].volume = current_volume - volume;
|
|
|
|
m_managed_trades[index].partial_close_volume = 0;
|
|
|
|
m_managed_trades[index].partial_close_reason = "";
|
|
|
|
m_managed_trades[index].last_update = TimeCurrent();
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
m_utils.Log(StringFormat("Partial close %d: %.2f lots (%s)",
|
|
|
|
ticket, volume, reason), LOG_INFO);
|
|
|
|
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Modify Trade |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::ModifyTrade(ulong ticket, double sl, double tp)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate stops
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if(sl > current_price - min_stop)
|
|
|
|
{
|
|
|
|
sl = current_price - min_stop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(sl < current_price + min_stop)
|
|
|
|
{
|
|
|
|
sl = current_price + min_stop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate take profit
|
|
|
|
if(tp > 0)
|
|
|
|
{
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
if(tp < current_price + min_stop)
|
|
|
|
{
|
|
|
|
tp = current_price + min_stop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(tp > current_price - min_stop)
|
|
|
|
{
|
|
|
|
tp = current_price - min_stop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modify position
|
|
|
|
if(!m_trade.PositionModify(ticket, sl, tp))
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Failed to modify position %d: %s",
|
|
|
|
ticket, m_trade.ResultComment()), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update trade record
|
2025-08-27 21:53:01 +01:00
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].sl = sl;
|
|
|
|
m_managed_trades[index].tp = tp;
|
|
|
|
m_managed_trades[index].last_update = TimeCurrent();
|
|
|
|
|
|
|
|
// Recalculate risk
|
|
|
|
double stop_distance = MathAbs(m_managed_trades[index].entry_price - sl);
|
|
|
|
m_managed_trades[index].risk_amount = m_risk.CalculateTradeRisk(
|
|
|
|
m_managed_trades[index].volume, stop_distance, symbol);
|
|
|
|
m_managed_trades[index].risk_percent =
|
|
|
|
(m_managed_trades[index].risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100;
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
m_utils.Log(StringFormat("Position %d modified: SL=%.5f, TP=%.5f",
|
|
|
|
ticket, sl, tp), LOG_DEBUG);
|
|
|
|
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Apply Trailing Stop |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::ApplyTrailingStop(ulong ticket)
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(m_trailing_method == TRAIL_NONE) return false;
|
|
|
|
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
|
|
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
|
|
|
|
double new_sl = 0;
|
|
|
|
|
|
|
|
switch(m_trailing_method)
|
|
|
|
{
|
|
|
|
case TRAIL_FIXED_POINTS:
|
|
|
|
{
|
|
|
|
double trail_distance = m_trail_distance * point;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = current_price - trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl > current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_sl = current_price + trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl < current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2025-08-22 17:46:10 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
case TRAIL_PERCENT:
|
|
|
|
{
|
|
|
|
double trail_distance = current_price * m_trail_distance / 100;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = current_price - trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl > current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_sl = current_price + trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl < current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TRAIL_ATR:
|
|
|
|
{
|
|
|
|
double atr = m_tech.GetATR();
|
|
|
|
double trail_distance = atr * (m_trail_distance / 10); // Normalize
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = current_price - trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl > current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_sl = current_price + trail_distance;
|
|
|
|
if(current_sl == 0 || new_sl < current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TRAIL_PARABOLIC_SAR:
|
|
|
|
{
|
|
|
|
int sar_handle = iSAR(symbol, PERIOD_CURRENT, 0.02, 0.2);
|
|
|
|
if(sar_handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
double sar_buffer[1];
|
|
|
|
if(CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0)
|
|
|
|
{
|
|
|
|
new_sl = sar_buffer[0];
|
|
|
|
IndicatorRelease(sar_handle);
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY && (current_sl == 0 || new_sl > current_sl))
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
else if(type == ORDER_TYPE_SELL && (current_sl == 0 || new_sl < current_sl))
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IndicatorRelease(sar_handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TRAIL_MOVING_AVERAGE:
|
|
|
|
{
|
|
|
|
double ma = m_tech.GetMA();
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY && (current_sl == 0 || ma > current_sl))
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, ma, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
else if(type == ORDER_TYPE_SELL && (current_sl == 0 || ma < current_sl))
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, ma, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TRAIL_STEPPED:
|
|
|
|
{
|
|
|
|
double profit_points = (current_price - entry_price) / point;
|
|
|
|
if(type == ORDER_TYPE_SELL)
|
|
|
|
profit_points = (entry_price - current_price) / point;
|
|
|
|
|
|
|
|
if(profit_points >= m_trail_start)
|
|
|
|
{
|
|
|
|
int steps = (int)(profit_points / m_trail_step);
|
|
|
|
double new_trail = steps * m_trail_step * point;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = entry_price + new_trail - m_trail_distance * point;
|
|
|
|
if(current_sl == 0 || new_sl > current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_sl = entry_price - new_trail + m_trail_distance * point;
|
|
|
|
if(current_sl == 0 || new_sl < current_sl)
|
|
|
|
{
|
|
|
|
return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Check Breakeven |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::CheckBreakeven(ulong ticket)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!m_breakeven_enabled) return false;
|
|
|
|
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
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);
|
|
|
|
|
|
|
|
double profit_points = 0;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
profit_points = (current_price - entry_price) / point;
|
|
|
|
|
|
|
|
if(profit_points >= m_breakeven_trigger)
|
|
|
|
{
|
|
|
|
double new_sl = entry_price + m_breakeven_offset * point;
|
|
|
|
|
|
|
|
if(current_sl < new_sl)
|
|
|
|
{
|
|
|
|
return MoveToBreakeven(ticket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
profit_points = (entry_price - current_price) / point;
|
|
|
|
|
|
|
|
if(profit_points >= m_breakeven_trigger)
|
|
|
|
{
|
|
|
|
double new_sl = entry_price - m_breakeven_offset * point;
|
2025-08-08 20:32:34 +01:00
|
|
|
|
2025-08-27 19:51:34 +01:00
|
|
|
if(current_sl == 0 || current_sl > new_sl)
|
|
|
|
{
|
|
|
|
return MoveToBreakeven(ticket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Move to Breakeven |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::MoveToBreakeven(ulong ticket)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
|
|
|
|
double new_sl = 0;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
new_sl = entry_price + m_breakeven_offset * point;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_sl = entry_price - m_breakeven_offset * point;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool result = ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
|
|
|
|
|
|
|
if(result)
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Position %d moved to breakeven at %.5f",
|
|
|
|
ticket, new_sl), LOG_INFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Get Managed Trades |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
int CTradeManager::GetManagedTrades(TradeInfo &trades[])
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
UpdateAllTrades();
|
|
|
|
|
|
|
|
ArrayResize(trades, m_trade_count);
|
|
|
|
|
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
trades[i] = m_managed_trades[i];
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return m_trade_count;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Update All Trades |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::UpdateAllTrades()
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
// Update existing trades
|
|
|
|
for(int i = m_trade_count - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if(!UpdateTradeInfo(i))
|
|
|
|
{
|
|
|
|
// Trade closed
|
2025-08-27 21:53:01 +01:00
|
|
|
RecordTrade(m_managed_trades[i]);
|
|
|
|
|
|
|
|
// Shift array
|
|
|
|
for(int j = i; j < m_trade_count - 1; j++)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[j] = m_managed_trades[j + 1];
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
2025-08-27 21:53:01 +01:00
|
|
|
m_trade_count--;
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for new positions
|
|
|
|
for(int i = 0; i < PositionsTotal(); i++)
|
|
|
|
{
|
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
if(ticket == 0) continue;
|
|
|
|
|
|
|
|
if(PositionGetInteger(POSITION_MAGIC) != m_magic_number)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check if already tracked
|
2025-08-27 21:53:01 +01:00
|
|
|
if(FindTradeIndex(ticket) >= 0) continue;
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
// Add new trade
|
|
|
|
if(m_trade_count >= ArraySize(m_managed_trades))
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
ArrayResize(m_managed_trades, m_trade_count + 10);
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
2025-08-27 21:53:01 +01:00
|
|
|
|
|
|
|
TradeInfo trade;
|
|
|
|
trade.ticket = ticket;
|
|
|
|
trade.symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
trade.type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
trade.volume = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
trade.entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
trade.current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
trade.sl = PositionGetDouble(POSITION_SL);
|
|
|
|
trade.tp = PositionGetDouble(POSITION_TP);
|
|
|
|
trade.profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
trade.swap = PositionGetDouble(POSITION_SWAP);
|
|
|
|
trade.commission = 0; // Not available directly
|
|
|
|
trade.open_time = (datetime)PositionGetInteger(POSITION_TIME);
|
|
|
|
trade.last_update = TimeCurrent();
|
|
|
|
trade.status = TRADE_STATUS_ACTIVE;
|
|
|
|
trade.comment = PositionGetString(POSITION_COMMENT);
|
|
|
|
trade.magic = m_magic_number;
|
|
|
|
trade.is_external = false;
|
|
|
|
trade.management_mode = MODE_FULL_CONTROL;
|
|
|
|
trade.close_requested = false;
|
|
|
|
trade.close_reason = "";
|
|
|
|
trade.partial_close_volume = 0;
|
|
|
|
trade.partial_close_reason = "";
|
|
|
|
trade.risk_amount = 0;
|
|
|
|
trade.risk_percent = 0;
|
|
|
|
trade.current_rr = 0;
|
|
|
|
|
|
|
|
m_managed_trades[m_trade_count] = trade;
|
|
|
|
m_trade_count++;
|
|
|
|
|
|
|
|
m_utils.Log(StringFormat("New position detected: %d", ticket), LOG_INFO);
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
2025-08-08 20:32:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Update Trade Info |
|
2025-08-08 20:32:34 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::UpdateTradeInfo(int index)
|
2025-08-08 20:32:34 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(index < 0 || index >= m_trade_count) return false;
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
ulong ticket = m_managed_trades[index].ticket;
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
|
|
|
// Position closed
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].status = TRADE_STATUS_CLOSED;
|
|
|
|
m_managed_trades[index].last_update = TimeCurrent();
|
2025-08-27 19:51:34 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update current values
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
m_managed_trades[index].profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
m_managed_trades[index].swap = PositionGetDouble(POSITION_SWAP);
|
|
|
|
m_managed_trades[index].sl = PositionGetDouble(POSITION_SL);
|
|
|
|
m_managed_trades[index].tp = PositionGetDouble(POSITION_TP);
|
|
|
|
m_managed_trades[index].volume = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
m_managed_trades[index].last_update = TimeCurrent();
|
2025-08-27 19:51:34 +01:00
|
|
|
|
|
|
|
// Update risk metrics
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_managed_trades[index].sl > 0)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
double stop_distance = MathAbs(m_managed_trades[index].entry_price -
|
|
|
|
m_managed_trades[index].sl);
|
|
|
|
m_managed_trades[index].risk_amount = m_risk.CalculateTradeRisk(
|
|
|
|
m_managed_trades[index].volume, stop_distance, m_managed_trades[index].symbol);
|
|
|
|
m_managed_trades[index].risk_percent =
|
|
|
|
(m_managed_trades[index].risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100;
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_managed_trades[index].tp > 0)
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
m_managed_trades[index].current_rr = m_utils.CalculateRiskReward(
|
|
|
|
m_managed_trades[index].entry_price,
|
|
|
|
m_managed_trades[index].sl,
|
|
|
|
m_managed_trades[index].tp,
|
|
|
|
m_managed_trades[index].type);
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2025-08-08 20:32:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Record Trade in History |
|
2025-08-08 20:32:34 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::RecordTrade(const TradeInfo &trade)
|
2025-08-08 20:32:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
if(m_history_count >= ArraySize(m_history))
|
2025-08-27 19:51:34 +01:00
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
ArrayResize(m_history, m_history_count + 100);
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
m_history[m_history_count] = trade;
|
2025-08-27 19:51:34 +01:00
|
|
|
m_history_count++;
|
|
|
|
|
|
|
|
// Update performance metrics
|
|
|
|
UpdatePerformanceMetrics();
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Update Performance Metrics |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::UpdatePerformanceMetrics()
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
// Reset metrics
|
|
|
|
ZeroMemory(m_performance);
|
|
|
|
|
|
|
|
double total_wins = 0;
|
|
|
|
double total_losses = 0;
|
|
|
|
int win_count = 0;
|
|
|
|
int loss_count = 0;
|
|
|
|
|
|
|
|
// Analyze history
|
|
|
|
for(int i = 0; i < m_history_count; i++)
|
|
|
|
{
|
|
|
|
m_performance.total_trades++;
|
|
|
|
|
|
|
|
if(m_history[i].profit > 0)
|
|
|
|
{
|
|
|
|
m_performance.winning_trades++;
|
|
|
|
total_wins += m_history[i].profit;
|
|
|
|
win_count++;
|
|
|
|
}
|
|
|
|
else if(m_history[i].profit < 0)
|
|
|
|
{
|
|
|
|
m_performance.losing_trades++;
|
|
|
|
total_losses += MathAbs(m_history[i].profit);
|
|
|
|
loss_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_performance.total_profit += m_history[i].profit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate metrics
|
|
|
|
if(m_performance.total_trades > 0)
|
|
|
|
{
|
|
|
|
m_performance.win_rate = (double)m_performance.winning_trades /
|
|
|
|
m_performance.total_trades * 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(win_count > 0)
|
|
|
|
{
|
|
|
|
m_performance.average_win = total_wins / win_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(loss_count > 0)
|
|
|
|
{
|
|
|
|
m_performance.average_loss = total_losses / loss_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(total_losses > 0)
|
|
|
|
{
|
|
|
|
m_performance.profit_factor = total_wins / total_losses;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate drawdown
|
|
|
|
double current_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
if(current_balance > m_peak_balance)
|
|
|
|
{
|
|
|
|
m_peak_balance = current_balance;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_performance.current_drawdown = ((m_peak_balance - current_balance) / m_peak_balance) * 100;
|
|
|
|
m_performance.max_drawdown = MathMax(m_performance.max_drawdown,
|
|
|
|
m_performance.current_drawdown);
|
|
|
|
|
|
|
|
// Expected value
|
|
|
|
if(m_performance.total_trades > 0)
|
|
|
|
{
|
|
|
|
m_performance.expected_value = m_performance.total_profit /
|
|
|
|
m_performance.total_trades;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sharpe ratio (simplified)
|
|
|
|
if(m_performance.average_loss > 0)
|
|
|
|
{
|
|
|
|
double avg_return = m_performance.expected_value;
|
|
|
|
double std_dev = MathSqrt(MathPow(m_performance.average_win - avg_return, 2) *
|
|
|
|
m_performance.win_rate / 100 +
|
|
|
|
MathPow(m_performance.average_loss + avg_return, 2) *
|
|
|
|
(1 - m_performance.win_rate / 100));
|
|
|
|
|
|
|
|
if(std_dev > 0)
|
|
|
|
{
|
|
|
|
m_performance.sharpe_ratio = avg_return / std_dev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recovery factor
|
|
|
|
if(m_performance.max_drawdown > 0)
|
|
|
|
{
|
|
|
|
m_performance.recovery_factor = m_performance.total_profit /
|
|
|
|
m_performance.max_drawdown;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_performance.last_update = TimeCurrent();
|
|
|
|
|
|
|
|
// Update risk manager with performance data
|
|
|
|
m_risk.UpdatePerformanceMetrics(m_performance.win_rate,
|
|
|
|
m_performance.average_win,
|
|
|
|
m_performance.average_loss);
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Get Performance Metrics |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::GetPerformanceMetrics(PerformanceMetrics &metrics)
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
UpdatePerformanceMetrics();
|
|
|
|
metrics = m_performance;
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Get Total Exposure |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
double CTradeManager::GetTotalExposure()
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
double total = 0;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
total += m_managed_trades[i].volume;
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Get Total Profit |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
double CTradeManager::GetTotalProfit()
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
double total = 0;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
2025-08-27 21:53:01 +01:00
|
|
|
total += m_managed_trades[i].profit;
|
2025-08-27 19:51:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get Trade Info |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTradeManager::GetTradeInfo(ulong ticket, TradeInfo &trade)
|
|
|
|
{
|
|
|
|
int index = FindTradeIndex(ticket);
|
|
|
|
if(index >= 0)
|
|
|
|
{
|
|
|
|
trade = m_managed_trades[index];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Cleanup History |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::CleanupHistory()
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
// Keep only last 1000 trades in memory
|
|
|
|
if(m_history_count > 1000)
|
|
|
|
{
|
|
|
|
int to_remove = m_history_count - 1000;
|
|
|
|
|
|
|
|
// Shift remaining records
|
|
|
|
for(int i = 0; i < 1000; i++)
|
|
|
|
{
|
|
|
|
m_history[i] = m_history[i + to_remove];
|
|
|
|
}
|
|
|
|
|
|
|
|
m_history_count = 1000;
|
|
|
|
|
|
|
|
m_utils.Log("Trade history cleaned up", LOG_DEBUG);
|
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Export History |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::ExportHistory(string filename)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(filename == "")
|
|
|
|
{
|
|
|
|
filename = StringFormat("TradeHistory_%s.csv",
|
|
|
|
TimeToString(TimeCurrent(), TIME_DATE));
|
|
|
|
}
|
|
|
|
|
2025-08-27 21:53:01 +01:00
|
|
|
TradeInfo history_array[];
|
|
|
|
ArrayResize(history_array, m_history_count);
|
|
|
|
for(int i = 0; i < m_history_count; i++)
|
|
|
|
{
|
|
|
|
history_array[i] = m_history[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
m_utils.SaveTradeHistory(history_array, m_history_count);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Reset Performance |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::ResetPerformance()
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
ZeroMemory(m_performance);
|
|
|
|
m_peak_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
m_last_update = TimeCurrent();
|
|
|
|
|
|
|
|
// Clear history
|
|
|
|
m_history_count = 0;
|
|
|
|
|
|
|
|
m_utils.Log("Performance metrics reset", LOG_INFO);
|
2025-08-08 20:32:34 +01:00
|
|
|
}
|
|
|
|
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Calculate Stop Loss |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
double CTradeManager::CalculateStopLoss(ENUM_ORDER_TYPE type, double entry, double atr)
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
double sl = 0;
|
|
|
|
double stop_distance = atr * m_atr_multiplier_sl;
|
|
|
|
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
sl = entry - stop_distance;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sl = entry + stop_distance;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_utils.NormalizePrice(sl, _Symbol);
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Calculate Take Profit |
|
2025-08-22 17:46:10 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
double CTradeManager::CalculateTakeProfit(ENUM_ORDER_TYPE type, double entry, double sl)
|
2025-08-22 17:46:10 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(sl == 0) return 0;
|
|
|
|
|
|
|
|
double risk = MathAbs(entry - sl);
|
|
|
|
double reward = risk * m_atr_multiplier_tp / m_atr_multiplier_sl;
|
|
|
|
|
|
|
|
double tp = 0;
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
|
|
{
|
|
|
|
tp = entry + reward;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tp = entry - reward;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_utils.NormalizePrice(tp, _Symbol);
|
2025-08-22 17:46:10 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Scale Out Position |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::ScaleOut(ulong ticket, double percent, string reason)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
double current_volume = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
double close_volume = current_volume * percent / 100;
|
|
|
|
|
|
|
|
return PartialClose(ticket, close_volume, reason);
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Scale In Position |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
bool CTradeManager::ScaleIn(ulong ticket, double additional_lots)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Create new position in same direction
|
|
|
|
MqlTradeRequest request = {};
|
|
|
|
request.symbol = PositionGetString(POSITION_SYMBOL);
|
|
|
|
request.type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
request.volume = additional_lots;
|
|
|
|
request.magic = m_magic_number;
|
|
|
|
request.comment = "Scale In";
|
|
|
|
|
|
|
|
// Use same stops as original
|
|
|
|
request.sl = PositionGetDouble(POSITION_SL);
|
|
|
|
request.tp = PositionGetDouble(POSITION_TP);
|
|
|
|
|
|
|
|
ulong new_ticket = OpenTrade(request);
|
|
|
|
|
|
|
|
if(new_ticket > 0)
|
|
|
|
{
|
|
|
|
m_utils.Log(StringFormat("Scaled in position %d with %.2f lots",
|
|
|
|
ticket, additional_lots), LOG_INFO);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Check Stop Levels |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::CheckStopLevels()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
|
|
|
// Check if stop loss needs adjustment
|
|
|
|
if(m_managed_trades[i].sl == 0)
|
|
|
|
{
|
|
|
|
// Apply default stop
|
|
|
|
double atr = m_tech.GetATR();
|
2025-08-27 21:53:01 +01:00
|
|
|
double sl = CalculateStopLoss(m_managed_trades[i].type,
|
|
|
|
m_managed_trades[i].entry_price, atr);
|
2025-08-27 19:51:34 +01:00
|
|
|
ModifyTrade(m_managed_trades[i].ticket, sl, m_managed_trades[i].tp);
|
|
|
|
}
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
//| Check Take Profit Levels |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 19:51:34 +01:00
|
|
|
void CTradeManager::CheckTakeProfitLevels()
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 19:51:34 +01:00
|
|
|
for(int i = 0; i < m_trade_count; i++)
|
|
|
|
{
|
|
|
|
// Check if take profit needs adjustment
|
|
|
|
if(m_managed_trades[i].tp == 0 && m_managed_trades[i].sl != 0)
|
|
|
|
{
|
|
|
|
// Apply default TP based on SL
|
2025-08-27 21:53:01 +01:00
|
|
|
double tp = CalculateTakeProfit(m_managed_trades[i].type,
|
|
|
|
m_managed_trades[i].entry_price,
|
|
|
|
m_managed_trades[i].sl);
|
2025-08-27 19:51:34 +01:00
|
|
|
ModifyTrade(m_managed_trades[i].ticket, m_managed_trades[i].sl, tp);
|
|
|
|
}
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
2025-08-27 19:51:34 +01:00
|
|
|
|
2025-08-22 17:46:10 +01:00
|
|
|
#endif // TRADE_MANAGER_MQH
|