Desarrollamos un asesor experto multidivisas (Parte 28): Añadimos un gestor de cierre de posiciones
Introducción
En la Parte 12, ya añadimos un módulo de gestión de riesgos al asesor multidivisa para limitar las pérdidas diarias y totales. Este no aumenta las ganancias, pero resulta fundamental para proteger el capital en condiciones adversas. Está basado en las reglas de prop-trading, con ajustes flexibles: reducción de capital en la divisa, como porcentaje del saldo o desde el inicio del día.
El módulo está implementado como una clase CVirtualRiskManager con métodos para realizar un seguimiento del saldo, las ganancias y la verificación de las restricciones. También se incluye una función de fijación de ganancias: una vez alcanzado el objetivo, todas las posiciones se cierran y la negociación se detiene.
Para las cuentas regulares, querríamos que la negociación se reiniciara automáticamente después de obtener ganancias. Actualmente esto requiere de intervención manual. Ha llegado el momento de automatizar también este proceso.
Para reiniciar las estrategias comerciales una vez alcanzadas las ganancias objetivo, se consideraron dos opciones:
- ampliar el gestor de riesgos actual,
- crear un módulo aparte.
Nos hemos decantado por la segunda opción porque el gestor de riesgos actual opera independientemente de las estrategias: solo cierra posiciones reales sin afectar a las virtuales. Modificar esta lógica complicaría la arquitectura y violaría la independencia modular.
El gestor de riesgos también genera una carga de pruebas adicional, por lo que lo mejor es trasladar la nueva funcionalidad a un módulo independiente; este se puede usar incluso sin que el gestor de riesgos esté en funcionamiento.
El nuevo objetivo es un módulo que puede reiniciar todas las estrategias cuando se cumplen las condiciones especificadas (ganancias, pérdidas, tiempo, etc.), sin depender de la historia y sin intervención manual. Llamaremos al nuevo módulo gestor de cierre, ya que es un módulo independiente cuya presencia no es obligatoria, pero su incorporación puede mejorar los resultados, y gestiona el proceso de cierre completo de todas las posiciones, tanto reales como virtuales.
Requisitos iniciales
Vamos a definir con mayor claridad cuáles serán las responsabilidades del gestor de cierre y qué parámetros necesitará para ello.
El gestor de cierre debe:
- Fijar las ganancias, es decir, cerrar todas las posiciones virtuales cuando se alcancen las ganancias especificadas. En este caso, las posiciones reales también se cerrarán automáticamente. Para controlar este proceso, vamos a introducir tres parámetros:
- El saldo básico. La cantidad de fondos en una cuenta comercial a partir de la cual se calcularán las ganancias o las pérdidas.
- El método de cálculo de ganancias. Puede tomar uno de varios valores posibles, por ejemplo, como un porcentaje del saldo básico o una cantidad fija en la divisa del depósito.
- El valor de ganancias. El número utilizado para calcular las ganancias mediante el método elegido.
- Fijar las pérdidas, es decir, cerrar todas las posiciones virtuales cuando se alcancen las pérdidas especificadas. Este proceso también requiere tres parámetros, uno o dos de los cuales pueden ser compartidos con los parámetros de fijación de ganancias:
- El saldo básico. La cantidad de fondos en una cuenta comercial a partir de la cual se calcularán las ganancias o las pérdidas.
- El método de cálculo de pérdidas. También puede tomar uno de varios valores posibles, al igual que el método de cálculo de ganancias.
- El valor de las pérdidas. El número utilizado para calcular las pérdidas mediante el método seleccionado.
- Habilitar el trailing de ganancias : cuando se alcanzan unas ganancias específicas, las posiciones virtuales no se cierran, sino que se recuerda un nivel de ganancias inferior, en el que las posiciones se cerrarán directamente. Si los ganancias aumentan, este nivel también debería incrementarse. El aumento puede producirse de manera continua o por etapas con cierto incremento. A este proceso se pueden añadir los siguientes parámetros:
- Habilitar trailing (Sí / No).
- Método para ajustar el nivel. En este parámetro podemos seleccionar el método preferido para establecer el nivel de habilitación de trailing. Por ejemplo, el nivel se puede establecer como un porcentaje de ganancias fijas o como un valor absoluto en la divisa de la cuenta comercial.
- Nivel de inicio de trailing. El número utilizado para calcular el nivel de inicio de trailing para el método seleccionado.
- Tamaño del paso. El número usado para calcular el tamaño del paso en el que se restablece el nivel de trailing. Para el cálculo, podemos utilizar el mismo método que para el nivel de inicio de trailing.
- Habilitar el nivel de ausencia de pérdidas: cuando las ganancias alcanzan este valor, se recuerda un pequeño nivel de ganancia positivo, en el que se cerrarán las posiciones. Con un mayor aumento de las ganancias, este nivel, a diferencia del trailing, no aumentará. Los parámetros que controlan este proceso pueden ser los siguientes:
- Habilitar ausencia de pérdidas (Sí/No).
- Método para ajustar el nivel. Este parámetro es similar al parámetro homónimo para el trailing, es decir, también puede ser relativo o absoluto.
- Nivel de activación de ausencia de pérdidas. Número utilizado para calcular el punto de ausencia de pérdidas del método seleccionado.
Repositorio del proyecto
En la Parte 25, añadimos una nueva estrategia y analizamos cómo crear un proyecto para optimizar automáticamente la estrategia seleccionada y crear un asesor final que incluya muchas instancias de estrategias comerciales con diferentes parámetros. Todo el código se dividió en dos partes: la biblioteca y el proyecto. Para la parte de la biblioteca, ya hemos creado en la Parte 26 un repositorio de código público Adwizard en el repositorio MQL5 Algo Forge. Sin embargo, en lo que respecta a al parte del proyecto, esto aún no se ha hecho.
Vamos a solucionar esto y crear un nuevo repositorio SimpleCandles. Este repositorio contendrá la parte del proyecto para crear el asesor final usando estrategias homónimas. Más allá de la rama principal main, también haremos una rama de desarrollo con el nombre develop. Si este proyecto es objeto de varios artículos, las ediciones relacionadas con los diferentes artículos se dividirán en distintas ramas derivadas de la rama develop. Cuando estén listas, se integrarán de nuevo en las ramas develop y main.
Primero creamos una carpeta local para contener la carpeta del proyecto, por ejemplo, MQL5/Experts/Articles/17608. Luego clonamos este repositorio en la carpeta seleccionada y creamos una carpeta dentro de ella, Include. En esta carpeta colocaremos el repositorio de la biblioteca de la que depende este proyecto. Después clonamos en la carpeta Include el repositorio de la biblioteca Adwizard.
Tras estas operaciones, obtendremos aproximadamente la siguiente estructura de carpetas en la carpeta de la terminal:

Fig. 1. Estructura de carpetas en el repositorio del proyecto después de clonar las partes del proyecto y la biblioteca.
En la carpeta del repositorio clonado Adwizard pasamos a la rama develop. Será común a todos los artículos. Sin embargo, a medida que avancemos en este proyecto, realizaremos cambios en la biblioteca Adwizard, en este repositorio crearemos una nueva rama, generada a partir de la rama develop.
Después de esto, también crearemos una rama separada en el repositorio del proyecto SimpleCandles para trabajar en este artículo y empezar el desarrollo en ella.
Preparando el código de la biblioteca
Antes de comenzar a implementar el gestor de cierre, prepararemos el terreno para su implementación. En primer lugar, observamos que las últimas versiones de MetaTrader han añadido una comprobación de tipos de variables más estricta, razón por la cual el código compilado anteriormente ahora produce errores del tipo:
parameter convertion type 'short[260]' to 'ushort[] &' is not allowed MTTester.mqh int user32::GetClassNameW(long,ushort&[],int) winuser.mqh
Por suerte, en el código que utilizamos, esto ocurre solo en un lugar y lo hemos solucionado cambiando el tipo de array:
static string GetClassName( const HANDLE Handle ) { string Str = NULL; ushort Buffer[MAX_PATH] = {0}; if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer))) Str = ::ShortArrayToString(Buffer); return(Str); }
Sin embargo, tras actualizaciones posteriores del terminal, este archivo ha sido reemplazado por completo con la última versión de la biblioteca MultiTester para corregir un comportamiento incorrecto que tenía otra causa.
El siguiente cambio está relacionado con la necesidad de que el gestor de cierre inicie el cierre de todas las posiciones. Así, añadimos a la clase de asesor CVirtualAdvisor un método independiente para cerrar todas las posiciones, de modo que el gestor de cierres pueda llamarlo si es necesario.
Para implementar este método, ya tenemos todo lo necesario: cada estrategia heredada de CVirtualStrategy posee un método para cerrar todas sus posiciones virtuales. Por ello, en la clase asesor, solo necesitamos llamar a este método para cada estrategia:
//+------------------------------------------------------------------+ //| Закрытие позиций всех стратегий | //+------------------------------------------------------------------+ void CVirtualAdvisor::Close(void) { // Для всех стратегий вызываем метод закрытия виртуальныхпозиций FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close(); }
En la Parte 27, nosotros creamos un componente para mostrar un texto de varias líneas en una ventana que se expande para cubrir todo el gráfico al que está vinculado el asesor. Lo elaboramos como parte de otro proyecto, pero también nos será útil aquí. Por consiguiente, lo transferiremos a la biblioteca Adwizard, colocando el archivo con la claseCConsoleDialog en la carpeta Adwizard/Utils. Para usarlo, agregaremos la creación de un objeto de esta clase en el asesor final en el archivo Adwizard/Experts/Expert.mqh :
CConsoleDialog *dialog; // Диалог для вывода текста с результатами //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // ... // Создаём и запускаем диалог для вывода результатов dialog = new CConsoleDialog(); dialog.Create(__NAME__ + ":" + (string) magic_); dialog.Run(); // Успешная инициализация return(INIT_SUCCEEDED); }
En la función para procesar un nuevo tick en el mismo archivo, agregaremos la configuración de nuevo texto para este objeto. Obtendremos el texto en sí del objeto de clase CVirtualAdvisor llamando a su método Text(), que escribiremos a continuación:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { expert.Tick(); // ... // Выводим текст с информацией о работе советника if (IsNewBar(Symbol(), PERIOD_M1)) { dialog.Text(expert.Text()); } }
Para evitar que las líneas de apertura de posición virtual se dibujen sobre el fondo del texto, deshabilitaremos temporalmente su visualización dejando vacío el método CVirtualChartOrder::Show():
//+------------------------------------------------------------------+ //| Показ виртуальной позиции (ордера) | //+------------------------------------------------------------------+ void CVirtualChartOrder::Show() { return; // ... }
Propiedad IsActive para todos los descendientes de CFactorable
Cuando realizamos la optimización en instrumentos comerciales que incluyen criptomonedas, y el inicio se efectúa en un bróker que no admite criptomonedas, puede producirse un error al iniciar el asesor final. Este consiste en intentar obtener la historia de negociación y las propiedades de un símbolo que no está incluido en la descripción general del mercado. En este caso, si el asesor final contiene una gran cantidad de instancias de estrategias comerciales que operan con los símbolos disponibles, simplemente podemos desactivar las estrategias para aquellos instrumentos que no estén incluidos en la descripción general del mercado.
Actualmente, todas las estrategias comerciales son descendientes de la clase CFactorable, lo cual permite crear objetos de estas estrategias a partir de la cadena de inicialización. Esta clase contempla la posibilidad de que la cadena de inicialización no sea del todo correcta. Entonces, este objeto y todos los objetos anteriores de la cadena de inicialización común se considerarán no válidos. En esta situación, el asesor no podrá inicializarse ni continuar trabajando.
Lo que nos gustaría es que cierto tipo de "error" en la cadena de inicialización nos permitiera simplemente ignorar parte de la cadena de inicialización, creando finalmente un objeto experto a partir de la cadena de inicialización completa. Para ello, vamos a añadir una nueva propiedad a la clase CFactorable, que llamaremos m_isActive, y un método para leer su valor, IsActive() :
//+------------------------------------------------------------------+ //| Базовый класс объектов, создаваемых из строки | //+------------------------------------------------------------------+ class CFactorable { private: // ... protected: // ... bool m_isActive; // Объект активен? // ... public: // ... bool IsActive(); // Объект активный? // ... };
En algunas clases, como la clase gestora de riesgos CVirtualRiskManager, dicha propiedad ya existía, por lo que en estas clases eliminaremos su declaración, ya que se hará en la clase básica. Esto también se aplica a la futura clase de gestor de cierre, que también usará esta propiedad para comprobar si está activa.
En el proceso, hemos hecho opcional especificar el gestor de riesgos y el gestor de cierre en la cadena de inicialización agregando una verificación de su presencia al iniciar el asesor en el constructor de la clase CVirtualAdvisor:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CVirtualAdvisor::CVirtualAdvisor(string p_params) { // Запоминаем строку инициализации m_params = p_params; // Читаем строку инициализации объекта группы стратегий string groupParams = ReadObject(p_params); // Читаем строку инициализации объекта риск-менеджера string riskManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualRiskManager")) { riskManagerParams = ReadObject(p_params); } // Читаем строку инициализации объекта менеджера закрытия string closeManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualCloseManager")) { closeManagerParams = ReadObject(p_params); } // Читаем магический номер ulong p_magic = ReadLong(p_params); // Читаем название эксперта string p_name = ReadString(p_params); // Читаем признак работы на только на открытии бара m_useOnlyNewBar = (bool) ReadLong(p_params); // Если нет ошибок чтения, то if(IsValid()) { // Создаём группу стратегий CREATE(CVirtualStrategyGroup, p_group, groupParams); // Инициализируем монитор символов статическим монитором символов m_symbols = CSymbolsMonitor::Instance(); // Инициализируем получателя статическим получателем m_receiver = CVirtualReceiver::Instance(p_magic); // Инициализируем интерфейс статическим интерфейсом m_interface = CVirtualInterface::Instance(p_magic); // Формируем из имени эксперта и параметров имя файла базы данных эксперта для сохранения состояния m_fileName = FileName(p_name, p_magic); // Запоминаем время начала работы (тестирования) m_fromDate = TimeCurrent(); // Сбрасываем время последнего сохранения m_lastSaveTime = 0; // Добавляем к эксперту содержимое группы Add(p_group); // Удаляем объект группы delete p_group; // Создаём объект риск-менеджера if(riskManagerParams != NULL) { m_riskManager = NEW(riskManagerParams); } // Создаём объект менеджера закрытия if(closeManagerParams != NULL) { m_closeManager = NEW(closeManagerParams); m_closeManager.Expert(&this); } } }
Una vez realizados los cambios, podemos pasar a la parte principal: la creación de un gestor de cierre.
Creando un gestor de cierre
Para empezar, debemos destacar varios estados posibles en los que puede encontrarse el gestor encargado del cierre. En condiciones normales, ni las ganancias previstas ni las pérdidas máximas se han alcanzado todavía. Mientras se encuentra en este estado, el gestor de cierre solo tiene que esperar la transición a uno de los siguientes estados. Al alcanzar unas ganancias o unas pérdidas determinadas, se llevará a cabo una transición a dos estados correspondientes. En estos estados, el gestor de cierre debe cerrar todas las posiciones, recordar los nuevos niveles de ganancias y pérdidas especificados y volver al estado normal.
Si la opción de trailing está habilitada, cuando se alcance el nivel de ganancias especificado, el gestor de cierre pasará a otro estado. La transición de vuelta a la normalidad requerirá acciones más complejas, por lo que no las describiremos con detalle por ahora.
Implementaremos todos los estados como un tipo enumerado ENUM_CM_STATE.
Para definir los métodos de cálculo de las ganancias y pérdidas previstas, también crearemos dos tipos de enumeración separados: ENUM_CM_CALC_LOSS y ENUM_CM_CALC_PROFIT. Contemplaremos dos opciones: un valor fijo en términos monetarios y un valor relativo como porcentaje de un saldo básico determinado.
// Возможные состояния менеджера закрытия enum ENUM_CM_STATE { CM_STATE_OK, // Лимиты не превышены CM_STATE_LOSS, // Превышен общий лимит CM_STATE_PROFIT, // Достигнута общая прибыль CM_STATE_TRAIL_PROFIT // Трейлинг прибыли }; // Возможные способы расчёта общего убытка enum ENUM_CM_CALC_LOSS { CM_CALC_LOSS_MONEY_BB, // [$] Fixed Money CM_CALC_LOSS_PERCENT_BB, // [%] of Base Balance }; // Возможные способы расчёта общей прибыли enum ENUM_CM_CALC_PROFIT { CM_CALC_PROFIT_MONEY_BB, // [$] Fixed Money CM_CALC_PROFIT_PERCENT_BB, // [%] of Base Balance };
La clase gestora de cierre heredará de la clase básica CFactorable para proporcionar la capacidad de crear un objeto gestor de cierre a partir de la cadena de inicialización. Al mismo tiempo, contará inmediatamente con una propiedad de actividad heredada para habilitar o deshabilitar fácilmente el gestor de cierre.
Para poder desempeñar su función, el gestor de cierre deberá recordar el nivel de saldo básico a partir del cual se calcularán las ganancias o pérdidas resultantes. Al fijar ganancias o pérdidas, este nivel también debe cambiar al valor del saldo actual de la cuenta alcanzado después de cerrar todas las posiciones. Esta es la diferencia entre este parámetro y el parámetro homónimo en el gestor de riesgos. Allí, el nivel del saldo básico permanece siempre inalterado.
El siguiente grupo de propiedades se usará para seleccionar el método de cálculo y el cálculo en sí de las ganancias y pérdidas previstas. Se utilizarán en los métodos de cálculo LossMoney() y ProfitMoney(), que retornan un valor en términos monetarios.
Para cerrar las posiciones, el gestor de cierre debe poder dirigirse al experto para solicitar el cierre. Por consiguiente, añadiremos un puntero al objeto experto y un método para asignarlo.
Agregaremos además una propiedad más para almacenar el estado actual del objeto gestor de cierre.
La derivación de CFactorable requiere colocar el constructor en un ámbito no público y agregar dos macros especiales, como se describe en la Parte 24.
Como resultado, obtenemos una descripción de la clase del gestor de cierres que se parece a esto:
//+------------------------------------------------------------------+ //| Класс менеджера закрытия (фиксации прибыли и убытков) | //+------------------------------------------------------------------+ class CVirtualCloseManager : public CFactorable { protected: // Основные параметры конструктора double m_baseBalance; // Базовый баланс ENUM_CM_CALC_LOSS m_calcLossLimit; // Способ расчёта максимального общего убытка double m_maxLossLimit; // Параметр расчёта максимального общего убытка ENUM_CM_CALC_PROFIT m_calcProfitLimit; // Способ расчёта максимальной общей прибыли double m_maxProfitLimit; // Параметр расчёта максимальной общей прибыли CVirtualAdvisor* m_expert; // Указатель на объект эксперта // Текущее состояние ENUM_CM_STATE m_state; // Состояние // Обновляемые значения double m_balance; // Текущий баланс double m_equity; // Текущие средства double m_profit; // Текущая плавающая прибыль double m_overallProfit; // Текущая общая прибыль относительно базового баланса // Защищённые методы double LossMoney(); // Максимальный общий убыток double ProfitMoney(); // Максимальная прибыль void UpdateProfit(); // Обновление текущих значений прибыли void CheckLimits(); // Проверка достижения допустимых уровней прибыли/убытка CVirtualCloseManager(string p_params); // Закрытый конструктор public: STATIC_CONSTRUCTOR(CVirtualCloseManager); // Статический метод создания объекта virtual void Tick(); // Обработка тика в менеджере закрытия virtual string Text(); // Информация о текущем состоянии // Привязка эксперта к менеджеру закрытия void Expert(CVirtualAdvisor* p_expert); virtual bool Save(); // Сохранение состояния virtual bool Load(); // Загрузка состояния virtual string operator~() override; // Преобразование объекта в строку }; REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Регистрация нового потомка CFactorable
Vamos a analizar los dos métodos principales de esta clase: el constructor y el método de procesamiento de ticks.
En el constructor, como de costumbre, leemos los valores de los parámetros secuencialmente desde la cadena de inicialización y los asignamos a las propiedades correspondientes, establecemos el estado actual como normal, actualizamos los valores de ganancias actuales y recordamos el valor del saldo actual como valor básico si no se ha establecido directamente:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CVirtualCloseManager::CVirtualCloseManager(string p_params) { // Запоминаем строку инициализации m_params = p_params; // Читаем строку инициализации и устанавливаем значения свойств m_isActive = (bool) ReadLong(p_params); m_baseBalance = ReadDouble(p_params); m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params); m_maxLossLimit = ReadDouble(p_params); m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params); m_maxProfitLimit = ReadDouble(p_params); // Устанавливаем состояние: Лимиты не превышены m_state = CM_STATE_OK; // Обновляем текущие значения прибыли UpdateProfit(); // Корректируем базовый баланс, если он не задан if(m_baseBalance == 0) { m_baseBalance = m_balance; } }
En el método básico de procesamiento de ticks, analizamos el estado actual y, dependiendo de él, o bien comprobamos si se han alcanzado los niveles objetivo de ganancias o pérdidas si el gestor se encontraba en un estado normal, o bien iniciamos el cierre de todas las posiciones y luego pasamos a un estado normal:
//+------------------------------------------------------------------+ //| Обработка тика в риск-менеджере | //+------------------------------------------------------------------+ void CVirtualCloseManager::Tick() { // Если риск-менеджер неактивен, то выходим if(!m_isActive) { return; } // Обновляем текущие значения прибыли UpdateProfit(); // Если менеджер в состоянии трейлинга, то if(m_state == CM_STATE_TRAIL_PROFIT) { // Пока просто будем сразу фиксировать прибыль, // переводя менеджер в соответсвующее состояние if(true) { m_state = CM_STATE_PROFIT; } } // Если менеджер в нормальном состоянии, то if(m_state == CM_STATE_OK) { // Проверяем превышение пределов убытка и прибыли CheckLimits(); } // Если менеджер в состоянии достигнутого убытка или прибыли, то if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) { // Закрываем все позиции m_expert.Close(); // Если все позиции закрыты, то if(PositionsTotal() == 0) { // Переходим в нормальное состояние m_state = CM_STATE_OK; // Обновляем значение базового баланса m_baseBalance = m_balance; } else { // Ждём закрытия всех позиций } // Сохраняем состояние эксперта m_expert.Save(); } }
Para empezar, hemos decidido limitarnos a esta funcionalidad del gestor de cierre, por lo que, por ahora, no utilizaremos el estado de trailing de ganancias.
Transmisión de los parámetros de entrada
Después de crear la clase del gestor de cierres, necesitamos conectarla al asesor. Para ello, deberemos añadir parámetros de entrada para controlar la creación de la cadena de inicialización para el gestor de cierre. Esto debe hacerse en el archivo Adwizard/Experts/Expert.mqh :
// ... //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Использовать группу стратегий" sinput int groupId_ = 0; // - ID группы из новой библиотеки (0 - последняя) sinput bool useAutoUpdate_ = true; // - Использовать автообновление? input group "::: Управление капиталом" sinput double expectedDrawdown_ = 10; // - Максимальный риск (%) sinput double fixedBalance_ = 10000; // - Используемый депозит (0 - использовать весь) в валюте счета input double scale_ = 1.00; // - Масштабирующий множитель для группы input group "::: Менеджер закрытия" input bool cmIsActive_ = true; // - Активен? input double cmStartBaseBalance_ = 0; // - Базовый баланс input ENUM_CM_CALC_LOSS cmCalcLossLimit_ = CM_CALC_LOSS_MONEY_BB; // - Способ расчёта убытка input double cmLossLimit_ = 100; // - Значение убытка для фиксации input ENUM_CM_CALC_PROFIT cmCalcProfitLimit_ = CM_CALC_PROFIT_MONEY_BB; // - Способ расчёта общей прибыли input double cmProfitLimit_ = 1000000; // - Значение общей прибыли для фиксации // ...
Este archivo se incluye al compilar el asesor final, por lo que los parámetros de entrada añadidos estarán disponibles en él. Por defecto, establecemos los valores de ganancias y pérdidas fijas en dinero, en la divisa del depósito. Todavía debemos seleccionar los valores nosotros mismos, así que por ahora no importa lo que especifiquemos en los valores predeterminados.
Pruebas iniciales
Veamos qué tenemos. En primer lugar, comprobamos si funciona correctamente el mecanismo para cerrar posiciones sin tener en cuenta las ganancias obtenidas. Si es así, en la siguiente etapa podremos comenzar a optimizar las ganancias obtenidas.
Para ejecutar el asesor final en el simulador, usaremos la base de datos del asesor final obtenida en la Parte 25 Entonces realizamos una optimización acelerada durante varios intervalos de un año de duración y obtuvimos doce grupos de estrategias almacenados en la tabla strategy_groups :

El archivo de la base de datos se llamaba SimpleCandles-27183.test.db.sqlite. Para que el asesor experto final pueda utilizar esta base de datos, este archivo debe estar ubicado en la carpeta de datos comunes de los terminales MeteTrader 5, en la subcarpeta Files. Además, el nombre del asesor final debe ser SimpleCandles.ex5 y el valor del número mágico en los parámetros de entrada debe dejarse igual a 27183.
Primero ejecutamos el asesor sin usar el gestor de cierre con el primer grupo de estrategias con id_group=20. Para ello, establecemos los siguientes valores para los parámetros de entrada:

Usaremos el mismo intervalo durante el cual se llevó a cabo la optimización automática como intervalo de prueba, es decir, todo el año 2022. Obtenemos los siguientes resultados:


Fig. 2. Resultados del asesor final con id_group=20 sin un gestor de cierre para 2022
Como podemos observar, la optimización ha encontrado combinaciones de parámetros bastante buenas para diferentes instancias de estrategias comerciales simples, lo que ha permitido obtener ganancias significativas durante un intervalo determinado, manteniéndose dentro del límite de pérdidas del 10%.
Ahora vamos a activar el gestor de cierre y a establecer un valor pequeño, por ejemplo 10 USD, como ganancias esperadas por la fijación:

Luego ejecutaremos el asesor en modo de prueba visual. Como hemos añadido la visualización de información sobre el trabajo al asesor final, en este modo podemos ver qué símbolos y cuántas estrategias se utilizan en el grupo con ID 20 de la base de datos del asesor (tres símbolos GBPUSD, EURUSD, EURGBP y 48 estrategias), cuál es el valor del saldo básico del gestor de cierre, el nivel objetivo de ganancias y pérdidas para el cierre.

Fig. 3. Iniciando la prueba visual del asesor con el gestor de cierre habilitado
La figura 3 muestra que el saldo básico del gestor de cierre ya ha alcanzado los 10009,89 dólares, lo que significa que todas las posiciones se han cerrado una vez que se ha alcanzado las ganancias objetivo de 10 dólares.
En el registro vemos la siguiente línea:
2022.01.03 02:31:00 CVirtualCloseManager::CheckLimits | CLOSE PROFIT Profit = 12.94 | OverallProfit = 10.54 (10.00)
El gestor de cierre se ha activado cuando las ganancias totales (ganancias totales = 10,54) en relación con el saldo básico inicial de 10.000 dólares han superado los 10 dólares. Como el modo de prueba solo se ejecuta al comienzo de cada barra de un minuto (OHLC de 1 minuto), el proceso de cierre de todas las posiciones abiertas se ha extendido durante dos minutos consecutivos, por lo que el nuevo nivel básico registrado ha resultado ligeramente inferior a 10010 dólares. Cuando se activa el modo de visualización de todos los ticks, ya no se observan dichas discrepancias.
Ahora vamos a probar el funcionamiento del gestor de cierre con limitación de pérdidas. Luego establecemos un valor pequeño para fijar las pérdidas, por ejemplo 20 dólares, mientras que el valor para fijar las ganancias será más significativo, de modo que haya una alta probabilidad de que se produzcan pérdidas, pero no ganancias.

En los demás parámetros prohibiremos trabajar solo en la apertura de la barra, para que al activar el modo de simulación de todos los tics (Every tick) el asesor experto realice todas las acciones requeridas en cada tick, y no solo al principio de la barra de un minuto:

Iniciaremos las pruebas con un intervalo de un día (03/01/2022). Al filtrar los mensajes de registro, seleccionaremos solo aquellas líneas que se generan cuando se alcanza la pérdida especificada de $20:
2022.01.03 17:11:33 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -33.13 | OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -20.51 | OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -21.20 | OverallProfit = -20.11 (-20.00)
Vemos que esto ha ocurrido tres veces durante el día de prueba, y en el modo de todos los ticks, las ganancias totales (OverallProfit), en las que se activa el cierre de posiciones al alcanzar las pérdidas especificadas, están mucho más cerca del valor especificado en los parámetros.
Tenga en cuenta que la primera entrada del registro anterior contiene la siguiente parte:
Profit = -33.13
Este es el valor de las ganancias actuales en las posiciones abiertas (una ganancia negativa es una pérdida). En este caso, difiere del valor de -$20 porque inicialmente se han cerrado varias posiciones con unas ganancias de aproximadamente $13. Por ello, hemos obtenido unas pérdidas de 20 dólares con respecto al saldo inicial de la cuenta básica, con exactamente este valor de ganancias en las posiciones abiertas.
Así pues, las pruebas iniciales han demostrado que el gestor de cierre desarrollado ya puede realizar la parte básica de su trabajo.
Conclusión
Haremos una breve pausa aquí y continuaremos desarrollando el rol del gestor de cierre en una de las próximas partes. Los planes para el desarrollo futuro de su funcionalidad incluyen principalmente la adición de una función de seguimiento de ganancias para posiciones abiertas y la posibilidad de establecer un nivel de ausencia de pérdidas.
Las mejoras no terminan ahí. Por ejemplo, el gestor de cierre actualmente comprueba si se han cerrado todas las posiciones simplemente esperando a que el número de posiciones abiertas llegue a cero. Pero esto también puede ocurrir si hay posiciones virtuales abiertas, así que veamos si necesitamos utilizar un método de verificación más fiable en este caso. También podríamos tener que organizar la interacción entre el gestor de riesgos y el gestor de cierre: al cerrar, se debe actualizar el estado del gestor de riesgos y viceversa.
Sin embargo, la primera versión ya está hecha y el siguiente paso no se hará desde cero.
Gracias por su atención, ¡hasta pronto!
Advertencia importante
Todos los resultados expuestos en este artículo y en todos los artículos anteriores de la serie se basan únicamente en datos de pruebas históricas y no ofrecen ninguna garantía de lograr ganancias en el futuro. El trabajo de este proyecto es de carácter exploratorio. Todos los resultados publicados pueden ser usados por cualquiera bajo su propia responsabilidad.
Contenido del archivo
| # | Nombre | Versión | Descripción | Cambios recientes |
|---|---|---|---|---|
| SimpleCandles | Carpeta de trabajo del proyecto (debe estar dentro de MQL5/Experts) | |||
| 1 | SimpleCandles.mq5 | 1.01 | Asesor final para el funcionamiento paralelo de varios grupos de estrategias de modelo. Los parámetros se tomarán de la biblioteca de grupos integrada. | Parte 25 |
| └ Optimization | Carpeta de asesores para la optimización de proyectos | |||
| 2 | CreateProject.mq5 | 1.02 | Script asesor para crear un proyecto con etapas, actividades y tareas de optimización. | Parte 25 |
| 3 | Optimization.mq5 | 1.00 | Asesor experto para la optimización automática de proyectos | |
| 4 | Stage1.mq5 | 1.02 | Asesor experto para optimizar una única instancia de una estrategia comercial (Etapa 1) | Parte 25 |
| 5 | Stage2.mq5 | 1.01 | Asesor experto para optimizar un grupo de instancias de estrategias comerciales (Etapa 2) | Parte 25 |
| 6 | Stage3.mq5 | 1.01 | Asesor experto que guarda un grupo normalizado formado de estrategias usando como base los datos del asesor experto con el nombre especificado. | Parte 25 |
| └ Strategies | Carpeta de estrategias del proyecto | Parte 25 | ||
| 7 | SimpleCandlesStrategy.mqh | 1.01 | Clase de la estrategia comercial SimpleCandles | Parte 25 |
| └ Include/Adwizard | Carpeta de la biblioteca de Adwizard | |||
| └ Base | Clases básicas de las que heredan otras clases del proyecto | |||
| 8 | Advisor.mqh | 1.04. | Clase básica del experto | Parte 10 |
| 9 | Factorable.mqh | 1.06 | Clase básica de objetos creados a partir de una cadena (string) | Parte 28 |
| 10 | FactorableCreator.mqh | 1.00 | Clase creadora que enlaza nombres y constructores estáticos de clases herederas CFactorable | Parte 24 |
| 11 | Interface.mqh | 1.01 | Clase básica de visualización de diversos objetos | Parte 4 |
| 12 | Receiver.mqh | 1.04. | Clase básica de transferencia de volúmenes abiertos a posiciones de mercado | Parte 12 |
| 13 | Strategy.mqh | 1.04. | Clase básica de estrategia comercial | Parte 10 |
| └ Database | Archivos para trabajar con todo tipo de bases de datos utilizadas por los asesores de proyectos | |||
| 14 | Database.mqh | 1.12 | Clase para trabajar con la base de datos | Parte 25 |
| 15 | db.adv.schema.sql | 1.00 | Esquema de la base de datos del asesor final | Parte 22 |
| 16 | db.cut.schema.sql | 1.00 | Esquema de una base de datos de optimización truncada | Parte 22 |
| 17 | db.opt.schema.sql | 1.05 | Esquema de la base de datos de optimización | Parte 22 |
| 18 | Storage.mqh | 1.01 | Clase de trabajo con almacenamiento Key-Value para el asesor experto final en la base de datos del asesor experto. | Parte 23 |
| └ Experts | Archivos con partes comunes de asesores usados de distintos tipos | |||
| 19 | Expert.mqh | 1.24 | Archivo de biblioteca para el asesor final. Los parámetros del grupo pueden tomarse de la base de datos del asesor experto | Parte 28 |
| 20 | Optimization.mqh | 1.04. | Archivo de biblioteca para el asesor experto que controla el inicio de las tareas de optimización | Parte 23 |
| 21 | Stage1.mqh | 1.19 | Archivo de biblioteca del asesor experto para optimizar una única instancia de una estrategia comercial (Etapa 1) | Parte 23 |
| 22 | Stage2.mqh | 1.04. | Archivo de biblioteca del asesor experto para optimizar un grupo de instancias de estrategias comerciales (Etapa 2) | Parte 23 |
| 23 | Stage3.mqh | 1.04. | Archivo de biblioteca para el asesor experto que guarda el grupo normalizado generado de estrategias en la base de datos del asesor experto con un nombre especificado. | Parte 23 |
| └ Optimization | Clases responsables del trabajo de optimización automática | |||
| 24 | OptimizationJob.mqh | 1.00 | Clase para el funcionamiento de la etapa de proyecto de optimización | Parte 25 |
| 25 | OptimizationProject.mqh | 1.00 | Clase para el proyecto de optimización | Parte 25 |
| 26 | OptimizationStage.mqh | 1.00 | Clase para la fase del proyecto de optimización | Parte 25 |
| 27 | OptimizationTask.mqh | 1.00 | Clase para la tarea de optimización (para la creación) | Parte 25 |
| 28 | Optimizer.mqh | 1.03 | Clase para el gestor de optimización automática de proyectos | Parte 22 |
| 29 | OptimizerTask.mqh | 1.03 | Clase para la tarea de optimización (para el pipeline) | Parte 22 |
| └ Strategies | Ejemplos de estrategias comerciales usadas para demostrar el funcionamiento del proyecto | |||
| 24 | HistoryStrategy.mqh | 1.00 | Clase de estrategia comercial para reproducir la historia de transacciones | Parte 16 |
| 25 | SimpleVolumesStrategy.mqh | 1.11 | Clase de estrategia comercial con uso de volúmenes de ticks | Parte 22 |
| └ Utils | Utilidades auxiliares, macros para la reducción del código | |||
| 26 | ConsoleDialog.mqh | 1.01 | Clase para mostrar información de texto en un gráfico | Parte 28 |
| 26 | ExpertHistory.mqh | 1.00 | Clase para exportar la historia de transacciones a un archivo | Parte 16 |
| 27 | Macros.mqh | 1.07 | Macros útiles para operaciones con arrays | Parte 26 |
| 28 | MTTester.mqh | — | Archivo para trabajar con el simulador de estrategias de la biblioteca MultiTester | Parte 28 |
| 29 | NewBarEvent.mqh | 1.00 | Clase de definición de una nueva barra para un símbolo específico | Parte 8 |
| 30 | SymbolsMonitor.mqh | 1.01 | Clase de obtención de información sobre instrumentos comerciales (símbolos) | Parte 28 |
| └ Virtual | Clases para crear diversos objetos unidos mediante un sistema de órdenes y posiciones comerciales virtuales | |||
| 31 | Money.mqh | 1.01 | Clase básica de gestión de capital | Parte 12 |
| 32 | TesterHandler.mqh | 1.07 | Clase para gestionar los eventos de optimización | Parte 23 |
| 33 | VirtualAdvisor.mqh | 1.12 | Clase de asesor experto para trabajar con posiciones (órdenes) virtuales | Parte 28 |
| 34 | VirtualChartOrder.mqh | 1.02 | Clase de posición virtual gráfica | Parte 28 |
| 35 | VirtualCloseManager.mqh | 1.00 | Clase de gestor de cierre | Parte 28 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | Clase experta para reproducir la historia de transacciones | Parte 16 |
| 37 | VirtualInterface.mqh | 1.00 | Clase de GUI del asesor | Parte 4 |
| 38 | VirtualOrder.mqh | 1.09 | Clase de órdenes y posiciones virtuales | Parte 22 |
| 39 | VirtualReceiver.mqh | 1.04. | Clase de transferencia de los volúmenes abiertos a las posiciones de mercado (receptor) | Parte 23 |
| 40 | VirtualRiskManager.mqh | 1.06 | Clase de gestión de riesgos (gestor de riesgos) | Parte 28 |
| 41 | VirtualStrategy.mqh | 1.09 | Clase de estrategia comercial con posiciones virtuales | Parte 23 |
| 42 | VirtualStrategyGroup.mqh | 1.04. | Clase de grupo o grupos de estrategias comerciales | Parte 28 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | Clase de receptor simbólico | Parte 3 |
| Common/Files | Carpeta compartida de datos del terminal MetaTrader 5 | |||
| 44 | SimpleCandles-27183.test.db.sqlite | — | Base de datos del asesor final | Parte 25 |
El código fuente también está disponible en los repositorios públicos SimpleCandles y Adwizard
Cómo utilizar los repositorios de código abierto
A medida que avancemos gradualmente hacia el nuevo sistema de almacenamiento Algo Forge, seguiremos trabajando en la mejor y más conveniente manera de utilizarlo. La restricción del MetaEditor a utilizar un único repositorio correspondiente a la carpeta raíz de MQL5 no resulta especialmente cómoda. Los demás repositorios solo estarán disponibles como subcarpetas de la carpeta Shared Projects, lo que facilita un poco la gestión, pero no lo suficiente como para justificar el uso inmediato de este método.
Además, durante la transición, el único repositorio principal se ha creado dos veces en nombre del superadministrador, y la segunda vez, por alguna razón, su creación ha destruido otros repositorios adicionales creados previamente por el usuario. Afortunadamente, la copia local del repositorio permite volver a subirlo al servidor, pero este tipo de acción forzada no es particularmente deseable. Por consiguiente, por ahora esperaremos a que se desarrollen más las funcionalidades del editor MetaEditor para trabajar con repositorios sin utilizarlos directamente.
Esto no tiene ninguna complicación: mientras continuamos trabajando en MetaEditor, simplemente trasladaremos por el momento todas las operaciones relacionadas con el almacenamiento a aplicaciones externas.
Por ejemplo, se puede obtener una copia de todos los archivos con el código de este artículo en la computadora local ejecutando el siguiente script en la consola, habiendo establecido previamente como carpeta actual alguna carpeta dentro de la carpeta MQL5 del terminal MetaTrader correspondiente:
# Creamos una carpeta para el proyecto
mkdir SimpleCandles
# Entramos en la carpeta del proyecto
cd SimpleCandles
# Clonamos el repositorio del proyecto en la carpeta actual
git clone https://forge.mql5.io/antekov/SimpleCandles.git .
# Cambiamos el repositorio a la rama deseada (para este artículo: "article-17608-close-manager")
git checkout article-17608-close-manager
# Comprobamos que hemos cambiado a la rama
git status
# Creamos una carpeta para la parte de la biblioteca
mkdir Include
# Pasamos a ella
cd Include
# Clonamos el repositorio de Adwizard en la carpeta de la biblioteca
git clone https://forge.mql5.io/antekov/Adwizard.git
# Pasamos a la carpeta creada
cd Adwizard
# Cambiamos el repositorio a la rama deseada (para este artículo: " article-17608-close-manager" )
git checkout article-17608-close-manager
# Comprobamos que hemos cambiado a la rama
git status
# Subimos dos niveles hasta la carpeta del proyecto original
cd ./../..
Lo único que falta en estos repositorios es un archivo con la base de datos del asesor final, ya que el código en el repositorio permite obtenerla mediante la realización de una optimización automática. Pero si es necesario, este archivo se puede extraer del archivo del artículo y colocarlo en la carpeta común del terminal, dentro de la carpeta Files.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/17608
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.
Redes neuronales en el trading: Previsión probabilística de series temporales (K2VAE)
De novato a experto: Noticias animadas utilizando MQL5 (IV) Análisis de mercado sobre modelos de IA alojados localmente
Introducción a MQL5 (Parte 18): Introducción al patrón de onda de Wolfe
Redes neuronales en el trading: Segmentación periódica adaptativa (Final)
- 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