English Русский Deutsch 日本語
preview
Cómo crear y optimizar un sistema de trading basado en ciclos (Detrended Price Oscillator - DPO)

Cómo crear y optimizar un sistema de trading basado en ciclos (Detrended Price Oscillator - DPO)

MetaTrader 5 — Sistemas comerciales |
49 2
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

En este artículo, presentaremos un nuevo concepto mediante el análisis de un indicador técnico y la evaluación de su posible utilidad en los sistemas de trading. Nos centraremos en el Detrended Price Oscillator (DPO).

El objetivo de esta serie de artículos es compartir herramientas prácticas que puedan usarse tanto como técnicas independientes como componentes de un sistema de trading más amplio. Nuestro objetivo es ayudarle a mejorar continuamente su rendimiento en el trading mediante el desarrollo, la prueba y la optimización de estas herramientas. Sin embargo, debe probar a fondo cada método para determinar si se adapta a su estilo de trading y si aporta un valor real. También me gustaría ofrecerle algunos consejos. Si quiere mejorar sus habilidades de programación, resulta fundamental que ponga en práctica lo que aprenda. Intente programar por su cuenta y poner a prueba lo que ha aprendido tanto como pueda, ya que esto le ayudará a sacar el máximo partido al tema que está estudiando.

Abordaremos los siguientes temas sobre el Detrended Price Oscillator (DPO):

  1. Definición del Detrended Price Oscillator (DPO): Conociendo lo mejor posible la definición y el método de cálculo de esta herramienta técnica, llegaremos a comprenderla con claridad.
  2. Indicador personalizado de Detrended Price Oscillator (DPO): El proceso de aprendizaje para programar un indicador personalizado implica modificar el indicador y adaptarlo a las preferencias del usuario.
  3. Estrategias con el Detrended Price Oscillator (DPO): Analizaremos estrategias sencillas con el Detrended Price Oscillator (DPO) que pueden incorporarse a nuestro sistema de trading, con el fin de mejorar nuestra comprensión de cómo aplicarlas.
  4. Sistema de trading basado en el Detrended Price Oscillator (DPO): Se realizará el desarrollo, las pruebas con datos históricos y la optimización de estos sencillos sistemas de trading.
  5. Conclusión

Aviso legal: Toda la información se facilita «tal cual», únicamente con fines educativos, y no está destinada a fines de negociación en los mercados financieros ni a servir de asesoramiento. La información no garantiza ningún tipo de resultado. Si decide usar estos materiales en cualquiera de sus cuentas de trading, lo hará por su cuenta y riesgo y será el único responsable.



Definición del Detrended Price Oscillator (DPO)

El Detrended Price Oscillator (DPO) es una herramienta que puede utilizarse para poner de relieve los ciclos de precios. Esto se consigue filtrando las tendencias a largo plazo. Se centra en los ciclos de precios a corto plazo. Mediante el uso de este indicador es posible identificar situaciones de sobrecompra o sobreventa y localizar los puntos de inflexión.

El indicador DPO compara el precio actual con una media móvil. Esta media móvil se desplaza hacia atrás en el tiempo. El objetivo es aislar los ciclos más cortos. El periodo de la media móvil suele elegirse en función de la duración del ciclo. El DPO es un indicador oscilador, ya que oscila por encima y por debajo de cero, lo que aporta información útil: los valores positivos indican que el precio cotiza por encima de su media desplazada, mientras que los negativos indican que se sitúa por debajo de ella.

Este indicador puede utilizarse para detectar máximos y mínimos cíclicos, así como para determinar el momento adecuado para entrar y salir del mercado. También se puede utilizar junto con otras herramientas para mejorar las señales generadas.

Si le interesa conocer el método de cálculo de este indicador, puede seguir los pasos que se indican a continuación:

  • Seleccione el periodo del DPO (N).
  • Calcule la media móvil simple (SMA) para el periodo determinado en el paso 1.

SMA = (Precio 1 + Precio 2 + ...) / N

  • Desplace la media móvil simple (SMA) hacia la izquierda un número de periodos igual a la mitad del periodo más uno.

desplazamiento = (N/2) + 1

  • Reste la media móvil simple (SMA) desplazada del precio.

DPO de hoy = Precio de hoy - SMA de hoy (desplazada hacia atrás en la magnitud indicada)



Indicador personalizado del Detrended Price Oscillator (DPO)

En esta sección, ofreceremos el código para este indicador DPO paso a paso. Además, crearemos nuestra propia versión personalizada y sencilla para incorporar las funciones y preferencias que necesitaremos para nuestro sistema de trading. Esto le dará una idea de las funciones y preferencias que puede añadir a su propio sistema.

Los pasos siguientes explicarán cómo programar esta línea DPO personalizada, línea por línea:

