English Русский 中文 Deutsch 日本語 Português
preview
Creación de un modelo de restricción de tendencia de velas (Parte 9): Asesor Experto de múltiples estrategias (II)

Creación de un modelo de restricción de tendencia de velas (Parte 9): Asesor Experto de múltiples estrategias (II)

MetaTrader 5Ejemplos |
166 1
Clemence Benjamin
Clemence Benjamin

Contenido:


Introducción

En el siglo XX, Richard Donchian estableció una estrategia de seguimiento de tendencias a través de sus estudios sobre los mercados financieros, que más tarde se convirtió en los canales de Donchian. En un artículo anterior hablamos brevemente sobre su trabajo, pero hoy nos centraremos en la implementación de las estrategias asociadas a su teoría. Según diversas fuentes, se cree que los canales abarcan múltiples estrategias dentro de su marco. La abundancia de literatura sobre los canales de Donchian sugiere la continua eficacia de esta teoría en el trading moderno. Al integrar las estrategias del canal Donchian, nuestro objetivo es ampliar las oportunidades de nuestro experto en restricciones de tendencias, mejorando tanto su rentabilidad como su adaptabilidad a diversas condiciones del mercado.

Algunas estrategias populares basadas en el canal de Donchian que están disponibles en línea incluyen la estrategia de ruptura, la estrategia de rastreo y la estrategia de reversión a la media, entre otras. Algunos traders destacados, como Rayner Teo, también han creado contenido educativo con el objetivo de enseñar a los traders cómo implementar estos canales de manera eficaz.

Los canales de Donchian están disponibles como indicadores gratuitos en la plataforma MetaTrader 5, lo que nos proporciona una ventaja significativa para este proyecto. Este acceso nos permite utilizar el código fuente del indicador y obtener una visión más profunda de su estructura, lo que facilita su adaptación a nuestro Asesor Experto Trend Constraint. A medida que nuestro código sigue aumentando en complejidad, desarrollaremos la nueva estrategia de forma independiente antes de integrarla en el programa principal. En los próximos segmentos, profundizaremos en nuestra comprensión de las teorías y mejoraremos aún más nuestro algoritmo.


¿Qué es el Canal Donchian?

El canal de Donchian es un indicador de análisis técnico que consta de tres líneas, a saber, la banda superior, la línea media y la banda inferior, que se utilizan para trazar máximos más altos y mínimos más bajos durante el movimiento de los precios. El mérito es de Richard Donchian, pionero en el campo del trading de seguimiento de tendencias, como se mencionó anteriormente. A continuación se presenta un breve resumen de las tres líneas:
  • Banda superior: esta línea representa el máximo más alto durante un período específico (por ejemplo, los últimos 20 períodos).
  • Banda inferior: Esta línea muestra el mínimo más bajo durante el mismo período especificado.
  • Línea media: A menudo calculada como el promedio de las bandas superior e inferior, esta línea se utiliza a veces como punto de referencia.
Aquí hay una ilustración del canal Donchian aplicado en MetaTrader 5:


Canal de Donchian

Líneas del canal de Donchian


Cómo acceder al canal de Donchian en MetaTrader 5

Normalmente se accede a él a través de la ventana Navegador, en la pestaña Indicadores, como se muestra en la imagen siguiente.

Acceso al canal Donchian en MetaTrade 5

Navegador de MetaTrader 5

Una vez accedido, puede arrastrar el canal Donchian al gráfico en el que desea utilizarlo, tal y como se muestra en la imagen siguiente. En este ejemplo, estamos utilizando la configuración predeterminada del canal en el índice Volatility 150 (1 s).

Añadir el canal de Donchian al gráfico

Añadir el canal Donchian al gráfico en MetaTrader 5

La razón por la que primero aplicamos el indicador al gráfico es para estudiar la relación entre la acción del precio y el canal. Esto nos ayuda a comprender las reglas de participación antes de comenzar el desarrollo del algoritmo. A continuación, mostraremos cómo acceder al código fuente del indicador en MetaEditor 5.


Cómo acceder al código fuente del Canal Donchian en MetaEditor

Para acceder al archivo fuente y editarlo en MetaEditor 5, abra la ventana Navegador y busque el indicador en la pestaña «Free Indicators», igual que en la plataforma MetaTrader 5. La diferencia clave es que aquí estamos trabajando con el archivo fuente, no con la versión compilada. Haga doble clic en el archivo para ver el código. Consulte las imágenes siguientes para facilitar el seguimiento.

Canales de Donchian en MetaEditor

Acceso al código fuente del canal Donchain en MetaEditor.


Implementación de estrategias del canal de Donchian dentro del Asesor Experto Trend Constraint

