MQLArticles/PosMgmt/Partials.mqh

499 lines
43 KiB
MQL5

2025-11-10 09:31:32 -05:00
<EFBFBD><EFBFBD>//+------------------------------------------------------------------+
//| Partials.mqh |
//| Copyright 2025, Niquel Mendoza. |
//| https://www.mql5.com/es/users/nique_372/news |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Niquel Mendoza."
2025-11-12 16:03:11 -05:00
#property link "https://www.mql5.com/es/users/nique_372/news"
#property strict
2025-11-10 09:31:32 -05:00
#ifndef MQLARTICLES_POSMGMT_PARTIALS_MQH
#define MQLARTICLES_POSMGMT_PARTIALS_MQH
//+------------------------------------------------------------------+
//| Include |
//+------------------------------------------------------------------+
#include "..\\RM\\RiskManagement.mqh"
//+------------------------------------------------------------------+
//| Structures |
//+------------------------------------------------------------------+
struct PartialTakeProfit
{
double partial_price;
double tp_level_percentage;
double volume_percentage_to_close;
inline void Reset()
{
2025-11-28 17:21:07 -05:00
ZeroMemory(this);
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
}
2025-11-10 09:31:32 -05:00
};
struct TrackedPosition
{
PartialTakeProfit tp_levels[];
ulong ticket;
ENUM_POSITION_TYPE type;
int current_partial_index;
};
//+------------------------------------------------------------------+
//| Defines |
2026-01-29 08:19:47 -05:00
2025-11-10 09:31:32 -05:00
//+------------------------------------------------------------------+
#define CPARCIAL_RESERVE_ARR 5
#define CPARCIAL_RESERVE_TO_DELETE 5
#define PARTIAL_NO_APPLIED "0"
//+------------------------------------------------------------------+
//| Class that implements partial closures |
//+------------------------------------------------------------------+
2025-11-28 17:21:07 -05:00
class CPartials : public CAccountGestor
2025-11-10 09:31:32 -05:00
{
2025-11-28 17:21:07 -05:00
private:
//--- General variables
ulong m_magic_number; // Magic number used to apply partials only to certain operations
CTrade* m_trade_executor; // CTrade type instance to apply partials
bool m_disable_partials_flag; // Boolean that indicates if partials can be executed
bool m_auto;
2025-11-10 09:31:32 -05:00
//--- Symbol variables
2026-01-29 08:19:47 -05:00
string m_trading_symbol; // Symbol from which minimum volume and volume step data will be obtained
double m_volume_step; // Volume step
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
double m_minimum_volume; // Minimum volume
2025-11-28 17:21:07 -05:00
2026-01-29 08:19:47 -05:00
int m_price_digits; // Symbol digits
2025-11-10 09:31:32 -05:00
//--- Variables for partials
2026-01-29 08:19:47 -05:00
int m_max_partial_levels; // Number of partials to take from each operation
TrackedPosition m_tracked_positions[]; // Array of operations to which partials will be applied
2025-11-28 17:21:07 -05:00
int m_indices_to_remove[]; // Indices to remove from the m_tracked_positions array
2025-11-10 09:31:32 -05:00
//--- Arrays to take, both arrays must be the same size.
double m_volume_percentages_to_close[];
double m_tp_level_percentages[];
//--- Temporary hashmap
CHashMap<double, double> m_temporary_hashmap;
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Function to add a new partial to the internal array
void AddToTrackedPositions(TrackedPosition & new_tracked_position);
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
public:
CPartials(bool is_global_instance);
~CPartials();
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Initialize function
bool Init(ulong magic_number_, string symbol_, string volume_percentages_string, string tp_percentages_string);
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Main function for position review
void CheckTrackedPositions();
//--- Function that will be automatically invoked by account status each time a position is opened or closed
void OnOpenClosePosition(const ROnOpenClosePosition &pos) override;
//--- Function to add a position to the internal array manually (this is automatically invoked in OnOpen..... for all positions
2025-11-28 17:21:07 -05:00
// opened with the magic number and with a valid tp).
2025-11-10 09:31:32 -05:00
bool AddPositionToTrack(ulong position_ticket, double position_tp, double entry_price, ENUM_POSITION_TYPE position_type, string symbol);
2025-11-28 17:21:07 -05:00
bool AddPositionToTrack(ulong position_ticket);
2025-11-10 09:31:32 -05:00
//--- Function that returns true if partials are allowed to be used
inline bool IsPartialsEnabled() const { return m_disable_partials_flag == false; }
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//---
// Auto mode
inline bool AutoMode() const { return m_auto; }
void AutoMode(bool auto) { m_auto = auto; }
2025-11-28 17:21:07 -05:00
// Symbol
2025-11-10 09:31:32 -05:00
inline string Symbol() const { return m_trading_symbol; }
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
// Magic
inline ulong MagicNumber() const { return m_magic_number; }
};
//+------------------------------------------------------------------+
//| Constructor and Destructor |
//+------------------------------------------------------------------+
CPartials::CPartials(bool is_global_instance)
2025-11-28 17:21:07 -05:00
: CAccountGestor(is_global_instance),
2025-11-10 09:31:32 -05:00
m_magic_number(NOT_MAGIC_NUMBER), m_disable_partials_flag(true), m_trading_symbol(NULL), m_volume_step(0.00),
m_minimum_volume(0.00), m_price_digits(0), m_max_partial_levels(0), m_auto(true)
{
m_trade_executor = new CTrade();
account_status.RegisterEvent(&this, ACCOUNT_STATUS_REG_FLAG_ON_OPEN_CLOSE_POSITION);
}
//+------------------------------------------------------------------+
CPartials::~CPartials()
2025-11-28 17:21:07 -05:00
{
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
if(!m_is_global)
2025-11-10 09:31:32 -05:00
account_status.UnregisterEvent(&this, ACCOUNT_STATUS_REG_FLAG_ON_OPEN_CLOSE_POSITION);
if(::CheckPointer(m_trade_executor) == POINTER_DYNAMIC)
delete m_trade_executor;
}
//+------------------------------------------------------------------+
//| Initialization function |
//+------------------------------------------------------------------+
bool CPartials::Init(ulong magic_number_, string symbol_, string volume_percentages_string, string tp_percentages_string)
2025-11-28 17:21:07 -05:00
{
2025-11-10 09:31:32 -05:00
//--- Validate if the strings are valid
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
if(volume_percentages_string == PARTIAL_NO_APPLIED || tp_percentages_string == PARTIAL_NO_APPLIED) // "0" as an invalid value
{
m_disable_partials_flag = true;
return false;
2025-11-28 17:21:07 -05:00
}
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
//--- Set the variables
m_disable_partials_flag = false;
m_magic_number = magic_number_;
m_trading_symbol = symbol_;
2025-11-10 09:31:32 -05:00
m_volume_step = ::SymbolInfoDouble(m_trading_symbol, SYMBOL_VOLUME_STEP);
m_minimum_volume = ::SymbolInfoDouble(m_trading_symbol, SYMBOL_VOLUME_MIN);
2025-11-28 17:21:07 -05:00
m_price_digits = (int)::SymbolInfoInteger(m_trading_symbol, SYMBOL_DIGITS);
2025-11-10 09:31:32 -05:00
m_trade_executor.SetExpertMagicNumber(m_magic_number);
//--- Initial resize
2025-11-28 17:21:07 -05:00
::ArrayResize(m_tracked_positions, 0, CPARCIAL_RESERVE_ARR);
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
::ArrayResize(m_indices_to_remove, 0, CPARCIAL_RESERVE_TO_DELETE);
2025-11-10 09:31:32 -05:00
//--- Convert strings to double arrays
StrTo::CstArray(m_volume_percentages_to_close, volume_percentages_string, ',');
StrTo::CstArray(m_tp_level_percentages, tp_percentages_string, ',');
//--- 2nd validation, we validate that the array size is valid
if(m_volume_percentages_to_close.Size() < 1 || m_tp_level_percentages.Size() < 1)
{
LogCriticalError(::StringFormat("The size of the partial arrays to take %u or percentages to take %u are invalid", m_volume_percentages_to_close.Size(), m_tp_level_percentages.Size()), FUNCION_ACTUAL);
2025-11-28 17:21:07 -05:00
m_disable_partials_flag = true;
2025-11-10 09:31:32 -05:00
return false;
}
2025-11-28 17:21:07 -05:00
//--- 3rd validation, we validate that both arrays are the same size
if(m_volume_percentages_to_close.Size() != m_tp_level_percentages.Size())
{
LogCriticalError(::StringFormat("The size of the partial arrays to take %u and percentages to take %u are not equal", m_volume_percentages_to_close.Size(), m_tp_level_percentages.Size()), FUNCION_ACTUAL);
m_disable_partials_flag = true;
2025-11-10 09:31:32 -05:00
return false;
}
//--- Initial log only if "info" type log is enabled
if(IsInfoLogEnabled())
{
FastLog(FUNCION_ACTUAL, INFO_TEXT, "Arrays before revision");
PrintArrayAsTable(m_tp_level_percentages, "Percentage of position where partials will be taken", "percentages simplified");
PrintArrayAsTable(m_volume_percentages_to_close, "Percentage to take", "percentages simplified");
2026-01-29 10:49:55 -05:00
}
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Declaration of indices to remove and hashmap cleanup
2025-11-28 17:21:07 -05:00
m_temporary_hashmap.Clear();
2025-11-10 09:31:32 -05:00
int indices_to_remove_temp[];
//--- Iteration and array cleanup
2026-01-29 10:49:55 -05:00
2025-11-10 09:31:32 -05:00
for(int i = 0; i < ::ArraySize(m_tp_level_percentages); i++)
2025-11-28 17:21:07 -05:00
{
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
if(m_tp_level_percentages[i] <= 0.00)
2025-11-10 09:31:32 -05:00
{
2025-11-28 17:21:07 -05:00
LogWarning(::StringFormat("The percentage where partials will be taken %f with index %d is invalid, therefore it will not be considered in partials", m_tp_level_percentages[i], i), FUNCION_ACTUAL);
2025-11-10 09:31:32 -05:00
AddArrayNoVerification(indices_to_remove_temp, i, 0);
continue;
2025-11-28 17:21:07 -05:00
}
2025-11-10 09:31:32 -05:00
if(m_volume_percentages_to_close[i] <= 0.00)
{
LogWarning(::StringFormat("The percentage to take %f with index %d is invalid, therefore it will not be considered in partials", m_volume_percentages_to_close[i], i), FUNCION_ACTUAL);
AddArrayNoVerification(indices_to_remove_temp, i, 0);
continue;
}
if(m_temporary_hashmap.ContainsKey(m_tp_level_percentages[i]))
AddArrayNoVerification(indices_to_remove_temp, i, 0);
else
2026-01-29 10:49:55 -05:00
m_temporary_hashmap.Add(m_tp_level_percentages[i], m_volume_percentages_to_close[i]);
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
}
2025-11-10 09:31:32 -05:00
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Sort and removal
2025-11-28 17:21:07 -05:00
RemoveMultipleIndexes(m_tp_level_percentages, indices_to_remove_temp, 0);
2025-11-10 09:31:32 -05:00
::ArraySort(m_tp_level_percentages);
::ArrayResize(m_volume_percentages_to_close, ::ArraySize(m_tp_level_percentages));
2025-11-28 17:21:07 -05:00
2025-11-10 09:31:32 -05:00
//--- Final iteration
for(int i = 0; i < ::ArraySize(m_tp_level_percentages); i++)
{
double val;
m_temporary_hashmap.TryGetValue(m_tp_level_percentages[i], val);
m_volume_percentages_to_close[i] = val / 100.0;
m_tp_level_percentages[i] /= 100.0;
}
//--- Final log
if(IsInfoLogEnabled())
{
FastLog(FUNCION_ACTUAL, INFO_TEXT, "Arrays after revision");
PrintArrayAsTable(m_tp_level_percentages, "Percentage of position where partials will be taken", "percentages simplified");
2025-11-28 17:21:07 -05:00
PrintArrayAsTable(m_volume_percentages_to_close, "Percentage to take", "percentages simplified");
2025-11-10 09:31:32 -05:00
}
m_max_partial_levels = ::ArraySize(m_volume_percentages_to_close);
return true;
}