English Русский Deutsch 日本語 Português
preview
Desarrollo y prueba de sistemas comerciales basados en el canal de Keltner

Desarrollo y prueba de sistemas comerciales basados en el canal de Keltner

MetaTrader 5Trading | 2 agosto 2024, 10:24
12 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

El concepto de volatilidad en el mercado financiero resulta esencial para entender el trading y hacer que funcione para nosotros. El objetivo principal del artículo consistirá en ofrecer un amplio abanico de resultados de pruebas de sistemas comerciales basados en el concepto de volatilidad utilizando el canal de Keltner para ver cómo puede funcionar como parte de un sistema de medición de la volatilidad o tomar medidas en determinadas condiciones.

Es posible que necesite optimizar su sistema según sus preferencias para obtener los mejores resultados, por lo que deberá hacer su trabajo y probar muchos aspectos diferentes de acuerdo con sus objetivos comerciales para encontrar la mejor configuración final para su sistema.

Aprenderemos a crear indicadores de canal de Keltner y sistemas comerciales personalizados para operar con estrategias comerciales sencillas, y luego los probaremos con diferentes activos financieros y los compararemos para averiguar cuál puede dar mejores resultados según nuestras pruebas y optimización. Intentaremos encontrar la mejor configuración. Abarcaremos los siguientes temas:

Una vez analicemos con detalle los principios de funcionamiento de los indicadores, podremos crear sistemas comerciales basados en estrategias sencillas. A continuación, podremos ponerlas a prueba y ver qué estrategia es mejor que las demás según sus resultados. Espero que este artículo le ayude a mejorar su forma de negociar y le aporte nuevas ideas.
¡Atención! Toda la información del presente artículo se ofrece «tal cual», únicamente con fines ilustrativos, y no supone ningún tipo de recomendación. El artículo no garantiza ningún resultado en absoluto. Todo lo que ponga en práctica usando este artículo como base, lo hará bajo su propia cuenta y riesgo; el autor no garantiza resultado alguno.


Definición de volatilidad

En esta parte, veremos qué es la volatilidad y su importancia en los mercados financiero y comercial. La volatilidad es un concepto estadístico que puede usarse para medir la dispersión de los rendimientos de los activos financieros. Al aumentar la volatilidad, también aumentará el riesgo. Este concepto también se refiere a grandes fluctuaciones en los mercados en ambas direcciones, y este movimiento fluctuante se medirá según la fluctuación en torno al precio medio.

Hay muchas factores que provocan la volatilidad del mercado, por ejemplo:

  • Sentimiento del mercado: la volatilidad del mercado puede verse influida por las emociones de los tráders.
  • Ingresos: los informes de ingresos pueden afectar a la volatilidad del mercado.
  • Eventos e indicadores económicos. Los eventos e indicadores económicos importantes también pueden provocar volatilidad en los mercados, como las noticias sobre la inflación, el PIB y los niveles de empleo.
  • Liquidez: existe una relación entre liquidez y volatilidad en el mercado; cuando la liquidez es baja, la volatilidad aumenta.

Cuando se trata de medir la volatilidad, vemos que existen muchos métodos, como el coeficiente beta y las desviaciones típicas. También hay muchos indicadores técnicos que pueden usarse para medir la volatilidad en el mercado, como las Bandas de Bollinger, el Average True Range (ATR), el Índice de Volatilidad (VIX) y el canal de Keltner, que examinaremos en este artículo. Medir la volatilidad puede resultar muy útil para evaluar las fluctuaciones de un activo financiero.

También existen muchos tipos de volatilidad, como la volatilidad implícita y la volatilidad histórica.


Definición de canal de Keltner

En esta parte, veremos con detalle el indicador del canal de Keltner, cómo utilizarlo y cómo podremos calcular este indicador para entender cómo podemos utilizarlo en nuestro beneficio. El canal de Keltner es un indicador de volatilidad que contiene dos bandas por encima y por debajo del precio, y una media móvil entre ellos.