Es importante destacar los parámetros rectores que dan forma a nuestros debates y establecen las reglas de participación a la hora de incorporar nuevas herramientas. Desde el principio, siempre definimos las condiciones restrictivas. Por ejemplo, solo compramos cuando hay una vela alcista D1 y vendemos cuando hay una vela bajista D1. Teniendo esto en cuenta, primero identificaremos las condiciones restrictivas antes de buscar oportunidades de orden presentadas por la configuración del canal que se alineen con la tendencia del mercado. Por ejemplo, en un escenario alcista D1, nos centraremos en:

  1. Para que el precio toque el límite inferior del canal y se soliciten órdenes de compra con mayor probabilidad de éxito.
  2. Rebote en la línea media con probabilidad de victoria intermedia.
  3. Rotura del límite superior, para una baja probabilidad de ganancia.

He presentado las tres ideas en la imagen que aparece a continuación.

Canal Donchian por Benjc Trade Advisor

Estrategias del canal de Donchian

Para esta presentación, utilizaré la técnica de ruptura, que supervisa las condiciones cuando el precio de mercado cierra fuera de los límites externos del canal. Ahora veamos el código fuente del indicador e identifiquemos los búferes relevantes.


Vista previa del código fuente del indicador

El indicador de canal Donchian predeterminado está disponible en la plataforma MetaTrader 5. También puede acceder directamente en MetaEditor 5 utilizando los métodos discutidos anteriormente.

//+------------------------------------------------------------------+
//|                                             Donchian Channel.mq5 |
//|                              Copyright 2009-2024, MetaQuotes Ltd |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "2009-2024, MetaQuotes Ltd"
#property link        "http://www.mql5.com"
#property description "Donchian Channel"
//---
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
//--- labels
#property indicator_label1  "Upper Donchian"
#property indicator_label2  "Middle Donchian"
#property indicator_label3  "Lower Donchian"

//--- input parameter
input int  InpDonchianPeriod=20;    // period of the channel
input bool InpShowLabel     =true;  // show price of the level

//--- indicator buffers
double    ExtUpBuffer[];
double    ExtMdBuffer[];
double    ExtDnBuffer[];

//--- unique prefix to identify indicator objects
string ExtPrefixUniq;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- define buffers
   SetIndexBuffer(0, ExtUpBuffer);
   SetIndexBuffer(1, ExtMdBuffer);
   SetIndexBuffer(2, ExtDnBuffer);

//--- set a 1-bar offset for each line
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);
   PlotIndexSetInteger(1, PLOT_SHIFT, 1);
   PlotIndexSetInteger(2, PLOT_SHIFT, 1);

//--- indicator name
   IndicatorSetString(INDICATOR_SHORTNAME, "Donchian Channel");
//--- number of digits of indicator value
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- prepare prefix for objects
   string number=StringFormat("%I64d", GetTickCount64());
   ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
   ExtPrefixUniq=ExtPrefixUniq+"_DN";
   Print("Indicator \"Donchian Channels\" started, prefix=", ExtPrefixUniq);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- if the indicator has previously been calculated, start from the bar preceding the last one
   int start=prev_calculated-1;

//--- if this is the first calculation of the indicator, then move by InpDonchianPeriod bars form the beginning
   if(prev_calculated==0)
      start=InpDonchianPeriod+1;

//--- calculate levels for all bars in a loop
   for(int i=start; i<rates_total; i++)
     {
      //--- get max/min values for the last InpDonchianPeriod bars
      int    highest_bar_index=ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
      int    lowest_bar_index=ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);;
      double highest=high[highest_bar_index];
      double lowest=low[lowest_bar_index];

      //--- write values into buffers
      ExtUpBuffer[i]=highest;
      ExtDnBuffer[i]=lowest;
      ExtMdBuffer[i]=(highest+lowest)/2;
     }

//--- draw labels on levels
   if(InpShowLabel)
     {
      ShowPriceLevels(time[rates_total-1], rates_total-1);
      ChartRedraw();
     }

//--- succesfully calculated
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete all our graphical objects after use
   Print("Indicator \"Donchian Channels\" stopped, delete all objects with prefix=", ExtPrefixUniq);
   ObjectsDeleteAll(0, ExtPrefixUniq, 0, OBJ_ARROW_RIGHT_PRICE);
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+
//|  Show prices' levels                                             |
//+------------------------------------------------------------------+
void ShowPriceLevels(datetime time, int last_index)
  {
   ShowRightPrice(ExtPrefixUniq+"_UP", time, ExtUpBuffer[last_index], clrBlue);
   ShowRightPrice(ExtPrefixUniq+"_MD", time, ExtMdBuffer[last_index], clrGray);
   ShowRightPrice(ExtPrefixUniq+"_Dn", time, ExtDnBuffer[last_index], clrRed);
  }