En primer lugar, especificaremos la descripción del indicador mediante la directiva de preprocesador #property y, a continuación, el valor del identificador.

#property description "Custom Detrended Price Oscillator"

Utilice la directiva de preprocesador #include para incluir el archivo MQH de medias móviles, especificando el nombre del archivo tal y como aparece en el archivo de inclusión. Esto nos permitirá utilizarlo más adelante en nuestro código.

#include <MovingAverages.mqh>

Especifique, como parte de la configuración, la ubicación deseada para mostrar el indicador (indicator_separate_window) mediante la directiva #property.

#property indicator_separate_window
Luego establecemos en 2 el número de búferes para el cálculo del indicador mediante la constante indicator_buffers.
#property indicator_buffers 2

Y configuramos en 1 el número de series gráficas del indicador mediante la constante indicator_plots.

#property indicator_plots   1

La etiqueta del indicador se configura utilizando «indicator_labelN» para establecer la cadena de la etiqueta como «DPO».

#property indicator_label1 "DPO"

El tipo de representación gráfica del indicador se configura mediante la propiedad indicator_typeN; en este caso, utilizaremos DRAW_HISTOGRAM.

#property indicator_type1   DRAW_HISTOGRAM

El color del gráfico se establece mediante indicator_colorN y será clrRoyalBlue.

#property indicator_color1  clrRoyalBlue

La anchura o el grosor del gráfico representado se determina mediante la propiedad «indicator_widthN», cuyo valor será 2.

#property indicator_width1  2

Después añadimos el nivel cero que se mostrará mediante indicator_levelN.

#property indicator_level1 0

Mediante la palabra clave input, declaramos una variable entera como parámetro de entrada del indicador DPO, cuyo valor podrá definir el usuario según sus preferencias, y estableceremos 20 como valor predeterminado.

input int detrendPeriodInp=20; // Period

Después declaramos dos arrays de tipo double, «dpoBuffer» y «maBuffer», y otra variable de tipo entero, «maPeriod».

double    dpoBuffer[];
double    maBuffer[];
int       maPeriod;

En la sección OnInit(), definiremos la función de inicialización del DPO. Cuando se cargue el indicador en el gráfico, esta función se ejecutará automáticamente una vez.

void OnInit()
{

}

Dentro de esta función OnInit(), definiremos la variable maPeriod, que determinará la duración del ciclo que se utilizará para el suavizado en el DPO

maPeriod=detrendPeriodInp/2+1;

Almacenamos y mostramos los valores del DPO y de la media móvil en búferes.

   SetIndexBuffer(0,dpoBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,maBuffer,INDICATOR_CALCULATIONS);

Utilice la función IndicatorSetInteger para establecer el número de decimales del DPO; sus parámetros son:

  • prop_id: para definir un valor entero como identificador de la propiedad del indicador, que es (INDICATOR_DIGITS).
  • prop_value: para definir el valor de la propiedad a partir de _Digits, sumando 1 al número de decimales del símbolo.
IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);

Use la función PlotIndexSetInteger para establecer la primera barra a partir de la cual se iniciará el trazado; sus parámetros son:

  • plot_index: define el índice de trazado, que es 0.
  • prop_id: define el identificador de la propiedad, que es (PLOT_DRAW_BEGIN).
  • prop_value: define el valor que se va a establecer, que es (maPeriod-1).
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,maPeriod-1);

Declare una variable de tipo string (indShortName) para definir el nombre abreviado que se mostrará en el indicador, utilizando StringFormat con dos parámetros: el nombre del indicador y el periodo del indicador introducido por el usuario.

string indShortName=StringFormat("Custom DPO(%d)",detrendPeriodInp);
Establezca el nombre definido utilizando IndicatorSetString con dos parámetros: el identificador de la propiedad, que es INDICATOR_SHORTNAME, y el valor de la propiedad de tipo string, que es el nombre definido.
IndicatorSetString(INDICATOR_SHORTNAME,indShortName);
A continuación, la función OnCalculate para calcular el indicador:
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

  }
Dentro de la función OnCalculate, declaramos una variable entera llamada «start». Declaramos e inicializamos otra variable llamada «index1» con el valor (begin + maPeriod - 1)
   int start;
   int index1=begin+maPeriod-1;

Gestione la inicialización y establezca desde dónde deben comenzar los cálculos si se trata del primer cálculo:

  • Utilice (ArrayInitialize) para inicializar el array con ceros
  • Actualice la variable «start» para que sea igual a «index1»
  • Si «begin» es mayor que 0, establezca el valor de la propiedad correspondiente de la línea del indicador correspondiente utilizando (PlotIndexSetInteger)