El canal Keltner fue introducido por primera vez por Chester Keltner en la década de 1960 en su libro "How to Make Money in Commodities". En sus cálculos usó una media móvil simple y un rango de máximos y mínimos. Hoy en día, se suele usar el rango medio verdadero (ATR) para los cálculos. Una configuración típica de media móvil tendría 20 periodos, con las bandas superior e inferior calculadas como el doble del ATR por encima y por debajo de la EMA. Estos parámetros pueden ajustarse según las preferencias y los objetivos comerciales del usuario.

El canal de Keltner se considera alcista cuando el precio alcance la banda superior y bajista cuando el precio alcance la banda inferior, ya que puede utilizarse para determinar la dirección de la tendencia. Las bandas también pueden utilizarse como apoyo y resistencia cuando los precios se mueven entre ellas sin una tendencia clara al alza o a la baja. En resumen, el canal de Keltner es un indicador técnico que mide la volatilidad de los activos financieros usando una media móvil exponencial y un rango medio verdadero. 

A continuación le mostramos cómo podemos calcular el canal de Keltner:

  • Calculamos la media móvil exponencial, que será la línea media del indicador.
  • Calculamos el rango medio verdadero para utilizarlo en el cálculo de las bandas.
  • Calculamos la banda superior para que sea igual a la EMA y le añadimos el resultado de la multiplicación de 2 y ATR.
  • Calculamos una banda inferior igual a la EMA y restamos el resultado de la multiplicación de 2 y ATR.

Bien...

Línea media del canal de Keltner = media móvil exponencial (EMA)

Banda superior del canal de Keltner = media móvil exponencial (EMA) + 2 * rango medio real (ATR)

Banda inferior del canal de Keltner = media móvil exponencial (EMA) - 2 * rango medio real (ATR)


Estrategias con el canal de Keltner

Utilizando las bandas como apoyo o resistencia, o tratando una aproximación a la banda superior como una tendencia alcista y una aproximación a la banda inferior como una tendencia bajista, utilizaremos dos estrategias sencillas:

  • Rebote de bandas: colocaremos una orden de compra en un rebote por encima de la banda inferior y colocaremos una orden de venta en un rebote por debajo de la banda superior.
  • Ruptura de banda: colocaremos una orden de compra cuando se rompa la banda superior y una orden de venta cuando se rompa la banda inferior.

Estrategia uno: rebote de bandas

Podemos utilizar el canal de Keltner para detectar señales de compra y venta según la posición del precio de cierre y de las bandas del indicador. Abriremos una orden de compra cuando el anterior de los últimos precios de cierre se cierre por debajo de la banda inferior anterior y al mismo tiempo el último precio de cierre se cierre por encima de la banda inferior. Abriremos una orden de venta cuando el penúltimo precio de cierre se cierre por encima de la banda superior y al mismo tiempo el último precio de cierre se cierre por debajo de la banda superior.

Esquemáticamente, esto podría visualizarse de la siguiente manera:

Penúltimo precio de cierre < banda inferior y último precio de cierre > banda inferior ==> señal de compra

Penúltimo precio de cierre > banda superior y último precio de cierre < banda superior ==> señal de venta

Segunda estrategia: ruptura de bandas
Según esta estrategia, colocaremos una orden durante la ruptura. Abriremos una orden de compra cuando el penúltimo precio de cierre está por debajo del penúltimo valor de la banda superior y al mismo tiempo el último precio de cierre se cierre por encima del último valor de la banda superior. Por el contrario, colocaremos una orden de venta cuando el penúltimo precio esté por encima del penúltimo valor de la banda inferior y al mismo tiempo el último precio de cierre se cierre por debajo del último valor de la banda inferior.

Esquemáticamente, esto podría visualizarse de la siguiente manera:

Penúltimo precio de cierre < banda superior y último precio de cierre > banda superior ==> señal de compra

Penúltimo precio de cierre > banda inferior y último precio de cierre < banda inferior ==> señal de venta


Sistema comercial con el canal de Keltner

En esta parte crearemos un sistema comercial para colocar órdenes de forma automática basado en la estrategia mencionada. Primero crearemos un indicador personalizado para calcular el canal de Keltner, que utilizaremos más adelante para crear un sistema comercial.