//+------------------------------------------------------------------+
//| Create or Update "Right Price Label" object                      |
//+------------------------------------------------------------------+
bool ShowRightPrice(const string name, datetime time, double price, color clr)
  {
   if(!ObjectCreate(0, name, OBJ_ARROW_RIGHT_PRICE, 0, time, price))
     {
      ObjectMove(0, name, 0, time, price);
      return(false);
     }

//--- make the label size adaptive
   long scale=2;
   if(!ChartGetInteger(0, CHART_SCALE, 0, scale))
     {
      //--- output an error message to the Experts journal
      Print(__FUNCTION__+", ChartGetInteger(CHART_SCALE) failed, error = ", GetLastError());
     }
   int width=scale>1 ? 2:1;  // if chart scale > 1, then label size = 2

   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);

   return(true);
  }
//+------------------------------------------------------------------+

El código personalizado anterior implementa el canal Donchian. Calcula y muestra tres líneas: la línea del canal superior (que representa el máximo más alto durante un período específico), la línea del canal inferior (que representa el mínimo más bajo durante el mismo período) y una línea media (el promedio de las líneas superior e inferior). El indicador está diseñado para visualizar posibles puntos de ruptura, con parámetros de entrada personalizables para el período del canal y la opción de mostrar etiquetas de precios en el gráfico. El código incluye funciones de inicialización para configurar buffers y propiedades de indicadores, un bucle de cálculo que actualiza las líneas de canal para cada barra del gráfico y funciones para administrar objetos gráficos y etiquetas de precios. En general, proporciona a los traders una herramienta para identificar tendencias y posibles oportunidades comerciales basadas en niveles de precios históricos.

Para una fácil comprensión, dividamos los fragmentos de código anteriores e identifiquemos nuestros buffers para un mayor desarrollo en la siguiente sección.

Declaración de búfer:

Hay tres búferes ExtUpBuffer[], ExtMdBuffer[], y ExtDnBuffer[] declarados que almacenan los valores superior, medio e inferior del canal de Donchian, respectivamente.

double ExtUpBuffer[];
double ExtMdBuffer[];
double ExtDnBuffer[];

Configuración del búfer en OnInit:

La función SetIndexBuffer vincula los trazos del gráfico (líneas) a los búferes, lo que permite dibujarlos y actualizarlos en el gráfico.

SetIndexBuffer(0, ExtUpBuffer);
SetIndexBuffer(1, ExtMdBuffer);
SetIndexBuffer(2, ExtDnBuffer);

Cálculo de valores de búfer en OnCalculate:

Este código calcula los precios más altos, más bajos y medios durante el periodo definido y los almacena en los respectivos búferes para cada barra.

for(int i=start; i<rates_total; i++)
{
   //--- calculate highest and lowest for the Donchian period
   int highest_bar_index = ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
   int lowest_bar_index  = ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);
   double highest = high[highest_bar_index];
   double lowest  = low[lowest_bar_index];

   //--- assign values to buffers
   ExtUpBuffer[i] = highest;
   ExtDnBuffer[i] = lowest;
   ExtMdBuffer[i] = (highest + lowest) / 2;
}

Para generar una señal de compra, la estrategia utiliza el búfer superior (ExtUpBuffer), que activa una compra cuando el precio cierra por encima de la línea Donchian superior. Por el contrario, se activa una señal de venta cuando el precio cierra por debajo de la línea Donchian inferior, definida por el búfer inferior (ExtDnBuffer). Además, el canal medio (ExtMdBuffer) puede actuar como filtro, refinando la estrategia al restringir las operaciones de compra a los casos en los que el precio se encuentra por encima del canal medio, lo que indica una tendencia alcista más fuerte. Teniendo en cuenta esta información, estoy seguro de que ahora podemos proceder a desarrollar nuestro Asesor Experto (EA).


Desarrollo de código

La disponibilidad del canal Donchian como indicador integrado simplifica nuestra tarea, ya que podemos desarrollar un Asesor Experto (EA) que se centre en los buffers del indicador para generar señales para la ejecución de operaciones. Como se mencionó anteriormente, para mayor claridad, primero desarrollaremos un EA basado en el canal de Donchian antes de integrarlo con nuestro Asesor Experto Trend Constraint. Hoy nos centraremos en la estrategia de ruptura utilizando el canal de Donchian. La condición de ruptura es sencilla: se produce cuando el precio cierra más allá de las bandas extremas del canal. Puede consultar la imagen anterior, donde explicamos varias estrategias en detalle.