De lo contrario, actualizaremos la variable «start» para que sea igual a (prev_calculated - 1).

   if(prev_calculated<index1)
     {
      ArrayInitialize(dpoBuffer,0.0);
      start=index1;
      if(begin>0)
         PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,index1);
     }
   else
      start=prev_calculated-1;

Calcule la media móvil simple utilizando la función definida (SimpleMAOnBuffer), incluida en el archivo MovingAverages.mqh

SimpleMAOnBuffer(rates_total,prev_calculated,begin,maPeriod,price,maBuffer);

Cree un bucle de cálculo que recorra cada barra desde start hasta rates_total para definir dpoBuffer[i] hasta que se elimine el indicador o se cierre el terminal

for(int i=start; i<rates_total && !IsStopped(); i++)
   dpoBuffer[i]=price[i]-maBuffer[i];

Devuelva el nuevo valor una vez finalizada la función OnCalculate

return(rates_total);

Este es el código completo del indicador DPO personalizado, que podemos reunir en un único bloque de código como se muestra a continuación

//+------------------------------------------------------------------+
//|                                                    customDPO.mq5 |
//+------------------------------------------------------------------+
#property description "Custom Detrended Price Oscillator"
#include <MovingAverages.mqh>
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
#property indicator_label1 "DPO"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrRoyalBlue
#property indicator_width1  2
#property indicator_level1 0
input int detrendPeriodInp=20; // Period
double    dpoBuffer[];
double    maBuffer[];
int       maPeriod;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit()
  {
   maPeriod=detrendPeriodInp/2+1;
   SetIndexBuffer(0,dpoBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,maBuffer,INDICATOR_CALCULATIONS);
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,maPeriod-1);
   string indShortName=StringFormat("Custom DPO(%d)",detrendPeriodInp);
   IndicatorSetString(INDICATOR_SHORTNAME,indShortName);
  }
//+------------------------------------------------------------------+
//| Detrended Price Oscillator                                       |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   int start;
   int index1=begin+maPeriod-1;
   if(prev_calculated<index1)
     {
      ArrayInitialize(dpoBuffer,0.0);
      start=index1;
      if(begin>0)
         PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,index1);
     }
   else
      start=prev_calculated-1;
   SimpleMAOnBuffer(rates_total,prev_calculated,begin,maPeriod,price,maBuffer);
   for(int i=start; i<rates_total && !IsStopped(); i++)
      dpoBuffer[i]=price[i]-maBuffer[i];
   return(rates_total);
  }
//+------------------------------------------------------------------+

Tras compilar el código sin errores e insertar el indicador en el gráfico, veremos lo siguiente:

DPOIndicator



Estrategias con el Detrended Price Oscillator (DPO)

En esta sección, presentaremos las siguientes estrategias sencillas para crear este sistema de trading utilizando el DPO:

  • Estrategia de cruce del nivel cero del DPO.
  • Estrategia de validación de tendencias con el DPO.

Estrategia de cruce del nivel cero del DPO:

Esta estrategia consiste en abrir posiciones de compra y venta basándose en el cruce entre el valor del DPO y el nivel cero. La posición de compra se abre cuando el valor anterior del DPO es inferior a 0 y el actual es superior a 0, y la posición de venta se abre cuando el valor anterior del DPO es superior a 0 y el actual es inferior al nivel cero.

En pocas palabras,

Valor anterior del DPO < 0 y valor actual del DPO > 0 --> Comprar

Valor anterior del DPO > 0 y valor actual del DPO < 0 --> Vender

Estrategia de validación de tendencias con el DPO:

Esta estrategia consiste en abrir posiciones de compra y venta basándose en el cruce del precio con la media móvil y en el cruce del DPO con el nivel cero, para validar este último, especialmente en las señales a corto plazo, con fines de optimización. Por lo tanto, la posición de compra se abrirá cuando el precio de cierre sea inferior al valor anterior de la media móvil, el precio ask de la barra actual sea superior al valor actual de la media móvil y el valor del DPO sea superior a 0. Por su parte, la posición de venta se abrirá cuando el precio de cierre sea superior al valor anterior de la media móvil, el precio bid de la barra actual sea inferior al valor actual de la media móvil y el valor del DPO sea inferior a 0.

En pocas palabras,

Precio de cierre < valor anterior de la media móvil y precio ask > valor de la media móvil y valor del DPO > 0 --> Comprar
Precio de cierre > valor anterior de la media móvil y precio bid < valor de la media móvil y valor del DPO < 0 --> Vender

Programaremos, probaremos y optimizaremos cada una de estas estrategias, explorando distintos conceptos para conseguir los mejores resultados posibles. Luego analizaremos esto con más detalle en la siguiente sección.



Sistema de trading basado en el indicador de precio sin tendencia (DPO)