Luego usaremos el preprocesador #property para definir la ubicación del indicador y utilizaremos indicator_chart_window para mostrar el indicador en el gráfico.

#property indicator_chart_window

Al definir el indicador de búfer usando el identificador Indicator_buffers, utilizaremos el valor 3, y al definir el número de líneas (plots) visualizadas, también utilizaremos el valor 3.

#property indicator_buffers 3
#property indicator_plots 3

Después configuraremos los indicadores por tipo, estilo, anchura, color y etiqueta para las líneas superior, intermedia e inferior.

#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_color1 clrRed
#property indicator_label1 "Keltner Upper Band"


#property indicator_type2 DRAW_LINE
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_color2 clrBlue
#property indicator_label2 "Keltner Middle Line"


#property indicator_type3 DRAW_LINE
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
#property indicator_color3 clrGreen
#property indicator_label3 "Keltner Lower Band"

Asimismo, configuraremos el periodo de la media móvil, el multiplicador del canal, la aplicación del ATR en el cálculo de la banda, el método de la media móvil y el tipo de precio.

input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type

Y declararemos tres arrays (superior, medio e inferior) de variables globales, dos manejadores de media móvil y ATR, y minBars.

double upper[], middle[], lower[];
int maHandle, atrHandle;
static int minBars = maPeriod + 1;

En la función OnInit(), enlazaremos el búfer de indicador a los arrays utilizando la función SetIndexBuffer con los siguientes parámetros:

  • index - búfer del índice: 0 - superior, 1 - medio, 2 - inferior.
  • buffer[] - array (upper - superior, middle - medio, lower - inferior).
  • data_type - datos para almacenar el indicador (puede ser uno de ENUM_INDEXBUFFER_TYPE, por defecto es INDICATOR_DATA, que es lo que usaremos).
   SetIndexBuffer(0,upper,     INDICATOR_DATA);
   SetIndexBuffer(1,middle,  INDICATOR_DATA);
   SetIndexBuffer(2,lower,  INDICATOR_DATA);

Ahora estableceremos el indicador AS_Series para los arrays utilizando la función ArraySetAsSeries y sus parámetros:

  • array[] - array por referencia (superior, medio e inferior).
  • flag - dirección de indexación del array (true - éxito, false - error).
   ArraySetAsSeries(upper, true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower, true);

También estableceremos el nombre del indicador utilizando la función IndicatorSetString y sus parámetros:

  • prop_id - identificador (puede ser uno de ENUM_CUSTOMIND_PROPERTY_STRING). Y usaremos INDICATOR_SHORTNAME para establecer el nombre del indicador.
  • prop_value - nombre del indicador deseado.
IndicatorSetString(INDICATOR_SHORTNAME,"Custom Keltner Channel " + IntegerToString(maPeriod));

A continuación, estableceremos los valores del indicador utilizando la función IndicatorSetInteger. Sus parámetros serán los mismos que IndicatorSetString, salvo que el tipo de datos será prop_value en lugar de string.

IndicatorSetInteger(INDICATOR_DIGITS,_Digits);

Luego definiremos el manejador de la media móvil utilizando la función iMA. Aquí tenemos sus parámetros:

  • symbol - nombre del símbolo. NULL - símbolo actual.
  • periodo. 0 será el periodo actual.
  • ma_period - periodo de la media móvil. Utilizaremos maPeriod.
  • ma_shift - desplazamiento horizontal, si fuera necesario.
  • ma_method - método de media móvil. Usaremos maMode.
  • applied_price - tipo de precio. Utilizaremos priceType.
maHandle = iMA(NULL, 0, maPeriod, 0, maMode, priceType);

Luego definiremos ATR según los parámetros de ATR. true - para usar en el cálculo de la banda, false - para no usarlo.

   if(isAtr)
     {
      atrHandle = iATR(NULL, 0, maPeriod);
      if(atrHandle == INVALID_HANDLE)
        {
         Print("Handle Error");
         return(INIT_FAILED);
        }
     }
   else
      atrHandle = INVALID_HANDLE;

Vamos a declarar la función indValue para especificar los buffers del indicador, la media móvil y los valores medio, superior e inferior.