Para empezar, crearemos un nuevo archivo en MetaEditor 5, tal y como se muestra en la ilustración siguiente. Lo he llamado «BreakoutEA» porque nuestro enfoque principal será esta estrategia de ruptura.

Nuevo EA

Iniciar un nuevo EA en MetaEditor

He dividido el proceso de construcción en cinco segmentos principales, que puede seguir paso a paso a continuación para comprender todo el desarrollo. Inicialmente, al iniciar el EA, comenzaremos con la plantilla básica, dejando las demás partes sin marcar. A continuación de esta plantilla, explicaremos los componentes clave que se unirán al final.

En esta plantilla básica, encontrarás propiedades esenciales, como la directiva (#property strict). Esta directiva garantiza que el compilador imponga el uso correcto de los tipos de datos, lo que ayuda a prevenir posibles errores de programación causados por incompatibilidades entre tipos. Otro aspecto crucial es la inclusión de la biblioteca de operaciones, que proporciona las herramientas necesarias para gestionar las operaciones bursátiles de manera eficiente. Estos pasos sientan una base sólida para el proceso de desarrollo.

//+------------------------------------------------------------------+
//|                                                   BreakoutEA.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/en/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

1. Inicialización del Asesor Experto

En el segmento de inicialización, se definen los parámetros de entrada para el EA. Estos parámetros nos permiten configurar el EA según nuestras preferencias de trading. Las entradas críticas incluyen el período del canal de Donchian, la relación riesgo-recompensa, el tamaño del lote para las operaciones y los valores de pip para stop loss y take profit.

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;

// Expert initialization function
int OnInit() {
    // Create a unique key for the indicator based on the symbol and period
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod);
    
    // Load the Donchian Channel indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    
    // Check if the indicator loaded successfully
    if (handle == INVALID_HANDLE) {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

Se crea una clave única basada en el símbolo comercial y el período definido, lo que garantiza que se pueda diferenciar cada instancia del indicador. La función iCustom() se utiliza para cargar el indicador del canal de Donchian, especificando su ruta dentro del directorio MetaTrader (Free Indicators\\Donchian Channel). Si la carga falla (indicado por un INVALID_HANDLE), se muestra un mensaje de error y la inicialización falla, lo que impide continuar con la ejecución sin los datos indicadores necesarios. Es importante especificar la ubicación de almacenamiento, ya que el indicador no existe en la carpeta raíz del indicador y, si no se hace, se produce un error como se muestra a continuación. En la mayoría de los casos, el EA no se ejecutará si el indicador no se carga correctamente.

//Typical Journal log when the EA fails to locate an indicator in the root indicators storage.
2024.10.20 08:49:04.117 2022.01.01 00:00:00   cannot load custom indicator 'Donchian Channel' [4802]
2024.10.20 08:49:04.118 2022.01.01 00:00:00   indicator create error in 'DonchianEA.mq5' (1,1)
2024.10.20 08:49:04.118 OnInit critical error
2024.10.20 08:49:04.118 tester stopped because OnInit failed

2. Limpieza y desinicialización

El segmento de limpieza es responsable de liberar los recursos que ha utilizado el EA. Esto se realiza en la función OnDeinit() , que se invoca cuando se elimina el EA o cuando MetaTrader 5 se está cerrando. La función garantiza que el indicador se libere utilizando IndicatorRelease(). La limpieza exhaustiva de los recursos es esencial para evitar fugas de memoria y mantener el rendimiento general de la plataforma.

// Expert deinitialization function
void OnDeinit(const int reason) {
    // Release the indicator handle to free up resources
    IndicatorRelease(handle);
}

3. Lógica de ejecución principal

La lógica de ejecución principal reside en la función OnTick() , que se activa con cada tick del mercado o cambio de precio. En esta función, se comprueba si hay alguna posición abierta actualmente utilizando la función PositionsTotal(). Si no hay posiciones abiertas, el programa procede a evaluar las condiciones comerciales invocando una función separada. Esta estructura evita que se abran múltiples operaciones a la vez, lo que podría generar un exceso de operaciones.

// Main execution function with block-based control
void OnTick() {
    // Check if any positions are currently open
    if (PositionsTotal() == 0) {
        CheckTradingConditions();
    }
}

4. Evaluación de las condiciones comerciales

En este segmento, el EA verifica las condiciones del mercado frente a las bandas superior e inferior del Canal de Donchian. Los búferes del indicador se redimensionan para acomodar los datos más recientes. La función CopyBuffer() recupera los valores más recientes del Canal Donchian.

// Check trading conditions based on indicator buffers
void CheckTradingConditions() {
    double ExtUpBuffer[], ExtDnBuffer[];  // Buffers for upper and lower Donchian bands

    // Resize buffers to hold the latest data

    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0) {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1]) {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1]) {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

Se obtiene el precio de cierre actual, que es crucial para evaluar las señales de trading. Las condiciones de negociación se definen de tal manera que se activa una orden de compra si el precio de cierre supera la banda superior, mientras que se coloca una orden de venta si el precio cae por debajo de la banda inferior. Los niveles de stop loss y take profit se calculan en función de los valores de pips definidos por el usuario para gestionar el riesgo de forma eficaz.

5. Funciones de realización de pedidos

Las funciones de colocación de órdenes gestionan la ejecución de operaciones de compra y venta. Cada función intenta realizar una operación utilizando métodos de la clase CTrade, lo que simplifica la gestión de las transacciones. Después de intentar ejecutar una operación, el programa comprueba si la orden se ha realizado correctamente. Si falla, se muestra un mensaje de error para informar al operador sobre el fallo. Estas funciones encapsulan la lógica de negociación y proporcionan una interfaz clara para realizar órdenes basadas en las condiciones establecidas anteriormente.

// Open a buy order
void OpenBuy(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a buy order
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order")) {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

// Open a sell order
void OpenSell(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a sell order
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order")) {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

Nuestro EA de ruptura del canal Donchian ya está completamente desarrollado y listo, todo en un solo lugar.

//+----------------------------------------------------------------------+
//|                                                       BreakoutEA.mq5 |
//|                                    Copyright 2024, Clemence Benjamin |
//|                 https://www.mql5.com/en/users/billionaire2024/seller |
//+----------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
#property strict
#include <Trade\Trade.mqh> // Include the trade library

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;
double ExtUpBuffer[];  // Upper Donchian buffer
double ExtDnBuffer[];  // Lower Donchian buffer

// Trade instance
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod); // Create a unique key for the indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release the indicator handle
    IndicatorRelease(handle);
}

//+------------------------------------------------------------------+
//| Main execution function with block-based control                 |
//+------------------------------------------------------------------+
void OnTick()
{
    // Check if any positions are currently open
    if (PositionsTotal() == 0)
    {
        CheckTradingConditions();
    }
}

//+------------------------------------------------------------------+
//| Check trading conditions based on indicator buffers              |
//+------------------------------------------------------------------+
void CheckTradingConditions()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

//+------------------------------------------------------------------+
//| Open a buy order                                                 |
//+------------------------------------------------------------------+
void OpenBuy(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order"))
    {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order                                                |
//+------------------------------------------------------------------+
void OpenSell(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order"))
    {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+

Probemos el Asesor Experto BreakoutEA antes de incorporarlo al código principal de Trend Constraint. Es importante señalar que esta no es la versión definitiva, ya que aún tenemos que implementar la lógica restrictiva alineada con nuestros objetivos generales.

Añadiendo BreakoutEA al gráfico

Haga clic con el botón derecho en la lista de Asesores Expertos y seleccione “Prueba” para abrir la ventana del probador. Desde allí, puede seleccionar y configurar BreakoutEA para realizar pruebas. Vea el rendimiento a continuación.

En el probador

BreakoutEA funcionando en el Probador de estrategias

¡Bien hecho! Hemos ejecutado con éxito las órdenes, lo cual es un gran logro. Ahora podemos utilizar esta base para mejorar la rentabilidad y filtrar operaciones innecesarias. Esto resalta la importancia de la siguiente etapa, donde incorporaremos restricciones para eliminar operaciones menos probables.


Incorporación a Asesor Experto Trend Constraint

La fusión de dos códigos implica combinar las funciones de ambos lados, y las funciones compartidas se vuelven primarias en el Asesor Experto final. Además, las funciones únicas de cada EA ampliarán el tamaño general y las capacidades del código fusionado. Por ejemplo, hay propiedades que existen tanto en Trend Constraint como en BreakoutEA, y las incorporaremos en un solo programa. Consulte el fragmento de código a continuación, que resalta estas propiedades compartidas.

// We merge it to one
#property strict
#include <Trade\Trade.mqh>  // Include the trade library

Ahora, procedamos a la incorporación. Al fusionar dos o más estrategias en un único Asesor Experto, las funciones principales que siguen siendo fundamentales para la lógica general de negociación incluyen las funciones OnInit(), OnTick() y las funciones de gestión de posiciones (como OpenBuy() y OpenSell()). Estas funciones constituyen el núcleo del EA, encargándose respectivamente de la inicialización de los indicadores, el análisis del mercado en tiempo real y la colocación de órdenes.

Mientras tanto, las características de la estrategia de ruptura del canal Donchian y el RSI con la estrategia de seguimiento de tendencias de media móvil en Trend Constraint se convierten en extensiones del programa existente, incorporadas como condiciones distintas dentro de la función OnTick(). El EA evalúa simultáneamente tanto las señales de ruptura del canal Donchian como las señales de tendencia del RSI y las medias móviles, lo que le permite reaccionar de forma más completa a las condiciones del mercado.

Al integrar estas funciones independientes, el EA mejora su capacidad de toma de decisiones, lo que da lugar a una estrategia de trading más sólida capaz de adaptarse a las diferentes dinámicas del mercado.

1. Funciones de inicialización

La función de inicialización (OnInit()) es crucial, ya que configura los indicadores necesarios para las dos estrategias de trading que empleará el Asesor Experto. Esta función se activa cuando se carga el EA por primera vez y garantiza que todos los componentes esenciales estén listos antes de que comiencen las operaciones bursátiles. Inicializa el RSI (Índice de Fuerza Relativa) y el indicador del Canal de Donchian. Si alguno de estos indicadores no se inicializa correctamente, la función devolverá un estado de error, lo que impedirá que el sistema de negociación se ejecute y evitará posibles riesgos de mercado.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Initialize RSI handle
    rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
    if (rsi_handle == INVALID_HANDLE)
    {
        Print("Failed to create RSI indicator handle");
        return INIT_FAILED;
    }

    // Create a handle for the Donchian Channel
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the Donchian Channel indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

2. Lógica de ejecución principal

La lógica de ejecución principal se gestiona en la función OnTick(), que se invoca cada vez que se produce un tick en el mercado. Esta función es el núcleo del EA, ya que coordina la evaluación de diversas estrategias de negociación en respuesta a las condiciones cambiantes del mercado. Llama secuencialmente a las funciones responsables de comprobar la estrategia de seguimiento de tendencias y la estrategia de ruptura. Además, incluye una comprobación de órdenes caducadas, lo que permite al EA gestionar el riesgo de forma eficaz al garantizar que no permanezcan activas posiciones obsoletas.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Execute both strategies independently on each tick
    CheckTrendConstraintTrading();
    CheckBreakoutTrading();
    CheckOrderExpiration(); // Check for expired Trend Following orders
}

3. Funciones modulares de condiciones comerciales

Estrategia de seguimiento de tendencias:

Esta función comprueba si hay alguna posición abierta antes de proceder con la toma de decisiones. Si no hay posiciones abiertas, recupera el valor actual del RSI y calcula las medias móviles a corto y largo plazo para determinar la tendencia del mercado. Si el mercado se encuentra en una tendencia alcista y el RSI indica condiciones de sobreventa, puede colocar una orden de compra. Por el contrario, si el mercado se encuentra en una tendencia bajista y el RSI indica condiciones de sobrecompra, puede colocar una orden de venta. Si hay posiciones abiertas, la función las gestiona con un mecanismo de trailing stop.

//+------------------------------------------------------------------+
//| Check and execute Trend Constraint EA trading logic              |
//+------------------------------------------------------------------+
void CheckTrendConstraintTrading()
{
    // Check if there are any positions open
    if (PositionsTotal() == 0)
    {
        // Get RSI value
        double rsi_value;
        double rsi_values[];
        if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
        {
            Print("Failed to get RSI value");
            return;
        }
        rsi_value = rsi_values[0];

        // Calculate moving averages
        double ma_short = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
        double ma_long = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE);

        // Determine trend direction
        bool is_uptrend = ma_short > ma_long;
        bool is_downtrend = ma_short < ma_long;

        // Check for buy conditions
        if (is_uptrend && rsi_value < RSI_Oversold)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLossPrice = currentPrice - StopLoss * _Point;
            double takeProfitPrice = currentPrice + TakeProfit * _Point;

            // Attempt to open a Buy order
            if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy") > 0)
            {
                Print("Trend Following Buy order placed.");
            }
            else
            {
                Print("Error placing Trend Following Buy order: ", GetLastError());
            }
        }
        // Check for sell conditions
        else if (is_downtrend && rsi_value > RSI_Overbought)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double stopLossPrice = currentPrice + StopLoss * _Point;
            double takeProfitPrice = currentPrice - TakeProfit * _Point;

            // Attempt to open a Sell order
            if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell") > 0)
            {
                Print("Trend Following Sell order placed.");
            }
            else
            {
                Print("Error placing Trend Following Sell order: ", GetLastError());
            }
        }
    }
    else
    {
        // Implement Trailing Stop for open positions
        TrailingStopLogic();
    }
}

Función de estrategia de ruptura: 

Esta función está diseñada para evaluar las condiciones de ruptura basándose en el indicador del canal de Donchian. Cambia el tamaño de los búferes necesarios para capturar los datos más recientes y comprueba si hay posibles oportunidades de ruptura. La estrategia busca niveles de precios específicos que se superen, lo que podría indicar un movimiento significativo de los precios en una dirección. Cuando se cumplan estas condiciones, el EA ejecutará las órdenes correspondientes.

/+-------------------------------------------------------------------+
//| Check and execute Breakout EA trading logic                      |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading Donchian Channel buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);
    
    // Get the daily open and close for the previous day
    double lastOpen = iOpen(Symbol(), PERIOD_D1, 1);
    double lastClose = iClose(Symbol(), PERIOD_D1, 1);

    // Determine if the last day was bullish or bearish
    bool isBullishDay = lastClose > lastOpen; // Bullish if close > open
    bool isBearishDay = lastClose < lastOpen; // Bearish if close < open

    // Check if there are any open positions before executing breakout strategy
    if (PositionsTotal() == 0) // Only proceed if no positions are open
    {
        // Buy condition: Closing price is above the upper Donchian band on a bullish day
        if (closePrice > ExtUpBuffer[1] && isBullishDay)
        {
            double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutBuyOrder(stopLoss, takeProfit);
        }

        // Sell condition: Closing price is below the lower Donchian band on a bearish day
        if (closePrice < ExtDnBuffer[1] && isBearishDay)
        {
            double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutSellOrder(stopLoss, takeProfit);
        }
    }
}


 Comprueba el trading de ruptura:

Esta función recupera los últimos valores del indicador del canal de Donchian para analizar las condiciones del mercado. Determina el precio de cierre de la vela actual y los precios de apertura y cierre diarios del día anterior. Basándose en la comparación de estos precios, identifica si el día anterior fue alcista o bajista. A continuación, la función comprueba si hay posiciones abiertas antes de ejecutar la estrategia de ruptura. Si no hay posiciones abiertas, comprueba las condiciones para abrir una orden de compra (si el precio de cierre está por encima de la banda superior en un día alcista) o una orden de venta (si el precio de cierre está por debajo de la banda inferior en un día bajista). Calcula los niveles de stop loss y take profit para cada operación antes de intentar colocar la orden.

//+------------------------------------------------------------------+
//| Open a buy order for the Breakout strategy                       |
//+------------------------------------------------------------------+
void OpenBreakoutBuyOrder(double stopLoss, double takeProfit)
{
    if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy"))
    {
        Print("Breakout Buy order placed.");
    }
    else
    {
        Print("Error placing Breakout Buy order: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order for the Breakout strategy                      |
//+------------------------------------------------------------------+
void OpenBreakoutSellOrder(double stopLoss, double takeProfit)
{
    if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell"))
    {
        Print("Breakout Sell order placed.");
    }
    else
    {
        Print("Error placing Breakout Sell order: ", GetLastError());
    }
}

4. Comprobación de caducidad de la orden

La función CheckOrderExpiration revisa todas las posiciones abiertas para identificar y cerrar aquellas que hayan superado un plazo de vigencia especificado. Esta funcionalidad es fundamental para mantener un entorno de negociación dinámico, gestionar el riesgo de forma eficaz y evitar que las posiciones antiguas permanezcan abiertas más tiempo del que es estratégicamente recomendable. La función comprueba el número mágico de cada posición para determinar si forma parte de la estrategia de seguimiento de tendencias y compara la hora actual con la hora de apertura de la posición para ver si debe cerrarse.

//+------------------------------------------------------------------+
//| Check for expired Trend Following orders                         |
//+------------------------------------------------------------------+
void CheckOrderExpiration()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            long magicNumber = PositionGetInteger(POSITION_MAGIC);

            // Check if it's a Trend Following position
            if (magicNumber == MagicNumber)
            {
                datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
                if (TimeCurrent() - openTime >= OrderLifetime)
                {
                    // Attempt to close the position
                    if (trade.PositionClose(ticket))
                    {
                        Print("Trend Following position closed due to expiration.");
                    }
                    else
                    {
                        Print("Error closing position due to expiration: ", GetLastError());
                    }
                }
            }
        }
    }
}