En esta sección, explicaremos cómo crear sistemas de trading automatizados basados en el DPO a partir de las estrategias mencionadas. Lo veremos paso a paso para comprender cómo puede funcionar este sistema, y usted puede tomarlo como punto de partida para desarrollar nuevas ideas y conceptos que le permitan seguir optimizándolo; ese es el objetivo principal de este tipo de artículos. En primer lugar, programaremos un EA sencillo que nos muestre los valores del DPO en el gráfico, para utilizarlo como base para todos los EA del sistema de trading que vamos a crear.

A continuación, configuraremos el parámetro de entrada del asesor experto (EA) para especificar el periodo deseado que se utilizará al invocar el indicador.

input int                 period=20; // Period

Primero, declare una variable de tipo entero para dpo.

int dpo;

En el evento OnInit(), inicializaremos nuestro indicador DPO personalizado llamando a la función iCustom. Sus parámetros son los siguientes:

  • Symbol: El nombre del símbolo como cadena de caracteres. Aquí utilizamos _Symbol para aplicarlo al símbolo del gráfico actual.
  • Period: El intervalo de tiempo como valor de tipo ENUM_TIMEFRAMES. Utilizamos PERIOD_CURRENT para aplicarlo al intervalo de tiempo actual del gráfico.
  • Name: El nombre del indicador personalizado, incluida la ruta correcta en el equipo local (escrita como folder/custom_indicator_name); en nuestro caso, el nombre del indicador (customDPO).
  • Input parameters: Una lista de los parámetros de entrada del indicador, si procede. En este caso, se trata del parámetro period.

A continuación, utilizamos el valor de retorno INIT_SUCCEEDED para confirmar que la inicialización se ha realizado correctamente y pasamos a la siguiente parte del código.

int OnInit()
  {
   dpo = iCustom(_Symbol,PERIOD_CURRENT,"customDPO",period);
   return(INIT_SUCCEEDED);
  }

En el evento OnDeinit(), especificaremos que el mensaje del EA se elimine cuando se produzca el evento de desinicialización.

void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }

En el evento OnTick(), que se activa en los EA cuando se produce un nuevo tick

void OnTick()
  {

  }

Declararemos un array dpoInd[] de tipo double.

double dpoInd[];

Recupere los datos del búfer especificado del indicador DPO mediante la función CopyBuffer. Sus parámetros son:

  • indicator_handle: El identificador del indicador DPO.
  • buffer_num: El número del búfer desde el que se va a copiar; 0 para el indicador DPO.
  • start_pos: Posición inicial, fijada aquí en 0.
  • count: Número de valores que se van a copiar; en este caso, 3.
  • buffer[]: Array de destino en el que se almacenarán los valores copiados (en este caso, dpoInd[]).
CopyBuffer(dpo,0,0,3,dpoInd);

Establezca la bandera AS_SERIES para el array dpoInd[ ] con el fin de indexar sus elementos como una serie temporal. Utilice el nombre del array y el valor de la bandera (true) como parámetros; la función devolverá true si se ejecuta correctamente.

ArraySetAsSeries(dpoInd,true);

Declare y defina una variable dpoVal de tipo double para obtener el valor actual del DPO y normalizarlo a 3 decimales.

double dpoVal = NormalizeDouble(dpoInd[0], 3);

Muestre el valor actual del DPO como comentario en el gráfico.

comment("DPO value = ",dpoVal);

El código completo en un solo bloque puede ser el siguiente:

//+------------------------------------------------------------------+
//|                                                      DPO_Val.mq5 |
//+------------------------------------------------------------------+
input int                 period=20; // Period
int dpo;
int OnInit()
  {
   dpo = iCustom(_Symbol,PERIOD_CURRENT,"customDPO",period);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
    double dpoInd[];
    CopyBuffer(dpo,0,0,3,dpoInd);
    ArraySetAsSeries(dpoInd,true);
    double dpoVal = NormalizeDouble(dpoInd[0], 3);
    Comment("DPO value = ",dpoVal);
  }

Tras compilar sin errores y adjuntar el EA al gráfico, podemos ver el valor del DPO como un comentario dinámico en el gráfico, tal y como se muestra a continuación:

DPOVal

Como puede ver, el valor del DPO que aparece en el gráfico es el mismo que el que muestra el indicador insertado (1794,363). Podemos utilizar esto como base para otros asesores expertos (EA) que implementen estrategias.

La estrategia de cruce del nivel cero del DPO:

Como ya hemos mencionado, la lógica de esta estrategia consiste en programar un EA capaz de enviar órdenes automáticamente cuando el indicador DPO cruza el nivel cero. Tenemos que programar el sistema para que, en cada tick, compruebe los valores actual y anterior del DPO y los compare con el nivel cero. Cuando el valor anterior sea inferior a cero y el valor actual cruce por encima de cero, necesitamos que el programa coloque una orden de compra. La orden de venta se colocará cuando el valor anterior del DPO sea superior a cero y el actual sea inferior a cero.

A continuación se muestra el código completo de este tipo de programa, que se puede ejecutar en MetaTrader 5:

//+------------------------------------------------------------------+
//|                                           DPO_Zero_Crossover.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int                 period=20; // Periods
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;
int dpo;
CTrade trade;
int barsTotal;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   dpo = iCustom(_Symbol,PERIOD_CURRENT,"customDPO",period);
   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 dpoInd[];
      CopyBuffer(dpo,0,0,3,dpoInd);
      ArraySetAsSeries(dpoInd,true);
      double dpoVal = NormalizeDouble(dpoInd[1], 6);
      double dpoPreVal = NormalizeDouble(dpoInd[2], 6);
      double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      if(dpoPreVal<0 && dpoVal>0)
        {
         double slVal=ask - slLvl*_Point;
         double tpVal=ask + tpLvl*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(dpoPreVal>0 && dpoVal<0)
        {
         double slVal=bid + slLvl*_Point;
         double tpVal=bid - tpLvl*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

Como puede ver en el bloque de código anterior, hay diferencias con respecto al código base de DPO Values. Estas diferencias se detallan a continuación.

Incluya el archivo trade.mqh para abrir órdenes

#include <trade/trade.mqh>

Añada más parámetros de entrada con valores predeterminados para el volumen en lotes, el nivel de stop loss y el nivel de take profit, que el usuario podrá especificar.

input int         period=20; // Periods
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;

Declare el objeto trade que se va a utilizar y la variable entera barsTotal.

CTrade trade;
int barsTotal;

En OnInit(), asignamos a barsTotal el valor devuelto por la función iBars, que devuelve el número de barras del historial para el símbolo y el periodo especificados.

barsTotal=iBars(_Symbol,PERIOD_CURRENT);

En OnTick(), declararemos bars como una variable de tipo entero y le asignaremos el valor devuelto por la función iBars.

int bars=iBars(_Symbol,PERIOD_CURRENT);

Estableceremos una condición para comprobar si hay una nueva barra y limitar así la colocación de órdenes: si la variable barsTotal no es igual a bars —lo que significa que se ha formado una nueva barra—, continuaremos con la ejecución del código.

if(barsTotal != bars)

Al formarse una nueva barra, asignar a barsTotal el valor de bars.

barsTotal=bars;

Actualice el valor del DPO de la última barra cerrada, con índice 1, y declare una variable adicional de tipo double para el valor anterior del DPO, con índice 2.

double dpoVal = NormalizeDouble(dpoInd[1], 6);
double dpoPreVal = NormalizeDouble(dpoInd[2], 6);

Declare y defina el precio ask y el precio bid.

double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);

Establezca la condición de la estrategia, declare y defina el stop loss y el take profit, y abrir las órdenes.

if(dpoPreVal<0 && dpoVal>0)
  {
   double slVal=ask - slLvl*_Point;
   double tpVal=ask + tpLvl*_Point;
   trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
  }
if(dpoPreVal>0 && dpoVal<0)
   {
    double slVal=bid + slLvl*_Point;
    double tpVal=bid - tpLvl*_Point;
    trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
   }

Tras compilar el código sin errores y adjuntar el EA al gráfico, podemos comprobar que las posiciones se pueden abrir como en los siguientes ejemplos.

Posición de compra:

DPO_Crossover_Buy

Posición de venta:

DPO_Crossover_Sell

Ahora tenemos que probar todas las estrategias en el par EUR/USD en los marcos temporales de 5 y 15 minutos durante el periodo comprendido entre el 1 de enero y el 31 de diciembre de 2023. Utilizaremos un stop loss de 300 puntos y un take profit de 900 puntos. A la luz de los resultados, nuestro principal enfoque de optimización consistirá en probar conceptos o herramientas adicionales, como la integración de una media móvil. También conviene probar diferentes marcos temporales para ver cuál ofrece mejores resultados.

Para comparar los resultados de las distintas pruebas de las estrategias, nos centraremos en las siguientes métricas clave:

  • Beneficio neto: se calcula restando la pérdida bruta del beneficio bruto, y el valor más alto es el mejor.
  • Reducción relativa del saldo: es la pérdida máxima que sufre la cuenta durante las operaciones; cuanto menor sea, mejor.
  • Factor de beneficio: es la relación entre el beneficio bruto y la pérdida bruta; cuanto mayor sea, mejor.
  • Beneficio esperado: es la ganancia o pérdida media de una operación, y cuanto mayor sea el valor, mejor.
  • Factor de recuperación: mide la capacidad de la estrategia analizada para recuperarse tras sufrir pérdidas; cuanto más alto sea, mejor.
  • Índice de Sharpe: determina el riesgo y la estabilidad del sistema de trading analizado comparando la rentabilidad con la rentabilidad libre de riesgo; cuanto mayor sea el índice de Sharpe, mejor.

Los resultados de las pruebas en el marco temporal de 15 minutos se muestran a continuación:

Results_0CO_15m-700

Results2_0CO_15m-700

Results3_0CO_15m-700

A partir de los resultados anteriores de la prueba en el marco temporal de 15 minutos, podemos destacar los siguientes valores clave:

  • Beneficio neto: 91 520,53 USD.
  • Reducción relativa del saldo: 35,81 %.
  • Factor de beneficio: 1,09.
  • Beneficio esperado: 21,17.
  • Factor de recuperación: 1,48.
  • Índice de Sharpe: 1,07.

Los resultados de las pruebas en el marco temporal de 5 minutos se muestran a continuación:

Results_0CO_5m-700

Results2_0CO_5m-700

Results3_0CO_5m-700

Según los resultados anteriores de la prueba en el marco temporal de 5 minutos, estos son los valores más relevantes:

  • Beneficio neto: 62 258,14.
  • Reducción relativa del saldo: 97,34 %.
  • Factor de beneficio: 1,02.
  • Beneficio esperado: 4,78.
  • Factor de recuperación: 0,30.
  • Índice de Sharpe: 0,17.

Como podemos ver, los resultados de las pruebas de esta estrategia en el gráfico de 15 minutos son mejores que en el de 5 minutos. Sin embargo, la reducción del saldo sigue siendo elevada, por lo que vamos a plantearnos añadir otra herramienta técnica —la media móvil— como medida de optimización para ver si mejora los resultados.

Estrategia de validación de tendencias con el DPO:

Como ya hemos mencionado, esta estrategia consiste en programar un EA capaz de enviar órdenes de forma automática en función de la posición del valor del DPO y de la relación entre la media móvil y los precios. Esto aporta un nivel adicional de confirmación a través de la media móvil. El programa debe comprobar, en cada tick, los valores actual y anterior del DPO, la media móvil, el precio de cierre y los precios bid.

Abrir una orden de compra cuando:

  • El cierre anterior es inferior al valor anterior de la media móvil.
  • El precio ask es superior al valor actual de la media móvil
  • El valor actual del DPO es mayor que cero.

Abrir una orden de venta cuando:

  • El cierre anterior es superior al valor anterior de la media móvil.
  • El precio bid es inferior al valor actual de la media móvil
  • El valor actual del DPO es inferior a cero.

A continuación se muestra el código completo de este tipo de programa, que se puede ejecutar en MT5:

//+------------------------------------------------------------------+
//|                                          DPO_trendValidation.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int         period=20; // Periods
input int         maPeriodInp=20; //MA Period
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;
int dpo;
int ma;
CTrade trade;
int barsTotal;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   dpo = iCustom(_Symbol,PERIOD_CURRENT,"customDPO",period);
   ma = iMA(_Symbol,PERIOD_CURRENT,maPeriodInp,0,MODE_SMA,PRICE_CLOSE);
   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 dpoInd[];
      double maInd[];
      CopyBuffer(dpo,0,0,3,dpoInd);
      CopyBuffer(ma,0,0,3,maInd);
      ArraySetAsSeries(dpoInd,true);
      ArraySetAsSeries(maInd,true);
      double dpoVal = NormalizeDouble(dpoInd[0], 6);
      double maVal= NormalizeDouble(maInd[0],5);
      double dpoPreVal = NormalizeDouble(dpoInd[1], 5);
      double maPreVal = NormalizeDouble(maInd[1],5);;
      double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double prevClose = iClose(_Symbol,PERIOD_CURRENT,1);
      if(prevClose<maPreVal && ask>maVal)
        {
         if(dpoVal>0)
           {
            double slVal=ask - slLvl*_Point;
            double tpVal=ask + tpLvl*_Point;
            trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
           }
        }
      if(prevClose>maPreVal && bid<maVal)
        {
         if(dpoVal<0)
           {
            double slVal=bid + slLvl*_Point;
            double tpVal=bid - tpLvl*_Point;
            trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Las diferencias en el código anterior se indican a continuación.

Añada otro parámetro de entrada para que el usuario pueda especificar el periodo de la media móvil:

input int         maPeriodInp=20; //MA Period

Declare una variable de tipo entero para «ma».

int ma;

Definición de la variable «ma» mediante la función «iMA» para obtener el identificador del indicador de media móvil.

ma = iMA(_Symbol,PERIOD_CURRENT,maPeriodInp,0,MODE_SMA,PRICE_CLOSE);

Declare el array maInd[] de tipo double.

double maInd[];

Recupere datos del búfer del indicador MA especificado mediante la función CopyBuffer.

CopyBuffer(ma,0,0,3,maInd);

Establezca la bandera AS_SERIES para el array maInd[ ] con el fin de indexar sus elementos como una serie temporal.

ArraySetAsSeries(maInd,true);

Declare y defina la variable «maVal» y normalícela.

double maVal= NormalizeDouble(maInd[0],5);

Declare y defina el valor del cierre anterior utilizando la función iClose.

double prevClose = iClose(_Symbol,PERIOD_CURRENT,1);

Condiciones de la estrategia en caso de compra y venta.

 if(prevClose<maPreVal && ask>maVal)
   {
    if(dpoVal>0)
      {
       double slVal=ask - slLvl*_Point;
       double tpVal=ask + tpLvl*_Point;
       trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
      }
   }
if(prevClose>maPreVal && bid<maVal)
   {
    if(dpoVal<0)
      {
       double slVal=bid + slLvl*_Point;
       double tpVal=bid - tpLvl*_Point;
       trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
       }
   }

Tras compilar el código de esta estrategia sin errores y adjuntar su archivo al gráfico, podemos ver que las posiciones de compra y venta se pueden abrir tal y como se muestra en los siguientes ejemplos.

Posición de compra:

DPO_TV_Buy

Posición de venta:

DPO_TV_Sell

Tenemos que probar esta estrategia en el par EURUSD durante el mismo periodo mencionado anteriormente, es decir, todo el año 2023, en los marcos temporales de 15 y 5 minutos. Los resultados de las pruebas de la estrategia de validación de tendencias con el DPO en el marco temporal de 15 minutos son los siguientes:

Results_TV_15m-700

Results2_TV_15m-700

Results3_TV_15m-700

Según los resultados anteriores de la prueba en el marco temporal de 15 minutos, estos son los valores más relevantes:

  • Beneficio neto: 21 866,08.
  • Reducción relativa del saldo: 16,22 %.
  • Factor de beneficio: 1,19.
  • Beneficio esperado: 42,79.
  • Factor de recuperación: 1,24.
  • Índice de Sharpe: 1,42.
Teniendo en cuenta las cifras relevantes mencionadas anteriormente, los resultados de la prueba en el marco temporal de 5 minutos son:

Results_TV_5m-700

Results2_TV_5m-700

Results3_TV_5m-700

Teniendo en cuenta las cifras relevantes mencionadas anteriormente, los resultados de la prueba en el marco temporal de 15 minutos son los siguientes:

  • Beneficio neto: 87 010,62.
  • Reducción relativa del saldo: 30,24 %.
  • Factor de beneficio: 1,24.
  • Beneficio esperado: 51,67.
  • Factor de recuperación: 2,08.
  • Índice de Sharpe: 1,36.

Ahora podemos ver los mejores resultados para cada métrica:

  • El beneficio neto más alto: 91 520,53, para la estrategia de cruce del nivel cero del DPO en el marco temporal de 15 minutos.
  • La reducción relativa del saldo más baja: 16,22 % para la estrategia de validación de tendencias del DPO en el marco temporal de 15 minutos.
  • El factor de beneficio más alto: 1,24 para la estrategia de validación de tendencias del DPO en el marco temporal de 5 minutos.
  • El mayor beneficio esperado: 51,67 para la estrategia de validación de tendencias del DPO en el marco temporal de 5 minutos.
  • El factor de recuperación más alto: 2,08 para la estrategia de validación de tendencias del DPO en el marco temporal de 5 minutos.
  • El índice de Sharpe más alto: 1,42 para la estrategia de validación de tendencias del DPO en el marco temporal de 15 minutos.

Está claro que el EA optimizado basado en la confirmación mediante la media móvil ofrece, en general, mejores resultados en la mayoría de las métricas. Por eso, le aconsejo que realice más pruebas utilizando diferentes enfoques, como otras herramientas técnicas, distintos marcos temporales y diferentes periodos, para comprobar si puede obtener mejores resultados a partir de esas pruebas.



Conclusión

Al finalizar este artículo, se espera que usted comprenda en qué consiste el indicador técnico «Detrended Price Oscillator» (DPO), qué mide, cómo se calcula y cómo pueden utilizarlo los operadores. Además, se espera que sepa programar su propio indicador personalizado según sus preferencias y, lo más interesante, crear un sistema de trading basado en dos estrategias sencillas:

  • La estrategia de cruce del nivel cero del DPO
  • La estrategia de validación de tendencias del DPO

Se espera que haya comprendido cómo optimizarlas aplicando los distintos enfoques presentados como ejemplos para obtener mejores resultados en sus operaciones. A partir de los métodos que usamos para optimizar los sistemas de trading mencionados, hemos podido determinar cuál es el mejor según la mayoría de los parámetros de las pruebas. Espero que este artículo le haya resultado útil y, si quiere leer más artículos míos —como guías sobre cómo crear sistemas de trading basados en indicadores técnicos populares—, puede acceder a ellos a través de mi página de publicaciones.

A continuación encontrará los archivos de código fuente que adjunto:

Nombre del archivo Descripción
customDPO Es para el indicador DPO personalizado
DPO_Val Corresponde al EA de valores dinámicos del DPO.
DPO_Zero_Crossover Corresponde al EA de la estrategia de cruce del nivel cero del DPO.
DPO_trendValidation Corresponde al EA de la estrategia de validación de tendencias del DPO.

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

Archivos adjuntos |
customDPO.mq5 (2.19 KB)
DPO_Val.mq5 (0.65 KB)
Dan Dumbraveanu
Dan Dumbraveanu | 27 sept 2025 en 18:05
Esto es muy interesante, pero me cuesta un poco relacionar la teoría con el código. Si lo he entendido bien, para calcular la media móvil simple (SMA) que se va a utilizar para la barra actual, hay que retroceder el periodo considerado N/2 + 1 barras y, a continuación, calcular la SMA utilizando las N barras anteriores a partir de ahí. Solo soy un principiante, así que no pretendo entender bien el código del indicador, pero por lo que he podido descifrar, me parece que N solo se utiliza para establecer el nombre, pero no en los cálculos como periodo de la SMA, y que, en su lugar, se utiliza N/2 + 1 (maPeriod en el código) como periodo de la SMA, pero no para realizar ningún desplazamiento. Perdóname si lo he entendido todo mal, pero por favor, indícame en qué me equivoco para poder entenderlo mejor.
Mark Anthony Graham
Mark Anthony Graham | 29 sept 2025 en 04:06

Gracias, señor.

Básicamente, me encanta su indicador.

Me parece eficaz cuando se utiliza en marcos temporales más amplios, por ejemplo: el semanal.

Algoritmo del Bisonte: Bison Algorithm (BIA) Algoritmo del Bisonte: Bison Algorithm (BIA)
Un nuevo método de optimización, el Algoritmo del Bisonte: Bison Algorithm (BIA), utiliza dos estrategias inspiradas en el comportamiento del bisonte para resolver problemas de optimización continua con una única función objetivo. Las características clave del BIA son dos principios fundamentales inspirados en el comportamiento del bisonte: la capacidad de moverse de forma dinámica y una estrategia defensiva.
Explorador de la biblioteca estándar de MQL5 (Parte 1): Introducción con CTrade, CiMA y CiATR Explorador de la biblioteca estándar de MQL5 (Parte 1): Introducción con CTrade, CiMA y CiATR
La biblioteca estándar de MQL5 desempeña un papel fundamental en el desarrollo de algoritmos de trading para MetaTrader 5. En esta serie de artículos, nuestro objetivo es dominar su uso para simplificar la creación de herramientas de trading eficientes para MetaTrader 5. Entre estas herramientas se incluyen asesores expertos personalizados, indicadores y otras utilidades. Hoy vamos a desarrollar un asesor experto de seguimiento de tendencias utilizando las clases CTrade, CiMA y CiATR. Este es un tema especialmente importante para todo el mundo, tanto si usted es principiante como si es un desarrollador con experiencia. Participe en esta discusión para descubrir más información.
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.
Superando las limitaciones del aprendizaje automático (Parte 4): Cómo superar el error irreducible mediante múltiples horizontes de previsión Superando las limitaciones del aprendizaje automático (Parte 4): Cómo superar el error irreducible mediante múltiples horizontes de previsión
El aprendizaje automático suele abordarse desde el punto de vista de la estadística o del álgebra lineal, pero, en este artículo, haremos hincapié en una perspectiva geométrica de las predicciones de los modelos. Así, demostraremos que los modelos no se aproximan realmente a la variable objetivo, sino que la representan en un nuevo sistema de coordenadas, lo que genera una desalineación inherente que da lugar a un error irreducible. El artículo sostiene que las predicciones a varios pasos, en las que se comparan las previsiones del modelo en distintos horizontes temporales, constituyen un enfoque más eficaz que las comparaciones directas con la variable objetivo. Aplicando este método a un modelo de negociación, el artículo demuestra que se consiguen mejoras significativas en la rentabilidad y la precisión sin modificar el modelo subyacente.