preview
Cierres parciales condicionales (Parte 1): Creación de la clase base

Cierres parciales condicionales (Parte 1): Creación de la clase base

MetaTrader 5Trading |
212 0
Niquel Mendoza
Niquel Mendoza



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.

  1. Condición de cierre parcial (condición): criterio que determina en qué momento se cerrarán parcialmente las posiciones de compra o venta.
  2. 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.

example1

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.

mqlarticles_repository

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.

Creating the ConditionalPartial folder

Figura 3: Creación de la carpeta ConditionalPartial dentro de la carpeta PosMgmt

A continuación, crearemos la carpeta Base.

Creating the Base folder

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.

Creation of the Defines and Base files

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 array
En 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 trade
Finalmente, 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:

Algorithm for buy positions

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

Posiciones de venta:

Algorithm for sell positions

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.
En caso de que esas dos condiciones sean ciertas, se "omitirá" la iteración actual.

    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.
Cuando todas las condiciones anteriores se cumplen, se imprime un mensaje informativo mediante LogInfo(), y se llama a la función AddPositionToTrack(), la cual añadirá la nueva posición al array interno.

//+------------------------------------------------------------------+
//| 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:

CarpetaArchivosDescripció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.mqhLibrerí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).
 
Archivos adjuntos |
Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones
Hoy analizaremos la creación de un panel de arbitraje en el lenguaje MQL5. ¿Cómo obtener tipos de cambio justos en Forex de formas diferentes? En esta ocasión, crearemos un indicador para obtener las desviaciones de los precios de mercado respecto a los tipos justos, y para estimar el beneficio de las vías de arbitraje para cambiar una divisa por otra (como en el arbitraje triangular).
Gestor de riesgos profesional remoto para Forex en Python Gestor de riesgos profesional remoto para Forex en Python
Hoy crearemos un gestor de riesgos profesional remoto para Forex en Python, y los desplegaremos en un servidor paso a paso. En el transcurso del artículo entenderemos cómo gestionar programáticamente los riesgos en Forex, y cómo no agotar más nuestro depósito en el mundo de las divisas.
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 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
En este artículo, creamos un asesor experto de trading con cuadrículas en MQL5 que utiliza el escalado dinámico de lotes. Cubrimos el diseño de la estrategia, la implementación del código y el proceso de backtesting. Por último, compartimos conocimientos clave y mejores prácticas para optimizar el sistema de comercio automatizado.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 13): Herramienta RSI Sentinel Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 13): Herramienta RSI Sentinel
La evolución de los precios puede analizarse eficazmente identificando divergencias, con indicadores técnicos como el RSI que proporcionan señales de confirmación cruciales. En el siguiente artículo, explicamos cómo el análisis automatizado de divergencias del RSI puede identificar continuaciones y reversiones de tendencias, ofreciendo así información valiosa sobre el sentimiento del mercado.