5. Lógica del stop dinámico (Trailing Stop)

El método TrailingStopLogic se encarga de gestionar las posiciones abiertas existentes ajustando sus niveles de stop-loss de acuerdo con las reglas de trailing stop. Para las posiciones largas, sube el stop loss si el precio actual supera el umbral del trailing stop. Para las posiciones cortas, reduce el stop loss cuando se cumplen las condiciones. Este enfoque ayuda a asegurar las ganancias al permitir que el stop loss siga los movimientos favorables de los precios, reduciendo el riesgo de pérdidas si el mercado se revierte.

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void TrailingStopLogic()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLoss = PositionGetDouble(POSITION_SL);

            // Update stop loss for long positions
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if (currentPrice - stopLoss > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
            // Update stop loss for short positions
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if (stopLoss - currentPrice > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}

6. Función de limpieza

La función OnDeinit() actúa como una rutina de limpieza que se ejecuta cuando el Asesor Experto se elimina del gráfico. Esta función se encarga de liberar cualquier recurso asignado o identificador de indicador, garantizando que no haya fugas de memoria ni referencias pendientes. Confirma que la EA se ha desinicializado correctamente y registra esta acción.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release indicators and handles
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    Print("Expert deinitialized.");
}


Pruebas y resultados

Aquí está nuestra experiencia con el probador de estrategias en la imagen de abajo, con las nuevas funciones funcionando.