void indValue(const double& h[], const double& l[], int shift)
  {
   double ma[1];
   if(CopyBuffer(maHandle, 0, shift, 1, ma) <= 0)
      return;
   middle[shift] = ma[0];
   double average = AVG(h, l, shift);
   upper[shift]    = middle[shift] + average * multiInp;
   lower[shift] = middle[shift] - average * multiInp;
  }

Declararemos las funciones AVG para calcular la posición de los límites según el multiplicador calculado

double AVG(const double& High[],const double& Low[], int shift)
  {
   double sum = 0.0;
   if(atrHandle == INVALID_HANDLE)
     {
      for(int i = shift; i < shift + maPeriod; i++)
         sum += High[i] - Low[i];
     }
   else
     {
      double t[];
      ArrayResize(t, maPeriod);
      ArrayInitialize(t, 0);
      if(CopyBuffer(atrHandle, 0, shift, maPeriod, t) <= 0)
         return sum;
      for(int i = 0; i < maPeriod; i++)
         sum += t[i];
     }
   return sum / maPeriod;
  }

En la función OnCalculate calcularemos los valores de los indicadores

   if(rates_total <= minBars)
      return 0;
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   int limit = rates_total - prev_calculated;
   if(limit == 0)             
     {
     }
   else
      if(limit == 1)      
        {
         indValue(high, low, 1);
         return(rates_total);
        }
      else
         if(limit > 1)       
           {
            ArrayInitialize(middle, EMPTY_VALUE);
            ArrayInitialize(upper,    EMPTY_VALUE);
            ArrayInitialize(lower, EMPTY_VALUE);
            limit = rates_total - minBars;
            for(int i = limit; i >= 1 && !IsStopped(); i--)
               indValue(high, low, i);
            return(rates_total);
           }
   indValue(high, low, 0);
   return(rates_total);

A continuación le mostramos el código completo en un bloque del canal personalizado de Keltner:

//+------------------------------------------------------------------+
//|                                       Custom_Keltner_Channel.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_color1 clrRed
#property indicator_label1 "Keltner Upper Band"
#property indicator_type2 DRAW_LINE
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_color2 clrBlue
#property indicator_label2 "Keltner Middle Line"
#property indicator_type3 DRAW_LINE
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
#property indicator_color3 clrGreen
#property indicator_label3 "Keltner Lower Band"
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
double upper[], middle[], lower[];
int maHandle, atrHandle;
static int minBars = maPeriod + 1;
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,upper, INDICATOR_DATA);
   SetIndexBuffer(1,middle, INDICATOR_DATA);
   SetIndexBuffer(2,lower, INDICATOR_DATA);
   ArraySetAsSeries(upper, true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower, true);
   IndicatorSetString(INDICATOR_SHORTNAME,"Custom Keltner Channel " + IntegerToString(maPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
   maHandle = iMA(NULL, 0, maPeriod, 0, maMode, priceType);
   if(isAtr)
     {
      atrHandle = iATR(NULL, 0, maPeriod);
      if(atrHandle == INVALID_HANDLE)
        {
         Print("Handle Error");
         return(INIT_FAILED);
        }
     }
   else
      atrHandle = INVALID_HANDLE;
   return INIT_SUCCEEDED;
  }
void indValue(const double& h[], const double& l[], int shift)
  {
   double ma[1];
   if(CopyBuffer(maHandle, 0, shift, 1, ma) <= 0)
      return;
   middle[shift] = ma[0];
   double average = AVG(h, l, shift);
   upper[shift]    = middle[shift] + average * multiInp;
   lower[shift] = middle[shift] - average * multiInp;
  }
double AVG(const double& High[],const double& Low[], int shift)
  {
   double sum = 0.0;
   if(atrHandle == INVALID_HANDLE)
     {
      for(int i = shift; i < shift + maPeriod; i++)
         sum += High[i] - Low[i];
     }
   else
     {
      double t[];
      ArrayResize(t, maPeriod);
      ArrayInitialize(t, 0);
      if(CopyBuffer(atrHandle, 0, shift, maPeriod, t) <= 0)
         return sum;
      for(int i = 0; i < maPeriod; i++)
         sum += t[i];
     }
   return sum / maPeriod;
  }
//+------------------------------------------------------------------+
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(rates_total <= minBars)
      return 0;
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   int limit = rates_total - prev_calculated;
   if(limit == 0)             
     {
     }
   else
      if(limit == 1)      
        {
         indValue(high, low, 1);
         return(rates_total);
        }
      else
         if(limit > 1)       
           {
            ArrayInitialize(middle, EMPTY_VALUE);
            ArrayInitialize(upper,    EMPTY_VALUE);
            ArrayInitialize(lower, EMPTY_VALUE);
            limit = rates_total - minBars;
            for(int i = limit; i >= 1 && !IsStopped(); i--)
               indValue(high, low, i);
            return(rates_total);
           }
   indValue(high, low, 0);
   return(rates_total);
  }
