Cierres parciales condicionales (Parte 1): Creación de la clase base
- Introducción a los cierres parciales condicionales, ventajas y desventajas
- Integramos los cierres parciales condicionales en MQL5
- Creación de los archivos del proyecto
- Implementación de la clase CConditionalPartials
- Declaración y definición de las funciones de la clase CConditionalPartials
- Conclusión
Introducción a los cierres parciales condicionales, ventajas y desventajas
En el último artículo que publiqué sobre los cierres parciales, analizamos las ventajas que ofrecen frente a otros métodos de gestión de posiciones, además de implementarlo en MQL5. Sin embargo, al inicio de aquel artículo mencioné que existen distintas formas de aplicar los cierres parciales, como por ejemplo, mediante rupturas de resistencias o soportes, entre otras.
En este artículo desarrollaremos un tipo de cierre parcial más complejo que el presentado anteriormente: los cierres parciales condicionales.
Los cierres parciales condicionales, son un tipo de cierre parcial que consiste en cerrar parcialmente una posición cuando se cumpla una condición específica.
Normalmente, esta condición corresponde a un patrón de "reversión" o contrario al tipo de posición que estemos rastreando. Por ejemplo, si estamos rastreando una posición de compra, una posible condición de reversión podría ser que el RSI se encuentre en sobrecompra o que el precio alcance un nivel de resistencia.
En resumen, se trata de una condición que posiblemente anticipe un cambio en la dirección del mercado.
Ventajas
La principal ventaja de este método frente a los cierres parciales básicos (niveles de takeprofit fijos) es que incorpora una lógica definida para ejecutar los cierres.
Dicha lógica, como mencionamos anteriormente, suele basarse en "patrones" de reversión, lo que le da un sentido más coherente que utilizar niveles de takeprofit arbitrarios.
Desventajas
Al igual que en los cierres parciales básicos, las comisiones generadas por cada cierre parcial pueden afectar los resultados en ciertos estilos de trading, como el scalping, donde este método podría no ofrecer una mejora significativa e incluso generar más pérdidas.
También es importante mencionar que la efectividad de este método depende en gran medida de la condición utilizada para realizar los cierres parciales.
Si dicha condición no es precisa, puede ocurrir que se "desperdicie" parte del posible beneficio de la operación, cerrando parcialmente en puntos que no representan verdaderas reversiones.
Por ejemplo, cuando el "RSI" entra en sobrecompra, es posible que el precio continúe su tendencia alcista, por lo que cerrar parcialmente en ese momento reduciría el potencial de ganancia de la operación.
Integramos los cierres parciales condicionales en MQL5
Antes de continuar con la implementación en MQL5, es necesario mencionar dos conceptos clave que conforman la base de los cierres parciales condicionales.
- Condición de cierre parcial (condición): criterio que determina en qué momento se cerrarán parcialmente las posiciones de compra o venta.
- Gestor de posiciones rastreadas (clase base): se encarga principalmente de rastrear determinadas posiciones y cerrar parcialmente aquellas posiciones al momento de cumplirse la condición de cierre parcial.
Este proceso puede representarse visualmente en la siguiente imagen.

Figura 1: Ejemplo de cierres parciales condicionales
En la imagen se observa que, al cumplirse la condición, se deberá de ejecutar un cierre parcial de dicha posición. El volumen a cerrar se determina a partir de un porcentaje del volumen actual de la posición, definido por el usuario. Por ejemplo, si la posición tiene un volumen de 0.10 lotes y el porcentaje configurado para el cierre parcial es del 20%, se cerrarán parcialmente 0.02 lotes.
Para implementar esta técnica de gestión de posiciones en MQL5, dividiremos el proceso en dos partes: por un lado, una interfaz que representará la condición, y por otro, una clase base encargada de ejecutar los cierres parciales, agregar las posiciones de forma "automática" y gestionar el flujo general.
Antes de comenzar con la creación de los archivos mqh, llevé a cabo algunos ajustes en la estructura del repositorio:
- La carpeta OrderBlock ahora se renombra a Ob.
- Agregué una nueva carpeta llamada IndicatorsCts, la cual contiene una librería sencilla para integrar indicadores técnicos mediante clases en MQL5.
- La carpeta PosManagement ahora se renombra a PosMgmt.

Figura 2:Repositorio MQLArticles
Creación de los archivos del proyecto
Comenzamos creando una carpeta llamada ConditionalPartial dentro de la carpeta PosMgmt. En esta carpeta se ubicará el código donde implementaremos los cierres parciales condicionales.

Figura 3: Creación de la carpeta ConditionalPartial dentro de la carpeta PosMgmt
A continuación, crearemos la carpeta Base.