Asesor Experto Trend Constraint. Ruptura de Donchian y seguimiento de tendencias

Asesor Experto Trend Constraint: Estrategias de ruptura del canal de Donchian y seguimiento de tendencias


Conclusión

Nuestro debate abordó el reto de adaptarse a las condiciones cambiantes del mercado mediante la incorporación de múltiples estrategias en un único Asesor Experto. Inicialmente desarrollamos un mini EA de ruptura para gestionar eficazmente el proceso de ruptura antes de integrarlo en nuestro Asesor Experto Trend Constraint principal. Esta integración mejoró la funcionalidad del Asesor Experto al alinear la estrategia de ruptura con el sentimiento del mercado en marcos temporales más amplios, concretamente con el análisis de velas D1, lo que redujo las ejecuciones excesivas de operaciones.

Cada operación ejecutada en el Probador de estrategias se anotó claramente con la estrategia empleada, lo que proporcionó transparencia y claridad a la hora de comprender los mecanismos subyacentes en funcionamiento. Puedo decir que para abordar un problema complejo de manera eficaz, hay que dividirlo en componentes más pequeños y manejables y abordar cada uno de ellos paso a paso, que es lo que hicimos al desarrollar un mini Asesor Experto antes de incorporarlo a uno.

Aunque nuestra implementación muestra potencial, sigue siendo un trabajo en curso con margen de mejora. El Asesor Experto sirve como herramienta educativa, demostrando cómo diferentes estrategias pueden combinarse para obtener mejores resultados en las operaciones. Le animo a que pruebe los Asesores Expertos proporcionados y los modifique para adaptarlos a sus estrategias de trading. Su opinión es fundamental para que podamos explorar juntos las posibilidades de combinar múltiples estrategias en el trading algorítmico.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16137

Archivos adjuntos |
BreakoutEA.mq5 (5.07 KB)
ramontds
ramontds | 31 oct 2024 en 10:26
Aprendí mucho aquí, ¡muchas gracias!
Asesor experto basado en un aproximador MLP universal Asesor experto basado en un aproximador MLP universal
El artículo presenta una forma sencilla y asequible de usar redes neuronales en un asesor comercial que no requiere conocimientos profundos en aprendizaje automático. El método excluye la normalización de la función objetivo y elimina los problemas de "explosión de pesos" y "estupor de la red", posibilitando un aprendizaje intuitivo y un control visual de los resultados.
Explorando la criptografía en MQL5: Un enfoque paso a paso Explorando la criptografía en MQL5: Un enfoque paso a paso
Este artículo analiza la integración de la criptografía en MQL5, mejorando la seguridad y la funcionalidad de los algoritmos de trading. Cubriremos los métodos criptográficos clave y su aplicación práctica en el comercio automatizado.
Redes neuronales en el trading: Conjunto de agentes con mecanismos de atención (MASAAT) Redes neuronales en el trading: Conjunto de agentes con mecanismos de atención (MASAAT)
Hoy le presentamos la estructura multiagente adaptativa de optimización de portafolios (MASAAT), que combina mecanismos de atención y análisis de series temporales. El MASAAT genera un conjunto de agentes que analizan series de precios y cambios direccionales, permitiendo identificar fluctuaciones sustanciales en los precios de los activos a diferentes niveles de detalle.
Optimización de portafolios en Fórex: Síntesis de VaR y la teoría de Markowitz Optimización de portafolios en Fórex: Síntesis de VaR y la teoría de Markowitz
¿Cómo funciona la negociación de portafolios en Fórex? ¿Cómo pueden sintetizarse la teoría de portafolios de Markowitz para optimizar las proporciones de los portafolios y el modelo VaR para optimizar el riesgo de los portafolios? Hoy crearemos un código de teoría de portafolios en el que, por un lado, obtendremos un riesgo bajo y, por otro, una rentabilidad aceptable a largo plazo.