//+------------------------------------------------------------------+

Después de compilar este código podremos encontrarlo en la carpeta del indicador. Tras insertarlo en el gráfico, lo veremos como en el ejemplo siguiente:

KCH_insert

Como podemos ver en el gráfico, tenemos un canal que rodea al precio. Este es el canal Keltner. Ahora deberemos crear nuestro sistema comercial utilizando el indicador basado en las estrategias mencionadas. Podemos hacerlo de la forma que se indica a continuación.

Primero crearemos un sistema comercial para cada estrategia mencionada anteriormente para automatizar la colocación de órdenes de compra y venta.


Estrategia uno: rebote de bandas:

Como hemos dicho, la primera estrategia consiste en considerar la apertura de operaciones basadas en un rebote en las bandas. El asesor deberá abrir las órdenes automáticamente. A continuación le mostraremos una posible implementación en código:

Incluiremos el archivo de comercio utilizando el preprocesador include

#include <trade/trade.mqh>

Luego declararemos los parámetros de entrada: el periodo de media móvil, el multiplicador del canal, el ATR para el cálculo de la banda, el modo de media móvil, el tipo de precio, el tamaño del lote, los puntos de stop loss y take profit.

input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 300;

Después declararemos variables globales para Keltner, barTotal y objeto de comercio

int keltner;
int barsTotal;
CTrade trade;

En la función OnInit(), definiremos la variable barsTotal, que se utilizará más adelante al evaluar la presencia o ausencia de una nueva barra mediante la función iBars. Aquí tenemos sus parámetros:

  • symbol - nombre del símbolo al que se aplica el código. _Symbol - símbolo actual.
  • timeframe - marco temporal. PERIOD_CURRENT - marco temporal actual.
barsTotal=iBars(_Symbol,PERIOD_CURRENT);

Ahora definiremos la variable Keltner, que se utilizará como manejador del indicador, utilizando la función iCustom y sus parámetros:

  • symbol - nombre del símbolo. _Symbl - símbolo actual.
  • period - marco temporal. Utilizaremos el actual (PERIOD_CURRENT).
  • name - nombre del indicador.
  • ...: parámetros del indicador.
keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);

En la función OnDeinit imprimiremos un mensaje al eliminar un asesor

Print("EA is removed");

En OnTick(), declararemos y definiremos una variable entera de barras que se comparará con barsTotal para ver si existe una nueva barra

int bars=iBars(_Symbol,PERIOD_CURRENT);

Nos aseguraremos de que barsTotal sea menor que bars

if(barsTotal < bars)

Si barsTotal es menor que bars, asignaremos bars a barsTotal

barsTotal=bars;

Luego declararemos tres arrays para la parte superior, media e inferior

double upper[], middle[], lower[];

Obtendremos los datos del búfer de indicador utilizando la función CopyBuffer. Aquí tenemos sus parámetros:

  • indicator_handle - manejador del indicador. Utilizaremos Keltner para la parte superior, media e inferior.
  • buffer_num - puntero al número de búfer de indicador (0 - superior, 1 - medio, 2 - inferior).
  • start_pos - posición inicial para el cálculo. Usaremos 0.
  • count - volumen a copiar. Usaremos 3.
  • buffer[] - array de destino para copiar. Utilizaremos los arrays superior, medio e inferior.
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);