Figura 4: Creación de la carpeta Base dentro de la carpeta ConditionalPartial
Dentro de esta carpeta ubicaremos los archivos que comprenderán la clase base.
Seguidamente, crearemos el archivo Defines.mqh, donde colocaremos las estructuras y defines utilizados por la clase base. En este mismo archivo también implementaremos la interfaz correspondiente a la condición de cierre parcial.
Después, añadiremos el archivo Base.mqh, que contendrá el código principal de la clase base encargada de gestionar los cierres parciales condicionales.

Figura 5: Creación de los archivos Defines.mqh y Base.mqh dentro de la carpeta Base
Implementación del archivo Defines.mqh
Dentro del archivo Defines.mqh, comenzaremos incluyendo la librería de gestión de riesgo.
//+------------------------------------------------------------------+ //| Includes | //+------------------------------------------------------------------+ #include "..\\..\\..\\RM\\RiskManagement.mqh"
Defines generales
En la sección de definiciones del archivo Defines.mqh, declararemos dos defines que establecerán la reserva inicial para los arrays principales utilizados por la clase.
El primer define corresponde al tamaño reservado para el array principal, donde se almacenarán las posiciones a las que se aplicarán los cierres parciales. El segundo define corresponde al array que almacenará los índices de las posiciones que serán removidas del array principal una vez que se complete el cierre parcial.
//+------------------------------------------------------------------+ //| Defines | //+------------------------------------------------------------------+ #define CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE 5 #define CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE 2
Interfaz para la condición del cierre parcial
Declararemos una interfaz (una clase que contiene únicamente funciones virtuales puras) que representará una condición de cierre parcial.
Esta interfaz será utilizada más adelante por nuestra clase base.
//+------------------------------------------------------------------+ //| Interface for partial condition | //+------------------------------------------------------------------+ interface IConditionPartial { public:
Para esta interfaz necesitaremos dos funciones booleanas que retornarán true cuando sea momento de cerrar parcialmente una operación de compra o de venta.
bool CloseBuy(); bool CloseSell();
Además, incluiremos una función getter llamada Name(), que deberá retornar el nombre de la condición, lo que permitirá al usuario identificar el tipo de condición implementada por la clase heredada.
string Name() const;
También añadiremos dos funciones para que las clases derivadas puedan limpiar o reiniciar variables al comenzar un nuevo día o una nueva semana.
void OnNewDay(); void OnNewWeek();
Algunas clases heredadas necesitarán saber cuándo inicia el proceso de cierres parciales (es decir, cuando pasamos de no tener posiciones rastreadas a tener al menos una). Para ello, implementaremos la función OnInitPartials().
void OnInitPartials(double initial_price);
Finalmente, agregaremos la función Execute(), donde cada clase heredada implementará su lógica principal.
void Execute(datetime current_time);
Código completo.
//+------------------------------------------------------------------+ //| Interface for partial condition | //+------------------------------------------------------------------+ interface IConditionPartial { public: void OnInitPartials(double initial_price); void OnNewDay(); void OnNewWeek(); void Execute(datetime current_time); string Name() const; bool CloseBuy(); bool CloseSell(); };
Esta interfaz define un total de siete funciones, las cuales deberán ser implementadas obligatoriamente por todas las clases que la hereden.
Estructuras
Dentro de las estructuras definiremos dos: una para la configuración de la clase base y otra para representar una posición a la cual se aplicarán los cierres parciales.
Estructura para la configuración de la clase
Crearemos una estructura que almacene todos los parámetros que la clase base necesita para funcionar correctamente.
Entre estos parámetros se encuentra la condición para el cierre parcial de posiciones de compra o venta. Para ello, declararemos un puntero del tipo IConditionPartial.
IConditionPartial *condition;
En ocasiones, puede suceder que entre cierres parciales consecutivos, dados por la condición o por la volatilidad del mercado, exista muy poca distancia, lo que provocaría cierres casi simultáneos.
Para evitarlo, daremos la posibilidad de definir una distancia mínima entre los cierres parciales aplicados a una misma posición. Esta distancia se determinará mediante la clase CDiff, que permite calcular una distancia basada en el atr o en puntos. Por lo tanto, también declararemos un puntero del tipo CDiff.
CDiff *min_distance_to_close_pos;
En cada cierre parcial se tomará un porcentaje del volumen actual de la posición. Para configurar esto, declararemos una variable de tipo string que contendrá los porcentajes separados por comas. El número de porcentajes indicará la cantidad de cierres parciales que se aplicarán a la posición.
No obstante, esto no siempre será exacto, ya que si el volumen de la posición es muy pequeño, puede que no sea posible realizar todos los cierres definidos por el usuario. Esto dependerá tanto de las condiciones del mercado que activen la condición, como del volumen y los porcentajes establecidos.
string str_percentage_volume_to_close;Cuando se abra una posición, necesitaremos saber si pertenece a nuestro EA o si tiene un número mágico específico. Para ello, añadiremos un parámetro que permitirá filtrar las operaciones según su número mágico. Si el valor es NOT_MAGIC_NUMBER, no se filtrará ninguna operación y se considerarán todas. ulong magic_number;En caso de que el EA deba decidir qué operaciones tendrán cierres parciales, incluiremos una variable booleana. Si esta variable es true, al momento de la apertura de una posición, esta se agregará "automáticamente" al array de posiciones rastreadas de la clase base. En caso contrario, el usuario deberá añadir manualmente las posiciones que tendrán cierres parciales. bool auto_mode;
Para la obtención de datos del símbolo, como el volumen mínimo, paso del volumen, etc. necesitaremos un símbolo del cual consultaremos dichos datos; por esta razón incluiremos una variable string symbol, la cual el usuario deberá darle un valor.
string symbol;A continuación, definiremos un constructor para la estructura, el cual inicializa las variables miembro con valores por defecto.
ConditionalPartialConfig() : str_percentage_volume_to_close(""), symbol(_Symbol), magic_number(NOT_MAGIC_NUMBER), condition(NULL), min_distance_to_close_pos(NULL), auto_mode(true) { }
Para facilitar la verificación de que las variables miembro tengan valores válidos, especialmente los punteros, agregaremos la función IsValid(), que verificará que los punteros condition y min_distance_to_close_pos sean válidos.
bool IsValid(string& error) const { if(!CheckPointer(condition)) { error = "Invalid condition pointer"; return false; } if(!CheckPointer(min_distance_to_close_pos)) { error = "Invalid min distance pointer"; return false; } return true; }Finalmente, añadiremos una función que convierta la variable miembro (del tipo string) str_percentage_volume_to_close en un array de tipo double, pasado por referencia. Si el proceso de conversión falla o el tamaño del array resultante es menor que uno, se asignará a la variable error el mensaje correspondiente con la descripción del error.
//--- bool ConvertStrToDoubleArr(ushort separator, double& out[], string& error) const { //--- Convert if(!StrTo::CstArray(out, str_percentage_volume_to_close, separator)) { error = StringFormat("When converting string %s\nTo double array", str_percentage_volume_to_close); return false; } //--- CheckSize const int size = ArraySize(out); if(size < 1) { error = StringFormat("Invalid %s string\nNo elements with separator = '%s'", str_percentage_volume_to_close, ShortToString(separator)); return false; } return true; }
Código completo.
//--- Structure for partial configuration struct ConditionalPartialConfig { //--- IConditionPartial *condition; CDiff *min_distance_to_close_pos; string str_percentage_volume_to_close; string symbol; ulong magic_number; bool auto_mode; //--- ConditionalPartialConfig() : str_percentage_volume_to_close(""), symbol(_Symbol), magic_number(NOT_MAGIC_NUMBER), condition(NULL), min_distance_to_close_pos(NULL), auto_mode(true) { } //--- bool IsValid(string& error) const { if(!CheckPointer(condition)) { error = "Invalid condition pointer"; return false; } if(!CheckPointer(min_distance_to_close_pos)) { error = "Invalid min distance pointer"; return false; } return true; } //--- bool ConvertStrToDoubleArr(ushort separator, double& out[], string& error) const { //--- Convert if(!StrTo::CstArray(out, str_percentage_volume_to_close, separator)) { error = StringFormat("When converting string %s\nTo double array", str_percentage_volume_to_close); return false; } //--- CheckSize const int size = ArraySize(out); if(size < 1) { error = StringFormat("Invalid %s string\nNo elements with separator = '%s'", str_percentage_volume_to_close, ShortToString(separator)); return false; } return true; } };
Estructura para rastrear una posición
Crearemos una estructura que servirá para representar una posición dentro de la clase base.
Esta estructura se añadirá a un array interno, donde será rastreada y actualizada conforme se vayan realizando los cierres parciales. Además, su índice dentro del array cambiará a medida que se modifique el volumen restante por cerrar.
Primero, necesitaremos almacenar el ticket y el tipo de posición.
ulong ticket; ENUM_POSITION_TYPE type;
Para calcular el precio mínimo al cual se ejecutará el próximo cierre parcial, incluiremos una variable de tipo double.
double next_min_price;Finalmente, declararemos una variable entera que representará el índice actual.
Este índice será consultado por la clase base para obtener el porcentaje del volumen que se debe cerrar en el cierre parcial actual.
int next_index_to_close;Código completo.
//--- Structure to store a trackable position (to which partial closes will be applied until its removal) struct ConditionalPartialTrackedPosition { ulong ticket; double next_min_price; ENUM_POSITION_TYPE type; int next_index_to_close; };
Implementación de la clase CConditionalPartials
Dentro del archivo Base.mqh, comenzaremos incluyendo el archivo Defines.mqh:
//+------------------------------------------------------------------+ //| Includes | //+------------------------------------------------------------------+ #include "Defines.mqh"
Luego, declararemos la clase CConditionalPartials, que será la encargada de aplicar los cierres parciales sobre las posiciones rastreadas, utilizando una condición definida por el usuario.
//+------------------------------------------------------------------+ //| Base class for conditional partials implementation | //+------------------------------------------------------------------+ class CConditionalPartials : public CAccountGestor
La clase CConditionalPartials heredará de CAccountGestor.
Recordemos que CAccountGestor es una clase que proporciona la "interfaz" necesaria para integrar funciones de tipo "evento", relacionadas principalmente con transacciones, como la apertura o el cierre de una posición.
Posteriormente, las funciones virtuales de CAccountGestor serán llamadas por una instancia global del código account_status cuando ocurra el evento correspondiente. Por ejemplo, al abrir o cerrar una posición, se llamará a la función OnOpenClosePosition() de todas las clases que hayan sido añadidas a la instancia global account_status.
Variables miembro de la clase
Las variables miembro de la clase se ubicarán al inicio de la definición de la clase base y serán declaradas con el modificador de acceso protected, lo que permitirá que puedan ser utilizadas por clases derivadas.
protected:En la sección general, declararemos un puntero de tipo IConditionPartial, el cual almacenará la condición que la clase utilizará para aplicar los cierres parciales sobre las posiciones.
Además, incluiremos una instancia de CTrade para ejecutar las operaciones de cierre parcial.
//--- 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
Para almacenar las posiciones a las cuales se les aplicarán los cierres parciales, definiremos un array del tipo ConditionalPartialTrackedPosition. Además, se declarará una variable que almacenará el tamaño actual de dicho array.
//--- 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
Una vez finalizada la aplicación de los cierres parciales sobre una posición, será necesario eliminarla del array principal m_tracked_positions_arr. Para evitar errores de rango o posibles fallos durante la iteración del array, la eliminación se realizará al finalizar el bucle.
Por este motivo, se definirá otro array de tipo int llamado m_indexes_to_remove, el cual almacenará los índices que deberán ser eliminados del principal.
int m_indexes_to_remove[]; // Indices to be removed from tracked positions arrayEn varias funciones de la clase será necesario acceder a las propiedades del símbolo (como el volumen mínimo, el paso de volumen, entre otros) sobre el cual se ejecutarán las posiciones a las que se aplicarán los cierres parciales. Para esto, se incluirá una sección dedicada a las variables que almacenarán información relacionada con el símbolo, como su nombre o sus propiedades principales.
//--- 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"Luego, se creará una sección destinada a las variables de tipo "parámetro" de la clase. En esta parte se ubicarán elementos como la distancia mínima entre cierres parciales, el número mágico, el array de porcentajes de volumen a cerrar parcialmente por posición, entre otros.
//--- 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 tradeFinalmente, para almacenar el valor calculado de la distancia mínima entre cierres parciales, se declarará una variable de tipo double.
//--- Min distance double m_min_distance_value; // Minimum distance between partials (value)
Constructor
Dentro del constructor de la clase inicializaremos las variables miembro con valores por defecto o inválidos, según corresponda. Además, el arraym_tracked_positions_arr se establecerá inicialmente con un tamaño de 0.//+------------------------------------------------------------------+ //| 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
En el cuerpo del destructor eliminaremos, si el define CONDITIONAL_PARTIAL_DELETE_PTR_IN_DESTRUCTOR esta definido en el código, el puntero m_partial_condition. Además, se eliminará el puntero m_min_distance_to_close_pos.
//+------------------------------------------------------------------+ //| 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; } }
Función de inicialización de la clase
La función de inicialización de la clase recibirá una referencia del tipo ConditionalPartialConfig, la cual contendrá los valores de configuración inicial de las variables parámetro de la clase.
//--- Initialization function bool Init(ConditionalPartialConfig& config);
En el cuerpo de la función Init(), primero se validarán los parámetros llamando a la función miembro IsValid() de la estructura config.
//+------------------------------------------------------------------+ //| 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; }
Si la función IsValid() devuelve false, significa que ocurrió un error (como un puntero inválido). En ese caso, se llamará a LogError() para imprimir el mensaje contenido en err en la terminal.
Si no se detectan errores, se procederá a la asignación de los valores. Se establecerán las variables parámetro con los valores correspondientes de la estructura config.
//--- 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();
A continuación, se llamará a la función RefreshSymbolData() para actualizar los datos del símbolo, como min_volume, step o digits.
Posteriormente, será necesario convertir el string miembro str_percentage_volume_to_close de config a un array del tipo double. Para ello, se usará la función miembro ConvertStrToDoubleArr().
//--- Convert str to double array if(!config.ConvertStrToDoubleArr(',', m_volumes_to_close_arr, err)) { LogError(err, FUNCION_ACTUAL); // Show error return false; }
Si ocurre un fallo durante la conversión, el error se imprimirá mediante la función LogError().
Finalmente, se iterará sobre los valores del array m_volumes_to_close_arr, verificando que cada valor sea válido (no mayor a 100.0 ni menor a 0.00). Luego, los porcentajes se convertirán a formato decimal, para evitar realizar divisiones repetitivas al calcular el volumen parcial de una posición.
Además, se comprobará que el array contenga al menos un elemento; de lo contrario, la función retornará false.
Para finalizar, los valores del array m_volumes_to_close_arr se imprimirán únicamente si los registros del tipo "info" están habilitados.
//--- 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; }
Declaración y definición de las funciones de la clase CConditionalPartials
Funciones para añadir posiciones a la clase
Para añadir posiciones a la clase, se crearán dos funciones sobrecargadas.
//--- Add function virtual void AddPositionToTrack(ENUM_POSITION_TYPE position_type, ulong position_ticket, double open_price); bool AddPositionToTrack(ulong position_ticket);
Primera función
El cuerpo de la primera función recibirá tres parámetros: el tipo de posición, el ticket de la posición y el precio de apertura.
//+-------------------------------------------------------------------------+ //| 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) {La función iniciará llamando a OnInitPartials() del puntero miembro m_partial_condition, pero solo si se trata de la primera posición añadida al array de seguimiento.
//--- if(m_tracked_positions_size == 0) // First position to be added to the tracked positions array m_partial_condition.OnInitPartials(open_price);
Luego, se obtendrá la distancia mínima entre parciales. Para ello, primero se ejecutará la función miembro OnNewBar() de CDiff, y posteriormente se almacenará el valor devuelto por Diff() en la variable m_min_distance_value.
//--- Obtain current value
m_min_distance_to_close_pos.OnNewBar();
m_min_distance_value = m_min_distance_to_close_pos.Diff();
A continuación, se creará una nueva variable llamada new_tracked del tipo ConditionalPartialTrackedPosition, donde se guardará la información de la posición, como el tipo, el ticket y el siguiente precio mínimo para ejecutar un cierre parcial.
//--- 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;
Finalmente, se añadirá la variable new_tracked al array interno de posiciones m_tracked_positions_arr.
//--- 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++; }
Segunda función
La segunda versión de AddPositionToTrack() recibirá únicamente, como parámetro, el ticket de la posición.
Esta función consultará internamente el tipo de posición y su precio de apertura a partir del ticket recibido, para luego llamar a la primera versión de AddPositionToTrack().
//+-------------------------------------------------------------------------+ //| 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; } //--- Add AddPositionToTrack((ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE), position_ticket, ::PositionGetDouble(POSITION_PRICE_OPEN)); return true; }
Función para remover posiciones de clase
Para eliminar posiciones rastreadas dinámicamente, se integró la función RemoveTrackedPosition(). Esta función recibe como único parámetro el ticket de la posición que se desea eliminar.
Devuelve true si el ticket pudo eliminarse correctamente; en caso contrario, retorna false.
Internamente, la función llama a RemoveIndexFromAnArrayOfPositions().
//+------------------------------------------------------------------+ //| 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 }
Funciones virtuales para aplicar los cierres parciales
Ahora crearemos dos funciones que se encargarán de iterar sobre el array de posiciones rastreadas m_tracked_positions_arr.
Comenzaremos declarando las funciones en la sección protegida de la clase.
//--- Partials closures Functions virtual void ClosePartialsForBuyPositions(); virtual void ClosePartialsForSellPositions();
El algoritmo que seguiremos es similar al usado en los parciales con niveles de takeprofit. A continuación se presentan dos imágenes que muestran el funcionamiento de las funciones ClosePartialsForBuyPositions() y ClosePartialsForSellPositions().
Posiciones de compra:

Figura 5: Algoritmo para la aplicación de los cierres parciales para las posiciones de compra
Posiciones de venta:

Figura 6: Algoritmo para la aplicación de los cierres parciales para las posiciones de venta.
Implementación de la función para los cierres parciales condicionales en posiciones de compra
Iniciaremos la definición de la función redimensionando a cero el array donde se almacenarán los índices que serán removidos del array de posiciones rastreadas m_tracked_positions_arr.
//+------------------------------------------------------------------+ //| 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);
A continuación, es necesario iterar sobre todas las posiciones. Para ello utilizaremos un bucle for que recorrerá desde el índice 0 hasta el último (tamaño de posiciones rastreadas menos 1).
//--- Iteration for(int i = 0; i < m_tracked_positions_size; i++) {
Definiremos una condición ifcon dos condiciones:
- El tipo de posición es de venta.
- El valor de m_symbol_bid es menor que el precio mínimo permitido para un cierre parcial.
if(m_tracked_positions_arr[i].type == POSITION_TYPE_SELL || m_symbol_bid < m_tracked_positions_arr[i].next_min_price) continue;Para obtener el volumen actual de la posición, primero debemos seleccionar su ticket. Si la función PositionSelectByTicket() devuelve false, añadiremos el índice correspondiente a m_indexes_to_remove para su posterior eliminación del array m_tracked_positions_arr.
if(!::PositionSelectByTicket(m_tracked_positions_arr[i].ticket)) { // "Delete" AddArrayNoVerification2(m_indexes_to_remove, i, CONDITIONAL_PARTIAL_ARR_TO_REMOVE_RESERVE) continue; }Luego calcularemos el siguiente precio mínimo al cual se deberá cerrar parcialmente la posición, además del volumen que se cerrará.
//--- 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;
Antes de cerrar parcialmente la posición con el volumen volume_to_close, verificaremos que este no sea menor que el volumen mínimo del símbolo. Si lo es, se omitirá el cierre y se avanzará al siguiente parcial. Para ello se incrementará en 1 el valor del miembr next_index_to_close.
Si dicho valor es igual al número de cierres parciales definidos, se agregará el índice i al array m_indexes_to_remove para su posterior eliminación de m_tracked_positions_arr. Finalmente, se continuará con la siguiente iteración.
//--- 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; }Si el volumen a cerrar es mayor o igual al mínimo permitido, se redondeará el valor de volume_to_close y se llamará a la función miembro CTrade::PositionClosePartial() para realizar el cierre parcial. Después, se incrementará en 1 el valor de next_index_to_close y se verificará nuevamente si alcanzó el número total de parciales definidos. En ese caso, el índice i se agregará al final del array m_indexes_to_remove para su posterior eliminación.
//--- Round volume_to_close = RoundToStep(volume_to_close, m_symbol_volume_step); 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) }Finalmente, se llamará a la función RemoveMultipleIndexes() que eliminará del array m_tracked_positions_arr los índices almacenados en m_indexes_to_remove.
//--- Multiple remove
m_tracked_positions_size = RemoveMultipleIndexes(m_tracked_positions_arr, m_indexes_to_remove, CONDITIONAL_PARTIAL_ARR_MAIN_RESERVE);
}Código completo.
//+------------------------------------------------------------------+ //| 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) 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); 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); }
Implementación de la función para los cierres parciales condicionados en posiciones de venta
Para las posiciones de venta, la única diferencia se encuentra en el primer if dentro del ciclo de iteración. En este bloque, se buscará que se cumplan las siguientes condiciones:
- La posición sea de compra.
- El valor de m_symbol_ask sea mayor que el precio máximo permitido para el cierre parcial.
if(m_tracked_positions_arr[i].type == POSITION_TYPE_BUY || m_symbol_ask > m_tracked_positions_arr[i].next_min_price) continue;
Código completo.
//+------------------------------------------------------------------+ //| 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) 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); 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); }
Función principal de ejecución Execute
La función Execute() representa el núcleo de la clase. En ella se realizan las llamadas a las funciones miembro del puntero m_partial_condition, CloseBuy() y CloseSell().
Comenzaremos declarando la función en la sección pública de la clase. Recibirá como parámetro una variable de tipo datetime, la cual debe contener el tiempo actual. Este valor se pasará luego al puntero m_partial_condition llamando a su función miembro IConditionPartial::Execute(). El objetivo de incluir este parámetro es permitir que la clase conozca el momento en que se ejecuta la función.
//--- Execute function void Execute(datetime current_time); // OnNewBar
Antes de definir el cuerpo de la función, declararemos el defineCONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS. Este define se utiliza para la compilación condicional del código (mediante #ifdef, #endif, etc.). Su propósito es evitar llamadas innecesarias a la función miembro Execute() del puntero m_partial_condition dentro de la función Execute() de la clase base.
Si este define está definido en el código, la función Execute() del puntero m_partial_condition se ejecutará solo cuando existan posiciones abiertas y junto al resto del código. En cambio, si no está definido, la función se ejecutará en todo momento, incluso cuando no haya posiciones.
Por ejemplo, si la condición está basada en un indicador como RSI, donde se busca detectar zonas de sobrecompra o sobreventa, conviene definir el define para evitar copiar buffers innecesariamente. Por otro lado, si se trata de condiciones que requieren observar continuamente el mercado (como la detección de swing sweeps), se recomienda no definirlo o dejarlo comentado.
Dentro del bloque donde se evalúa si CONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS está definido, el primer paso será verificar con un if que exista al menos una posición rastreada por la clase. Es decir, que el tamaño del array de posiciones rastreadas sea mayor que cero.
if(m_tracked_positions_size > 0) {
Luego, se ejecutará la condición mediante la función Execute() del puntero m_partial_condition.
//--- Execution
m_partial_condition.Execute(current_time);
A continuación, se llamará a las funciones CloseBuy() y CloseSell(), almacenando sus resultados en dos variables booleanas constantes, lo que garantiza que sus valores no se modifiquen. //--- Close conditions const bool buy = m_partial_condition.CloseBuy(); const bool sell = m_partial_condition.CloseSell();
Después, se evaluará si es necesario cerrar parcialmente las posiciones rastreadas de compra o venta.
//--- Obtain min diff if(buy || sell) { }
Seguidamente, se obtendrá la distancia mínima entre parciales, la cual se almacenará en la variable miembro m_min_distance_value de la clase base.
m_min_distance_to_close_pos.OnNewBar(); m_min_distance_value = m_min_distance_to_close_pos.Diff();Finalmente, si el valor del booleano buyes verdadero, se consultará el bid y se almacenará su valor, para luego llamar a la función ClosePartialsForBuyPositions(). De forma similar, si selles verdadero, se consultará el ask y se llamará a la función ClosePartialsForSellPositions().
//--- 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(); }
El código completo del bloque donde el defineCONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS está definido es el siguiente.
#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(); } } }
Por otro lado, el segundo bloque, que se compila cuando el define CONDITIONAL_PARTIAL_EXECUTE_WHEN_THE_ARE_POSITIONS no está definido, mantiene la misma lógica, pero la función Execute() del puntero m_partial_condition se ejecuta siempre, independientemente de que existan posiciones abiertas.
#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 }
Código completo.
//+-------------------------------------------------------------------------------------------+ //| 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 }
Funciones “evento” de la clase
Declararemos las funciones OnOpenClosePosition(), OnNewWeek() y OnNewDay(), todas heredadas de la clase CAccountGestor, dentro de la sección pública de la clase.
//--- 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(); }
Función "OnOpenClosePosition"
Comenzaremos definiendo la función evento OnOpenClosePosition().
Recordemos que esta función se ejecuta cada vez que se abre o se cierra una posición. Dentro de ella, primero verificaremos que el deal_entry_type de la posición es de salida (DEAL_ENTRY_OUT). Si esto se cumple, intentaremos eliminar el ticket de la posición del array m_tracked_positions_arr. Si la eliminación tiene éxito, reduciremos el tamaño del array de posiciones rastreadas en una unidad.
//+------------------------------------------------------------------+ //| 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 }
Para el segundo bloque condicional, el código dentro del if se ejecutará únicamente si se cumplen todas las siguientes condiciones:
- La variable miembro m_auto_add_to_tracked_positions es true.
- El deal_entry_type de la posición es DEAL_ENTRY_IN.
- El último símbolo obtenido desde account_status.LastTransctionSymbol() coincide con el valor de la variable miembro m_symbol.
- El número mágico de la posición es igual al valor de m_magic_number, o bien, m_magic_number es igual a NOT_MAGIC_NUMBER.
//+------------------------------------------------------------------+ //| 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); } }
Funciones OnNewDay() y OnNewWeek()
Las funciones OnNewDay() y OnNewWeek() son sencillas. Su propósito es ejecutar las funciones miembro OnNewDay() y OnNewWeek() de la condición m_partial_condition.
void OnNewDay(datetime init_time) override final { m_partial_condition.OnNewDay(); } void OnNewWeek(datetime init_time) override final { m_partial_condition.OnNewWeek(); }
Funciones setter y getter generales
Finalizaremos la implementación de la clase con las funciones setter y getter correspondientes a sus principales atributos.
Condición de cierre parcial PartialCloseCondition
Para la condición parcial, el getter devolverá el puntero interno m_partial_condition.// Condition IConditionPartial* PartialCloseCondition() { return m_partial_condition; }
El setter se declarará en la parte pública de la clase de la siguiente manera.
void PartialCloseCondition(IConditionPartial* new_condition, bool delete_prev_ptr);Dentro del cuerpo de la función, eliminaremos el puntero miembro m_partial_condition únicamente si el parámetro booleano delete_prev_ptr es verdadero y dicho puntero es dinámico. Luego, asignaremos el nuevo valor recibido al puntero m_partial_condition.
//+----------------------------------------------------------------------------------+ //| 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; }
Número mágico MagicNumber
El getter del número mágico devolverá un valor de tipo ulong, correspondiente a la variable miembro m_magic_number.ulong MagicNumber() const { return m_magic_number; }
El setter asignará el nuevo valor new_value a la variable miembro m_magic_number y actualizará el número mágico en el objeto m_trade.
//+------------------------------------------------------------------+ //| 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); }
Mínima distancia entre cierres parciales MinDistBetweenPartialClosures
El getter devolverá el puntero m_min_distance_to_close_pos.// Minimum distance between partial closures CDiff* MinDistBetweenPartialClosures() { return m_min_distance_to_close_pos; }
El setter se declarará en la parte pública de la clase.
void MinDistBetweenPartialClosures(CDiff* new_value, bool delete_prev_ptr);
En el cuerpo de la función, primero eliminaremos el puntero miembro m_min_distance_to_close_pos si el parámetro delete_prev_ptr es verdadero y el puntero es dinámico. Después, asignaremos el nuevo valor recibido al puntero interno.
//+------------------------------------------------------------------+ //| 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. | //| Outputs: The function returns no value. | //+------------------------------------------------------------------+ void CConditionalPartials::MinDistBetweenPartialClosures(CDiff *new_value, bool delete_prev_ptr) { //--- Delete previous pointer if necessary if (delete_prev_ptr && ::CheckPointer(m_min_distance_to_close_pos) == POINTER_DYNAMIC) { delete m_min_distance_to_close_pos; } //--- Assign new pointer m_min_distance_to_close_pos = new_value; }
Símbolo Symbol
Para cambiar u obtener el símbolo actual, del cual se extraen los datos como el volumen mínimo o el paso del volumen, agregaremos un setter y un getter. El getter se definirá en la parte pública de la clase.string Symbol() { return m_symbol; }
El setter se declarará a continuación.
void Symbol(string new_symbol);En la definición del cuerpo de la función, asignaremos el nuevo valor new_symbol a la variable miembro m_symbol y posteriormente llamaremos a RefreshSymbolData() para actualizar los datos asociados al símbolo.
//+------------------------------------------------------------------+ //| 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(); }
Modo automático "Auto mode"
Para modificar el modo automático, que determina si las posiciones se agregan de forma automática al array interno m_tracked_positions_arr, declararemos un setter y un getter en la parte pública de la clase.
// 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; }
Número de posiciones rastreadas TrackedPositionsSize
Para obtener la cantidad de posiciones rastreadas, declararemos y definiremos un getter en la parte pública de la clase.Este método devolverá el valor de m_tracked_positions_size.
//--- Tracked positions size int TrackedPositionsSize() const { return m_tracked_positions_size; }
Solo será necesario este getter, ya que el tamaño del array se actualiza de manera interna según la lógica de la clase.
Conclusión
En este artículo abordamos una forma más estructurada de aplicar los cierres parciales en MQL5.
Comenzamos explicando en qué consiste esta técnica de gestión de posiciones, junto con algunas de sus ventajas y posibles limitaciones frente a los cierres parciales simples. Posteriormente, desarrollamos una clase base que servirá como fundamento para la gestión de cierres parciales condicionales.
En los próximos artículos sobre este tema, implementaremos las "condiciones" de cierre parcial que utilizará la clase base CConditionalPartials. Estas condiciones permitirán ejecutar cierres parciales de manera automática sobre las posiciones rastreadas.
Además, mencionar que todo el código tratado en este artículo, así como en artículos anteriores, se encuentra disponible en el siguiente repositorio.
Estructura del repositorio:
| Carpeta | Archivos | Descripción |
|---|---|---|
| Examples | Get_Lot_By_Risk_Per_Trade_and_SL.mq5 Get_Sl_by_risk_per_operation_and_lot.mq5 Risk_Management_Panel.mq5 | Ejemplos prácticos del uso de la librería de gestión de riesgo (RM). |
| IndicatorsCts | IndicatorsBases.mqh | Librería para implementar indicadores simples usando clases. |
| Ob | Bots/ - Art19682 / - Ea.mq5 Indicator/ - Main.mqh - OrderBlockIndPart2.mq5 | Incluye el indicador de Order Blocks, así como los asesores expertos utilizados como ejemplo en otros artículos publicados por el autor (nique_372). |
| PosMgmt | ConditionalPartial / Base/ - Base.mqh - Defines.mqh Breakeven.mqh Partials.mqh | Contiene librerías para la gestión de posiciones: - Breakeven - Cierre parcial - Cierre parcial condicional |
| RM | AccountStatus.mqh LossProfit.mqh LoteSizeCalc.mqh Modificators.mqh OcoOrder.mqh OrdersGestor.mqh RiskManagement.mqh RiskManagementBases.mqh RM_Defines.mqh RM_Functions.mqh | Todos los módulos que conforman la librería de gestión de riesgo RM. |
| Utils | FA \ - Atr.mqh - AtrCts.ex5 - BarControler.mqh - ClasesBases.mqh - Events.mqh - FuncionesBases.mqh - Managers.mqh - SimpleLogger.mqh - Sort.mqh - StringToArray.mqh CustomOptimization.mqh Fibonacci.mqh File.mqh Funciones Array.mqh GraphicObjects.mqh RandomSimple.mqh | Librería de utilidades para la creación de librerías, EAs e indicadores. Incluye funciones para trabajar con arrays, tiempo, conversiones, strings, matemáticas simples, además de clases para manejo de patrones de velas, ATR optimizado, validación de suspensión del PC, entre otros. |
| Sets | Article_19682 \ - ARTICLE_PARTIAL_SET_TEST_2_OB_WITHOUT_PARTIALS.set - ARTICLE_PARTIAL_SET_OB_TEST_2_WITH_PARTIALS.set - ARTICLE_PARTIAL_SET_TEST_1_WITH_PARTIALS.set - ARTICLE_PARTIAL_SET_TEST_1_WITHOUT_PARTIALS.set | Sets utilizados en otros artículos publicados por el autor (nique_372). |
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones
Gestor de riesgos profesional remoto para Forex en Python
Automatización de estrategias de trading en MQL5 (Parte 7): Creación de un EA para el comercio en cuadrícula con escalado dinámico de lotes
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 13): Herramienta RSI Sentinel
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso