//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, Niquel Mendoza. | //| https://www.mql5.com/es/users/nique_372/news | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Niquel Mendoza." #property link "https://www.mql5.com/es/users/nique_372/news" #property strict #ifndef MQLARTICLES_POSMGMT_CONDPARTIALS_BASE_BASE_MQH #define MQLARTICLES_POSMGMT_CONDPARTIALS_BASE_BASE_MQH //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include "Defines.mqh" //+------------------------------------------------------------------+ //| Base class for conditional partials implementation | //+------------------------------------------------------------------+ class CConditionalPartials : public CAccountGestor { protected: //--- General IConditionPartial* m_partial_condition; // Pointer to IConditionPartial, this pointer will give the condition to close buy and sell positions partially CTrade m_trade; // CTrade instance, to apply partial closes //--- Tracked Positions // Array of ConditionalPartialTrackedPosition type, this array will store the positions // To which partial closes will be applied ConditionalPartialTrackedPosition m_tracked_positions_arr[]; int m_tracked_positions_size; // Number of positions being tracked int m_indexes_to_remove[]; // Indices to be removed from tracked positions array //--- Symbol string m_symbol; // Symbol from which data like min_volume, step, etc. will be obtained double m_symbol_bid; // Symbol bid double m_symbol_ask; // Symbol ask double m_symbol_min_volume; // Minimum volume (opening or closing) for symbol m_symbol double m_symbol_volume_step; // Volume step for symbol m_symbol int8_t m_symbol_digits; // Digits of symbol "m_symbol" //--- Parameters // Boolean that will indicate to the class if when trades are opened (with the magic number, or magic number = 0), they will be added // Automatically to the trackable positions array bool m_auto_add_to_tracked_positions; CDiff* m_min_distance_to_close_pos; // Pointer to CDiff, this will give the minimum distance between partial closes ulong m_magic_number; // Magic number double m_volumes_to_close_arr[]; // Array of percentages, that will close the trades in m_tracked_positions_arr int m_volume_to_close_size; // Number of partial closes planned for each trade //--- Min distance double m_min_distance_value; // Minimum distance between partials (value) //--- Symbol Functions void RefreshSymbolData(); //--- Partials closures Functions virtual void ClosePartialsForBuyPositions(); virtual void ClosePartialsForSellPositions(); public: CConditionalPartials(void); ~CConditionalPartials(void); //--- Initialization function bool Init(ConditionalPartialConfig& config); //--- Add function virtual void AddPositionToTrack(ENUM_POSITION_TYPE position_type, ulong position_ticket, double open_price); bool AddPositionToTrack(ulong position_ticket); //--- Remove bool RemoveTrackedPosition(ulong position_ticket); //--- Events functions void OnOpenClosePosition(const ROnOpenClosePosition &pos) override final; void OnNewDay(datetime init_time) override final { m_partial_condition.OnNewDay(); } void OnNewWeek(datetime init_time) override final { m_partial_condition.OnNewWeek(); } //--- Execute function void Execute(datetime current_time); // OnNewBar //--- Getter and Setters // Condition IConditionPartial* PartialCloseCondition() { return m_partial_condition; } void PartialCloseCondition(IConditionPartial* new_condition, bool delete_prev_ptr); // Magic number ulong MagicNumber() const { return m_magic_number; } void MagicNumber(ulong new_value); // Minimum distance between partial closures CDiff* MinDistBetweenPartialClosures() { return m_min_distance_to_close_pos; } void MinDistBetweenPartialClosures(CDiff* new_value, bool delete_prev_ptr); // Symbol string Symbol() { return m_symbol; } void Symbol(string new_symbol); // Auto mode bool IsAutoMode() const { return m_auto_add_to_tracked_positions; } void AutoMode(bool new_value) { m_auto_add_to_tracked_positions = new_value; } //--- Tracked positions size int TrackedPositionsSize() const { return m_tracked_positions_size; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CConditionalPartials::CConditionalPartials(void) : m_partial_condition(NULL), m_tracked_positions_size(0), m_symbol(NULL), m_symbol_ask(0.00), m_symbol_bid(0.00), m_symbol_digits(0), m_symbol_min_volume(0.00), m_symbol_volume_step(0.00), m_auto_add_to_tracked_positions(true), m_min_distance_to_close_pos(NULL), m_magic_number(NOT_MAGIC_NUMBER), m_volume_to_close_size(0), m_min_distance_value(0.00) { ::ArrayResize(m_tracked_positions_arr, 0, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ #define CONDITIONAL_PARTIAL_DELETE_PTR_IN_DESTRUCTOR CConditionalPartials::~CConditionalPartials() { //--- Delete condition #ifdef CONDITIONAL_PARTIAL_DELETE_PTR_IN_DESTRUCTOR if(CheckPointer(m_partial_condition) == POINTER_DYNAMIC) { delete m_partial_condition; } #endif //--- Delete diff if(CheckPointer(m_min_distance_to_close_pos) == POINTER_DYNAMIC) { delete m_min_distance_to_close_pos; } } //+------------------------------------------------------------------+ //| Initialization function, the function sets the class parameters | //| through the "config" variable. | //| Inputs: | //| - ConditionalPartialConfig& config: variable that will store the | //| initial class configuration. | //| Outputs: | //| - bool: The function returns true on success, otherwise false. | //+------------------------------------------------------------------+ bool CConditionalPartials::Init(ConditionalPartialConfig& config) { //--- Check parameters are valid string err = ""; if(!config.IsValid(err)) { LogError(err, FUNCION_ACTUAL); // Show error return false; } //--- Assignment m_magic_number = config.magic_number; m_partial_condition = config.condition; m_symbol = config.symbol; m_min_distance_to_close_pos = config.min_distance_to_close_pos; m_auto_add_to_tracked_positions = config.auto_mode; m_trade.SetExpertMagicNumber(m_magic_number); //--- Symbol refresh RefreshSymbolData(); //--- Convert str to double array if(!config.ConvertStrToDoubleArr(',', m_volumes_to_close_arr, err)) { LogError(err, FUNCION_ACTUAL); // Show error return false; } //--- Convert to "decimal" int idx_to_remove[]; m_volume_to_close_size = ::ArraySize(m_volumes_to_close_arr); for(int i = 0; i < m_volume_to_close_size; i++) { const double val = m_volumes_to_close_arr[i]; if(val > 100.0) { LogWarning(::StringFormat("Found at position %d, value greater than 100.00%%, omitting value", i), FUNCION_ACTUAL); idx_to_remove.Push(i); continue; } else if(val < 0.000001) { LogWarning(::StringFormat("Found at position %d, value less than 0.000001%%, omitting value", i), FUNCION_ACTUAL); idx_to_remove.Push(i); continue; } m_volumes_to_close_arr[i] /= 100.00; } m_volume_to_close_size = RemoveMultipleIndexes(m_volumes_to_close_arr, idx_to_remove, 0); //--- Check array size if(m_volume_to_close_size < 1) { LogError("The size of the array of volumes to be closed is less than 1", FUNCION_ACTUAL); return false; } //--- Print array if(IsInfoLogEnabled()) { ArrayPrint(m_volumes_to_close_arr, 2, " | "); } //--- Finish return true; } //+-------------------------------------------------------------------------------------------+ //| Function that executes the main logic of the class | //| Inputs: | //| datetime current_time: Variable of datetime type, which must | //| store the current time. | //| Outputs: the function returns nothing. | //| Notes: | //| - The function can be called on each tick or each bar, this | //| will depend on the Execute function of the m_partial_condition variable, | //| for example if the condition is an indicator, like RSI | //| the logic could be executed on each candle, or for example if the condition | //| evaluates a mitigation of an orderblock, in this case it would be executed on each tick | //| | //| - The define "CONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS", | //| allows conditional code compilation, if this define is defined | //| the Execute(..) function of the m_partial_condition pointer, will only be called | //| when there are open positions, otherwise if the define is not defined | //| it will be called at all times. | //+-------------------------------------------------------------------------------------------+ //#define CONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS //+------------------------------------------------------------------+ void CConditionalPartials::Execute(datetime current_time) { #ifdef CONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS if(m_tracked_positions_size > 0) { //--- Execution m_partial_condition.Execute(current_time); //--- Close conditions const bool buy = m_partial_condition.CloseBuy(); const bool sell = m_partial_condition.CloseSell(); //--- Obtain min diff if(buy || sell) { m_min_distance_to_close_pos.OnNewBar(); m_min_distance_value = m_min_distance_to_close_pos.Diff(); //--- Check buy condition if(buy) { m_symbol_bid = ::SymbolInfoDouble(m_symbol, SYMBOL_BID); ClosePartialsForBuyPositions(); } //--- Check sell condition if(sell) { m_symbol_ask = ::SymbolInfoDouble(m_symbol, SYMBOL_ASK); ClosePartialsForSellPositions(); } } } #else //--- Execution m_partial_condition.Execute(current_time); //--- Check conditions if(m_tracked_positions_size > 0) { //--- Close conditions const bool buy = m_partial_condition.CloseBuy(); const bool sell = m_partial_condition.CloseSell(); //--- Obtain min diff if(buy || sell) { m_min_distance_to_close_pos.OnNewBar(); m_min_distance_value = m_min_distance_to_close_pos.Diff(); //--- Check buy condition if(buy) { m_symbol_bid = ::SymbolInfoDouble(m_symbol, SYMBOL_BID); ClosePartialsForBuyPositions(); } //--- Check sell condition if(sell) { m_symbol_ask = ::SymbolInfoDouble(m_symbol, SYMBOL_ASK); ClosePartialsForSellPositions(); } } } #endif } //+------------------------------------------------------------------+ //| Function that executes every time a trade is closed or opened | //+------------------------------------------------------------------+ void CConditionalPartials::OnOpenClosePosition(const ROnOpenClosePosition &pos) { //--- if(pos.deal_entry_type == DEAL_ENTRY_OUT) { // Once the position is closed we verify if that ticket is in the internal array, if so the function // will remove it. if(RemoveIndexFromAnArrayOfPositions(m_tracked_positions_arr, pos.position.ticket, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE)) m_tracked_positions_size--; // Change the value of the array size } else if(m_auto_add_to_tracked_positions && pos.deal_entry_type == DEAL_ENTRY_IN && account_status.LastTransctionSymbol() == m_symbol && (pos.position.magic == m_magic_number || m_magic_number == NOT_MAGIC_NUMBER)) { LogInfo(::StringFormat("A trade has just been opened with ticket %I64u", pos.position.ticket), FUNCION_ACTUAL); AddPositionToTrack(pos.position.type, pos.position.ticket, pos.position.open_price); } } //+-------------------------------------------------------------------------+ //| The function creates a new trackable position and adds it | //| to the internal array. | //| Inputs: | //| - ENUM_POSITION_TYPE position_type: position type (buy or sell) | //| - ulong position_ticket: Position ticket. | //| - double open_price: Trade opening price. | //| Outputs: The function returns no value. | //| Notes: | //| - The position ticket must be valid, | //| the function will not check if the ticket exists. | //+-------------------------------------------------------------------------+ void CConditionalPartials::AddPositionToTrack(ENUM_POSITION_TYPE position_type, ulong position_ticket, double open_price) { //--- if(m_tracked_positions_size == 0) // First position to be added to the tracked positions array m_partial_condition.OnInitPartials(open_price); //--- Obtain current value m_min_distance_to_close_pos.OnNewBar(); m_min_distance_value = m_min_distance_to_close_pos.Diff(); //--- Creation of a new position to track ConditionalPartialTrackedPosition new_tracked; new_tracked.type = position_type; new_tracked.ticket = position_ticket; new_tracked.next_min_price = position_type == POSITION_TYPE_BUY ? open_price + m_min_distance_value : open_price - m_min_distance_value; new_tracked.next_index_to_close = 0; //--- Add to tracked positions ::ArrayResize(m_tracked_positions_arr, m_tracked_positions_size + 1, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE); m_tracked_positions_arr[m_tracked_positions_size] = new_tracked; m_tracked_positions_size++; } //+-------------------------------------------------------------------------+ //| With a function similar to the AddPositionToTrack(...) function | //| differing in that this overloaded function only requires the | //| position ticket (the function internally already queries the | //| position type, open_price, etc.). | //| Inputs: | //| - ulong position_ticket: Position ticket. | //| Outputs: Returns true if a trackable position could be created with the | //| ticket, otherwise false | //| Notes: | //| - If the position ticket doesn't exist, or is invalid (failure of | //| PositionSelectByTicket) the function will not create a new trackable | //| position with the ticket | //+-------------------------------------------------------------------------+ bool CConditionalPartials::AddPositionToTrack(ulong position_ticket) { //--- Select if(!::PositionSelectByTicket(position_ticket)) { LogError(::StringFormat("Invalid ticket: %I64u", position_ticket), FUNCION_ACTUAL); return false; } //--- Check symbol const string sym =::PositionGetString(POSITION_SYMBOL); if(sym != m_symbol) { LogError(::StringFormat("The symbol for position %I64u = %s, does not correspond to that of the class = %s", position_ticket, sym, m_symbol), FUNCION_ACTUAL); return false; } //--- Add AddPositionToTrack((ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE), position_ticket, ::PositionGetDouble(POSITION_PRICE_OPEN)); return true; } //+------------------------------------------------------------------+ //| Function that iterates over all positions to track (buy) | //| and partially closes the trade | //+------------------------------------------------------------------+ void CConditionalPartials::ClosePartialsForBuyPositions(void) { //--- Resize to zero ::ArrayResize(m_indexes_to_remove, 0, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE); //--- Iteration for(int i = 0; i < m_tracked_positions_size; i++) { if(m_tracked_positions_arr[i].type == POSITION_TYPE_SELL || m_symbol_bid < m_tracked_positions_arr[i].next_min_price) continue; if(!::PositionSelectByTicket(m_tracked_positions_arr[i].ticket)) { // "Delete" AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) #ifdef CONDITIONAL_PARTIAL_MORE_INFO LogError(::StringFormat("Ticket selection failed %I64u", m_tracked_positions_arr[i].ticket), FUNCION_ACTUAL); #endif continue; } //--- m_tracked_positions_arr[i].next_min_price += m_min_distance_value; //--- Initial calculation const double lot_size = ::PositionGetDouble(POSITION_VOLUME); double volume_to_close = m_volumes_to_close_arr[m_tracked_positions_arr[i].next_index_to_close] * lot_size; //--- Check min volume to close if(volume_to_close < m_symbol_min_volume) { //--- Log LogWarning(::StringFormat("Partial close %d, of position %I64u skipped, volume to close %.*f is less than minimum volume %.*f", m_tracked_positions_arr[i].next_index_to_close, m_tracked_positions_arr[i].ticket, m_symbol_digits, volume_to_close, m_symbol_digits, m_symbol_min_volume), FUNCION_ACTUAL); //--- m_tracked_positions_arr[i].next_index_to_close++; // Skip this "partial close", advance to next index if(m_tracked_positions_arr[i].next_index_to_close == m_volume_to_close_size) // Limit reached { AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) } continue; } //--- Round volume_to_close = RoundToStep(volume_to_close, m_symbol_volume_step); #ifdef CONDITIONAL_PARTIAL_MORE_INFO LogInfo(::StringFormat("Partially closing %.2f lots of position %I64u", volume_to_close, m_tracked_positions_arr[i].ticket), FUNCION_ACTUAL); #endif //--- Close m_trade.PositionClosePartial(m_tracked_positions_arr[i].ticket, volume_to_close); //--- Next index m_tracked_positions_arr[i].next_index_to_close++; //--- Check limit if(m_tracked_positions_arr[i].next_index_to_close == m_volume_to_close_size) // Limit reached { AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) } } //--- Multiple remove m_tracked_positions_size = RemoveMultipleIndexes(m_tracked_positions_arr, m_indexes_to_remove, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE); } //+------------------------------------------------------------------+ //| Function that iterates over all positions to track (sell) | //| and partially closes the trade | //+------------------------------------------------------------------+ void CConditionalPartials::ClosePartialsForSellPositions(void) { //--- Resize to zero ::ArrayResize(m_indexes_to_remove, 0, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE); //--- Iteration for(int i = 0; i < m_tracked_positions_size; i++) { if(m_tracked_positions_arr[i].type == POSITION_TYPE_BUY || m_symbol_ask > m_tracked_positions_arr[i].next_min_price) continue; if(!::PositionSelectByTicket(m_tracked_positions_arr[i].ticket)) { // "Delete" AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) #ifdef CONDITIONAL_PARTIAL_MORE_INFO LogError(::StringFormat("Ticket selection failed %I64u", m_tracked_positions_arr[i].ticket), FUNCION_ACTUAL); #endif continue; } //--- m_tracked_positions_arr[i].next_min_price -= m_min_distance_value; //--- Initial calculation const double lot_size = ::PositionGetDouble(POSITION_VOLUME); double volume_to_close = m_volumes_to_close_arr[m_tracked_positions_arr[i].next_index_to_close] * lot_size; //--- Check min volume to close if(volume_to_close < m_symbol_min_volume) { //--- Log LogWarning(::StringFormat("Partial close %d, of position %I64u skipped, volume to close %.*f is less than minimum volume %.*f", m_tracked_positions_arr[i].next_index_to_close, m_tracked_positions_arr[i].ticket, m_symbol_digits, volume_to_close, m_symbol_digits, m_symbol_min_volume), FUNCION_ACTUAL); //--- Increment m_tracked_positions_arr[i].next_index_to_close++; // Skip this "partial close", advance to next index //--- Check limits if(m_tracked_positions_arr[i].next_index_to_close == m_volume_to_close_size) // Limit reached { AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) } continue; } //--- Round volume_to_close = RoundToStep(volume_to_close, m_symbol_volume_step); #ifdef CONDITIONAL_PARTIAL_MORE_INFO LogInfo(::StringFormat("Partially closing %.2f lots of position %I64u", volume_to_close, m_tracked_positions_arr[i].ticket), FUNCION_ACTUAL); #endif //--- Close m_trade.PositionClosePartial(m_tracked_positions_arr[i].ticket, volume_to_close); //--- Next index m_tracked_positions_arr[i].next_index_to_close++; //--- Check limit if(m_tracked_positions_arr[i].next_index_to_close == m_volume_to_close_size) // Limit reached { AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) } } //--- Multiple remove m_tracked_positions_size = RemoveMultipleIndexes(m_tracked_positions_arr, m_indexes_to_remove, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE); } //+----------------------------------------------------------------------------------+ //| The function sets a new partial close "condition", it changes | //| the "address" of the m_partial_condition pointer to new_condition, and | //| if necessary the previous pointer is deleted | //| Inputs: | //| - IConditionPartial *new_condition: Pointer to IConditionPartial. | //| - bool delete_prev_ptr: Boolean indicating if the internal pointer is deleted | //| Outputs: The function returns nothing. | //+----------------------------------------------------------------------------------+ void CConditionalPartials::PartialCloseCondition(IConditionPartial *new_condition, bool delete_prev_ptr) { //--- Delete prev ptr if(delete_prev_ptr && ::CheckPointer(m_partial_condition) == POINTER_DYNAMIC) { delete m_partial_condition; } //--- m_partial_condition = new_condition; } //+------------------------------------------------------------------+ //| Sets a new internal pointer for the minimum distance | //| between partials. | //| Inputs: | //| - CDiff *new_value: Pointer to CDiff. | //| - bool delete_prev_ptr: Boolean indicating if the | //| internal class pointer m_min_distance_to_close_pos is deleted | //| before assigning it a new "value" (address). | //| Outputs: The function returns no value | //+------------------------------------------------------------------+ void CConditionalPartials::MinDistBetweenPartialClosures(CDiff *new_value, bool delete_prev_ptr) { //--- Delete prev ptr if(delete_prev_ptr && ::CheckPointer(m_min_distance_to_close_pos) == POINTER_DYNAMIC) { delete m_min_distance_to_close_pos; } //--- m_min_distance_to_close_pos = new_value; } //+------------------------------------------------------------------+ //| Sets a new value for the magic number | //| Inputs: | //| - ulong new_value: new magic number value. | //| Outputs: The function returns no value | //+------------------------------------------------------------------+ void CConditionalPartials::MagicNumber(ulong new_value) { m_magic_number = new_value; m_trade.SetExpertMagicNumber(new_value); } //+------------------------------------------------------------------+ //| Sets a new symbol. | //| Inputs: | //| - string new_symbol: New symbol. | //| Outputs: The function returns no value. | //| Notes: | //| - The function should only be called when there are no | //| open positions, to avoid unexpected bugs with | //| tracked positions from other symbols. | //+------------------------------------------------------------------+ void CConditionalPartials::Symbol(string new_symbol) { m_symbol = new_symbol; RefreshSymbolData(); } //+------------------------------------------------------------------+ //| Removes a trackable position from the internal array | //| Inputs: | //| ulong position_ticket: Ticket of the trackable position to be | //| removed | //| Outputs: The function returns true, if the ticket was | //| successfully removed, otherwise false (ticket does not | //| exist). | //+------------------------------------------------------------------+ bool CConditionalPartials::RemoveTrackedPosition(ulong position_ticket) { if(RemoveIndexFromAnArrayOfPositions(m_tracked_positions_arr, position_ticket, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE)) { m_tracked_positions_size--; // Resize return true; // Success } return false; // Error, or position_ticket not exist in array } //+------------------------------------------------------------------+ //| "Re-queries" the values of minimum volume, volume step and | //| symbol digits. | //+------------------------------------------------------------------+ void CConditionalPartials::RefreshSymbolData(void) { m_symbol_digits = (int8_t)::SymbolInfoInteger(m_symbol, SYMBOL_DIGITS); m_symbol_min_volume = ::SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN); m_symbol_volume_step = ::SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP); } #endif // MQLARTICLES_POSMGMT_CONDPARTIALS_BASE_BASE_MQH //+------------------------------------------------------------------+