Estableceremos el indicador AS_SERIES usando la función ArraySetAsSeries

      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);

Declararemos y determinaremos los valores prevUpper, middle y lower del penúltimo valor del indicador

      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);

Declararemos y determinaremos los valores superior, medio e inferior del último valor del indicador

      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);

Declararemos y determinaremos los precios de cierre último y anterior utilizando la función iClose. Aquí tenemos sus parámetros:

  • symbol - nombre del símbolo.
  • timeframe - marco temporal aplicado.
  • shift - si necesitamos desplazarnos hacia atrás o no.
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);

Luego estableceremos las condiciones de la estrategia para colocar una orden de compra: si el precio de cierre anterior es inferior al valor mínimo anterior y el último precio de cierre es superior al último valor mínimo.

      if(prevLastClose<prevLowerValue && lastClose>lowerValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = lowerValue - slPips*_Point;
         double tpVal = middleValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

A continuación, estableceremos las condiciones de la estrategia para colocar una orden de venta: si el precio de cierre anterior es superior al valor máximo anterior y el último precio de cierre es inferior al último valor máximo.

      if(prevLastClose>prevUpperValue && lastClose<upperValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = upperValue + slPips*_Point;
         double tpVal = middleValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }

Código completo del sistema comercial que trabaja sobre la estrategia de rebote de bandas:

//+------------------------------------------------------------------+
//|                                       Keltner_Trading_System.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 300; 
int keltner;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal < bars)
     {
      barsTotal=bars;
      double upper[], middle[], lower[];
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);
      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);
      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);
      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);
      if(prevLastClose<prevLowerValue && lastClose>lowerValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = lowerValue - slPips*_Point;
         double tpVal = middleValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(prevLastClose>prevUpperValue && lastClose<upperValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = upperValue + slPips*_Point;
         double tpVal = middleValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+


Estrategia dos: ruptura de bandas:

Como hemos dicho, la segunda estrategia consistirá en considerar la apertura de transacciones basadas en la ruptura de las bandas por encima o por debajo. El asesor deberá abrir las órdenes automáticamente. A continuación expondremos las diferencias en la escritura de código de este sistema comercial:

Condiciones para colocar una orden de compra: el penúltimo precio de cierre estará por debajo del penúltimo valor de la banda superior, y el último precio de cierre estará por encima del valor de la última banda superior.

      if(prevLastClose<prevUpperValue && lastClose>upperValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = upperValue - slPips*_Point;
         double tpVal = upperValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

Condiciones para colocar una orden de venta: el penúltimo precio de cierre estará por encima del penúltimo valor de la banda inferior, y último precio de cierre estará por debajo del valor de la última banda inferior.

      if(prevLastClose>prevLowerValue && lastClose<lowerValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = lowerValue + slPips*_Point;
         double tpVal = lowerValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }

Código completo del sistema comercial que trabaja sobre la estrategia de rebote de bandas:

//+------------------------------------------------------------------+
//|                                      Keltner_Trading_System2.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int      maPeriod            = 10;           // Moving Average Period
input double   multiInp             = 2.0;          // Channel multiplier
input bool     isAtr                 = false;        // ATR
input ENUM_MA_METHOD     maMode       = MODE_EMA;     // Moving Average Mode
input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;// Price Type
input double      lotSize=1;
input double slPips = 150;
input double tpPips = 500; 
int keltner;
int barsTotal;
CTrade trade;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal < bars)
     {
      barsTotal=bars;
      double upper[], middle[], lower[];
      CopyBuffer(keltner,0,0,3,upper);
      CopyBuffer(keltner,1,0,3,middle);
      CopyBuffer(keltner,2,0,3,lower);
      ArraySetAsSeries(upper,true);
      ArraySetAsSeries(middle,true);
      ArraySetAsSeries(lower,true);
      double prevUpperValue = NormalizeDouble(upper[2], 5);
      double prevMiddleValue = NormalizeDouble(middle[2], 5);
      double prevLowerValue = NormalizeDouble(lower[2], 5);
      double upperValue = NormalizeDouble(upper[1], 5);
      double middleValue = NormalizeDouble(middle[1], 5);
      double lowerValue = NormalizeDouble(lower[1], 5);
      double lastClose=iClose(_Symbol,PERIOD_CURRENT,1);
      double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2);
      if(prevLastClose<prevUpperValue && lastClose>upperValue)
        {
         double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         double slVal = upperValue - slPips*_Point;
         double tpVal = upperValue + tpPips*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(prevLastClose>prevLowerValue && lastClose<lowerValue)
        {
         double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         double slVal = lowerValue + slPips*_Point;
         double tpVal = lowerValue - tpPips*_Point;
         trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

Probaremos estos asesores de las dos estrategias comerciales con oro (XAUUSD), EURUSD, GBPUSD utilizando el marco temporal H1 con la configuración de entrada por defecto para el periodo comprendido entre el 1 de enero al 31 de diciembre de 2023.

Nos centraremos en las siguientes dimensiones clave para hacer comparaciones entre ellas:

  • Beneficio neto (Net Profit): se calcula restando la pérdida bruta del beneficio bruto. Cuanto mayor sea el valor, mejor.
  • Reducción relativa del balance (Balance DD relative): pérdida máxima en la cuenta durante el funcionamiento. Cuanto menor sea el valor, mejor.
  • Factor de beneficio (Profit Factor): relación entre el beneficio bruto y la pérdida bruta. Cuanto mayor sea el valor, mejor.
  • Expectativa de ganancia (Expected payoff): beneficio o pérdida media de una transacción. Cuanto mayor sea el valor, mejor.
  • Factor de recuperación (Recovery factor): la capacidad de una estrategia probada para recuperarse de las pérdidas. Cuanto mayor sea el valor, mejor.
  • Ratio de Sharpe: determina el riesgo y la estabilidad del sistema comercial probado comparando la rentabilidad con la rentabilidad sin riesgo. Cuanto mayor sea el valor, mejor.


Estrategia uno: rebote de bandas:

Pruebas con XAUUSD

Resultados de la prueba con XAUUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba con XAUUSD, tenemos valores importantes:

  • Beneficio neto: 11918,60 USD.
  • Disminución relativa del balance: 3,67%.
  • Factor de beneficio: 1,36.
  • Expectativa de ganancia: 75,91.
  • Factor de recuperación: 2,85.
  • Ratio de Sharpe: 4,06.

Pruebas con EURUSD

Resultados de la prueba con EURUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba con EURUSD, tenemos valores importantes

  • Beneficio neto: USD 2221,20.
  • Disminución relativa del balance: 2,86%.
  • Factor de beneficio: 1,11.
  • Expectativa de ganancia: 13,63.
  • Factor de recuperación: 0,68.
  • Ratio de Sharpe: 1,09.

Pruebas con GBPUSD

Resultados de la prueba con GBPUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba en GBPUSD, tenemos valores importantes

  • Beneficio neto: USD -1389,40.
  • Disminución relativa del balance: 4,56%.
  • Factor de beneficio: 0,94.
  • Expectativa de ganancia: -8,91.
  • Factor de recuperación: -0,28.
  • Ratio de Sharpe: -0,78.


Estrategia dos: ruptura de bandas:

Pruebas con XAUUSD

Resultados de la prueba con XAUUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba con XAUUSD, tenemos valores importantes:

  • Beneficio neto: USD -11783.
  • Disminución relativa del balance: 12,89%.
  • Factor de beneficio: 0,56.
  • Expectativa de ganancia: -96,58.
  • Factor de recuperación: -0,83.
  • Ratio de Sharpe: -5,00.

Pruebas con EURUSD

Resultados de la prueba con EURUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba con EURUSD, tenemos valores importantes

  • Beneficio neto: USD -1358,30.
  • Disminución relativa del balance: 6,53%.
  • Factor de beneficio: 0,94.
  • Expectativa de ganancia: -8,54.
  • Factor de recuperación: -0,20.
  • Ratio de Sharpe: -0,59.

Pruebas con GBPUSD

Resultados de la prueba con GBPUSD:

gráfico

backtest

backtest2

Como podemos ver en los resultados de la prueba en GBPUSD, tenemos valores importantes

  • Beneficio neto: USD -3930,60.
  • Disminución relativa del balance: 5,06%.
  • Factor de beneficio: 0,84.
  • Expectativa de ganancia: -25,69.
  • Factor de recuperación: -0,75.
  • Ratio de Sharpe: -2,05.

En línea con lo que hemos visto en las pruebas anteriores de estrategias para más de un instrumento, podemos encontrar todos los resultados en una tabla que nos permitirá compararlos fácilmente como se muestra a continuación:

all_results2

Basándonos en la tabla anterior, podemos encontrar los mejores valores correspondientes a la estrategia y el marco temporal probados:
  • Beneficio neto: El mejor rendimiento (USD 11918,60) se ha obtenido usando la estrategia de rebote de banda al realizar pruebas con XAUUSD.
  • Reducción relativa del balance: El mejor indicador (2,86 %) se ha obtenido utilizando la estrategia de rebote de banda al realizar la prueba con EURUSD.
  • Factor de beneficio: El mejor indicador (1,36) se ha obtenido utilizando la estrategia de rebote de banda al realizar la prueba con XAUUSD.
  • Esperanza matemática: El mejor indicador (75,91) se ha obtenido utilizando la estrategia de rebote de banda al realizar la prueba con XAUUSD
  • Factor de recuperación: El mejor indicador (2,85) se ha obtenido utilizando la estrategia de rebote de banda al realizar la prueba con XAUUSD
  • Ratio de Sharpe: El mejor indicador (4,06) se ha obtenido utilizando la estrategia de rebote de banda al realizar la prueba con XAUUSD.

Por consiguiente, la mejor estrategia será la estrategia de rebote de banda con XAUUSD. También podemos reoptimizar las estrategias según nuestras propias preferencias para alcanzar nuestros objetivos comerciales.


Conclusión

Para los tráders resulta muy importante contar con un sistema comercial fiable, y si este sistema comercial identifica y tiene en cuenta todos los aspectos importantes, como la volatilidad y otros, aumentará su fiabilidad.

En este artículo hemos mostrado los resultados de la prueba de varias estrategias sencillas basadas en el canal de Keltner.

Hemos considerado las siguientes estrategias:

  • Rebote de banda: colocamos una orden de compra en un rebote por encima de la banda inferior y colocamos una orden de venta en un rebote por debajo de la banda superior.
  • Ruptura de banda: colocaremos una orden de compra cuando se rompa la banda superior y una orden de venta cuando se rompa la banda inferior.

A continuación, las hemos probado y, basándonos en los resultados de cada estrategia, hemos identificado valores importantes para XAUUSD, EURUSD y GBPUSD.

Como hemos mencionado anteriormente, el objetivo principal de este artículo es compartir algunas ideas sobre diferentes sistemas comerciales que nos puedan empujar a crear mejores sistemas comerciales, por lo que debemos entender que las estrategias mencionadas podrían necesitar una optimización adicional y más esfuerzo para obtener mejores resultados.

Espero que el artículo le haya resultado útil. Si quiere aprender más sobre la construcción de sistemas comerciales basados en diferentes estrategias y distintos indicadores técnicos, puede leer mis artículos anteriores sobre los indicadores técnicos más populares entrando en la sección "Publicaciones" de mi perfil. Espero que también le resulten útiles.

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

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Aplicamos el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza en MQL5 Aplicamos el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza en MQL5
En este artículo, discutiremos cómo utilizar el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza para analizar el comportamiento de las series de precios en MQL5.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Redes neuronales: así de sencillo (Parte 75): Mejora del rendimiento de los modelos de predicción de trayectorias Redes neuronales: así de sencillo (Parte 75): Mejora del rendimiento de los modelos de predicción de trayectorias
Los modelos que creamos son cada vez más grandes y complejos. Esto aumenta los costes no sólo de su formación, sino también de su funcionamiento. Sin embargo, el tiempo necesario para tomar una decisión suele ser crítico. A este respecto, consideremos los métodos para optimizar el rendimiento del modelo sin pérdida de calidad.