English Русский 中文 Deutsch 日本語 Português
Usando indicadores para la optimización RealTime de EAs

Usando indicadores para la optimización RealTime de EAs

MetaTrader 5Probador | 10 diciembre 2018, 07:30
1 447 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Contenido

Introducción

Cada vez que iniciamos un asesor en el gráfico, surge la cuestión de la elección de los parámetros óptimos, es decir, los que den la máxima rentabilidad. Para buscar estos parámetros, se realiza la optimización de la estrategia comercial usando los datos históricos. Pero, como ya sabemos, el mercado está en constante movimiento. Y, con el paso del tiempo, los parámetros seleccionados quedan poco a poco obsoletos.

Entonces, necesitaremos optimizar el asesor de nuevo. Este ciclo es constante. Cada usuario elige el momento de la nueva optimización. Pero, pensemos en lo siguiente: ¿se puede automatizar este proceso? ¿Qué métodos existen para solucionar esta cuestión? Es posible que usted ya haya considerado la gestión programática con el simulador estándar de estrategias, mediante el inicio del terminal con un archivo configurado propio. Queremos proponer un enfoque un tanto distinto, y delegar en el indicador el papel del simulador.

1. Idea

Está claro que un indicador no es en absoluto un simulador de estrategias. ¿Y cómo puede ayudarnos a la hora de optimizar el asesor? Nuestra idea consiste en escribir en el indicador la lógica de funcionamiento del asesor y monitorear la rentabilidad de la ejecución de las transacciones virtuales en tiempo real. Al efectuar la optimización en el simulador de estrategias, se realiza una serie de pruebas con la iteración de los parámetros establecidos. Actuaremos de la misma forma: vamos a iniciar simultáneamente varios ejemplares de un mismo indicador con distintos parámetros, por analogía con las pasadas del simulador de estrategias. En el momento de tomar una decisión, el asesor preguntará a los indicadores iniciados y elegirá para la ejecución las mejores lecturas.

Resulta lícito hacerse la pregunta: ¿para qué inventar la rueda? Vamos a analizar las ventajas y desventajas de esta solución. Sin duda, la principal ventaja de este enfoque es la optimización del asesor en unas condiciones prácticamente idénticas a las del tiempo real. La segunda ventaja es la posibilidad de nombrar las simulaciones realizadas con los ticks reales precisamente de su bróker; pero, por otra parte, la simulación en tiempo real representa una gran desventaja, puesto que es necesario esperar a que se recopilen las estadísticas. También resulta positivo que, al desplazarse temporalmente, el indicador-simulador no recalculará toda la historia, sino solo el tick actual, al tiempo que el simulador de estrategias realizará una pasada de la historia desde el principio. Tal enfoque ofrece una optimización más rápida en el momento necesario. Por consiguiente, podemos realizar la optimización prácticamente en cada barra.

Podemos mencionar entre las desventajas de este enfoque la ausencia de una historia de ticks para la simulación con la historia. Está claro que podemos usar CopyTicks o CopyTicksRange. Pero la carga de la historia de ticks necesita tiempo, y después el recálculo de un gran volumen de información, además de las capacidades de cálculo de la computadora y tiempo. No debemos olvidar que estamos usando indicadores. Y todos los indicadores de un instrumento en MetaTrader 5 funcionan en un mismo flujo. Tras este hecho se oculta una limitación más: un número demasiado elevado de indicadores puede provocar la ralentización del funcionamiento del terminal.

Para minimizar los riesgos derivados de las desventajas que acabamos de enumerar, admitiremos las siguientes concesiones:

  1. Al inicializar el simulador indicador, la historia se calculará según los precios М1 OHLC. En el cálculo de beneficio/pérdidas de las órdenes, primero se comprobará el stop-loss, y después el take-profit según High/Low (dependiendo del tipo de orden).
  2. En relación con el punto 1, las órdenes se abrirán solo en la apertura de la vela.
  3. Para reducir el número total de simuladores-indicadores, usaremos un enfoque meditado con respecto a la elección de los parámetros usados en ellos. Aquí podemos añadir el salto mínimo y el filtrado de parámetros de acuerdo con la lógica del indicador. Por ejemplo, al usar MACD, si el intervalo de los parámetros de la media móvil rápida y lenta se solapan, no se iniciará el simulador-indicador para seleccionar los parámetros donde el periodo de la media móvil lenta es inferior o igual al periodo de la media móvil rápida, puesto que contradice la propia lógica del funcionamiento del indicador. También podemos añadir la divergencia mínima entre periodos, descartando inicialmente las variantes con un gran número de señales falsas.

2. Estrategia comercial

Para poner a prueba el método, vamos a usar una sencilla estrategia basada en 3 indicadores clásicos WPR, RSI y ADX. La señal de compra será el cruce del nivel WPR de sobreventa (nivel -80) de abajo hacia arriba. En este caso, además, se controlará que RSI no se encuentre en la zona de sobrecompra (por encima el nivel 70). Puesto que ambos indicadores son osciladores, su uso se verá justificado en los movimientos latarales (flat). La presencia de flat se comprobará con los indicadores ADX, cuyo valor no deberá superar el nivel 40.

Punto de entrada de compra

Para la venta, la señal será opuesta. El indicador WPR cruza el nivel de sobrecompra -20 de arriba hacia abajo, el valor RSI debe estar por encima de la zona de sobreventa 30. ADX controla la presencia de flat, como sucede con la compra.

Punto de entrada de venta

Como ya hemos dicho anteriormente, realizaremos la entrada en la posición en la apertura de la vela que sigue a la señal. La salida de la posición la realizaremos según un stop-loss o take-profit fijo.

Para controlar las pérdidas, en el mercado siempre habrá no más de una posición.

3. Preparando el simulador-indicador

3.1. Clase de las transacciones virtuales

Tras determinar la estrategia comercial, ha llegado el momento de escribir el indicador de prueba. Lo primero que debemos hacer es preparar las órdenes virtuales cuya ejecución monitorearemos en el indicador. Anteriormente, en el artículo [ 1] ya se describió la clase de la orden virtual. En nuestro trabajo, podremos usar perfectamente este desarrollo con una pequeña adición. El asunto es que la clase anteriormente descrita tiene el método Tick, que comprueba el momento de cierre de la orden según los percios Ask y Bid actuales. Este enfoque es totalmente aplicable al trabajar solo en tiempo real, y no es en absoluto aplicable al realizar la comprobación con datos históricos. Vamos a rehacer un poco la función indicada, añadiendo a sus parámetros el precio y el spread. Después de ejecutar las operaciones, el método retornará el estado de la orden. Como resultado de esta adición, el método adoptará el aspecto siguiente.

bool CDeal::Tick(double price, int spread)
  {
   if(d_ClosePrice>0)
      return true;
//---
   switch(e_Direct)
     {
      case POSITION_TYPE_BUY:
        if(d_SL_Price>0 && d_SL_Price>=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price<=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
             }
          }
        break;
      case POSITION_TYPE_SELL:
        price+=spread*d_Point;
        if(d_SL_Price>0 && d_SL_Price<=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price>=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
             }
          }
        break;
     }
   return IsClosed();
  }

Podrá familiarizarse con el código completo de la clase en los anexos.

3.2. Programando el indicador

En la siguiente etapa, pasaremos al copiado del propio indicador. Puesto que nuestro simulador-indicador ejecutará en cierto modo el papel de asesor, sus parámetros también nos recordarán a los parámetros del experto. Al principio, en los parámetros del indicador estableceremos el periodo de simulación, los niveles de stop-loss y take-profit. Después definiremos los parámetros de los indicadores utilizados. Como conclusión, podremos prever la indicación de la dirección del comercio y el periodo de promedicación de los datos estadísticos. Hablaremos con más detalle sobre el uso de cada parámetro a medida que se vayan usando en el código del indicador.

input int                  HistoryDepth      =  500;           //Depth of history(bars)
input int                  StopLoss          =  200;           //Stop Loss(points)
input int                  TakeProfit        =  600;           //Take Profit(points)
//--- Parámetros del indicador RSI
input int                  RSIPeriod         =  28;            //RSI Period
input double               RSITradeZone      =  30;            //Overbaying/Overselling zone size
//--- Parámetros del indicador WPR
input int                  WPRPeriod         =  7;             //Period WPR
input double               WPRTradeZone      =  30;            //Overbaying/Overselling zone size
//--- Parámetros del indicador ADX
input int                  ADXPeriod         =  11;            //ADX Period
input int                  ADXLevel          =  40;            //Flat Level ADX
//---
input int                  Direction         =  -1;            //Trade direction "-1"-All, "0"-Buy, "1"-Sell
//---
input int                  AveragePeriod     =  10;            //Averaging Period

Para realizar los cálculos y el intercambio de datos con el asesor, crearemos en nuestro indicador 9 búferes de indicador que contendrán la siguiente información:

1. Probabilidad de transacción rentable.

double      Buffer_Probability[];

2. Factor de beneficio en el intervalo simulado.

double      Buffer_ProfitFactor[];

3. Niveles de stop-loss y take-profit. Estos dos búferes se podrían excluir creando una matriz de correspondencia del manejador del indicador y los niveles indicados en el experto, o bien solicitar los parámetros del indicador según su manejador al abrir la transacción. Esta solución nos ha parecido la más sencilla.

double      Buffer_TakeProfit[];
double      Buffer_StopLoss[];

4. Búferes para el cálculo del número total de transacciones realizadas y su rentabilidad durante periodo simulado.

double      Buffer_ProfitCount[];
double      Buffer_DealsCount[];

5. Los dos siguientes búferes son auxiliares: se usan para calcular los valores anteriores y contienen datos análogos solo de la barra actual.

double      Buffer_ProfitCountCurrent[];
double      Buffer_DealsCountCurrent[];

6. Y el último de la lista, pero no el menos importante, es el búfer que transmite al asesor la señal de ejecución de la transacción.

double      Buffer_TradeSignal[];

Excepto los búferes indicados en el bloque de variables globales, declaramos la matriz para guardar las transacciones abiertas, la variable para registrar la hora de ejecución de la última transacción, las variables para guardar los manejadores de los indicadores, así como las matrices para obtener la información desde los indicadores.

CArrayObj   Deals;
datetime    last_deal;
int         wpr_handle,rsi_handle,adx_handle;
double      rsi[],adx[],wpr[];

Al inicio de la función OnInit, efectuamos la inicialización de los indicadores.

int OnInit()
  {
//--- Obteniendo el manejador del indicador RSI
   rsi_handle=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod,PRICE_CLOSE);
   if(rsi_handle==INVALID_HANDLE)
     {
      Print("Test Indicator",": Failed to get RSI handle");
      Print("Handle = ",rsi_handle,"  error = ",GetLastError());
      return(INIT_FAILED);
     }
//--- Obteniendo el manejador del indicador WPR
   wpr_handle=iWPR(Symbol(),PERIOD_CURRENT,WPRPeriod);

   if(wpr_handle==INVALID_HANDLE)
     {
      Print("Test Indicator",": Failed to get WPR handle");
      Print("Handle = ",wpr_handle,"  error = ",GetLastError());
      return(INIT_FAILED);
     }
//--- Obteniendo el manejador del indicador ADX
   adx_handle=iADX(Symbol(),PERIOD_CURRENT,ADXPeriod);
   if(adx_handle==INVALID_HANDLE)
     {
      Print("Test Indicator",": Failed to get ADX handle");
      Print("Handle = ",adx_handle,"  error = ",GetLastError());
      return(INIT_FAILED);
     }

A continuación, conectamos los búferes de indicador con las matrices dinámicas.

//--- indicator buffers mapping
   SetIndexBuffer(0,Buffer_Probability,INDICATOR_CALCULATIONS);
   SetIndexBuffer(1,Buffer_DealsCount,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,Buffer_TradeSignal,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,Buffer_ProfitFactor,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,Buffer_ProfitCount,INDICATOR_CALCULATIONS);
   SetIndexBuffer(5,Buffer_TakeProfit,INDICATOR_CALCULATIONS);
   SetIndexBuffer(6,Buffer_StopLoss,INDICATOR_CALCULATIONS);
   SetIndexBuffer(7,Buffer_DealsCountCurrent,INDICATOR_CALCULATIONS);
   SetIndexBuffer(8,Buffer_ProfitCountCurrent,INDICATOR_CALCULATIONS);

Y asignamos a todas las matrices las propiedades de las series temporales.

   ArraySetAsSeries(Buffer_Probability,true);
   ArraySetAsSeries(Buffer_ProfitFactor,true);
   ArraySetAsSeries(Buffer_TradeSignal,true);
   ArraySetAsSeries(Buffer_DealsCount,true);
   ArraySetAsSeries(Buffer_ProfitCount,true);
   ArraySetAsSeries(Buffer_TakeProfit,true);
   ArraySetAsSeries(Buffer_StopLoss,true);
   ArraySetAsSeries(Buffer_DealsCountCurrent,true);
   ArraySetAsSeries(Buffer_ProfitCountCurrent,true);
//--- 
   ArraySetAsSeries(rsi,true);
   ArraySetAsSeries(wpr,true);
   ArraySetAsSeries(adx,true);

Al finalizar la función, reseteamos la matriz de transacciones y la fecha de la última transacción, y también asignamos un nombre a nuestro indicador.

   Deals.Clear();
   last_deal=0;
//---
   IndicatorSetString(INDICATOR_SHORTNAME,"Test Indicator");
//---
   return(INIT_SUCCEEDED);
  }

La carga de los datos actuales de los indicadores la realizaremos en la función GetIndValue. En la entrada, la función indicada recibirá la profundicad necesaria de la historia de los datos cargados, y en la salida, la función retornará el número de elementos cargados. Los propios datos de los indicadores se guardarán en la matrices declaradas a nivel global.

int GetIndValue(int depth)
  {
   if(CopyBuffer(wpr_handle,MAIN_LINE,0,depth,wpr)<=0 || CopyBuffer(adx_handle,MAIN_LINE,0,depth,adx)<=0 || CopyBuffer(rsi_handle,MAIN_LINE,0,depth,rsi)<=0)
      return -1;
   depth=MathMin(ArraySize(rsi),MathMin(ArraySize(wpr),ArraySize(adx)));
//---
   return depth;
  }

Para comprobar las señales sobre la apertura de transacciones, crearemos las funciones inversas BuySignal y SellSignal. Podrá familiarizarse con detalle con el código de las funciones en los anexos.

La funcionalidad básica, como en cualquier indicador, se concentrará en la función OnCalculate. Las operaciones en esta función se pueden dividir lógicamente en 2 flujos:

  1. Al recalcular más de una barra (primer inicio después de la inicialización o apertura de una nueva barra). En este flujo comprobaremos las señales para la apertura de transacciones en cada barra no recalculada y el procesamiento de las órdenes stop de las transacciones abiertas según los datos históricos del marco temporal М1.
  2. En cada nuevo tick, cuando la nueva barra aún no se ha formado, comprobamos solo la activación de órdenes stop de las posiciones abiertas.

Al inicio de la función comprobamos el número de nuevas barras desde el momento del último inicio de la función. Si se trata del primer inicio de la función tras la inicialización del indicador, establecemos la profundidad del recálculo del indicador en no más de la profundidad de simulación necesaria, y ajustamos los búferes de indicador a su estado original.

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[])
  {
//---
   int total=rates_total-prev_calculated;
   if(prev_calculated<=0)
     {
      total=fmin(total,HistoryDepth);
//---
      ArrayInitialize(Buffer_Probability,0);
      ArrayInitialize(Buffer_ProfitFactor,0);
      ArrayInitialize(Buffer_TradeSignal,0);
      ArrayInitialize(Buffer_DealsCount,0);
      ArrayInitialize(Buffer_ProfitCount,0);
      ArrayInitialize(Buffer_TakeProfit,TakeProfit*_Point);
      ArrayInitialize(Buffer_StopLoss,StopLoss*_Point);
      ArrayInitialize(Buffer_DealsCountCurrent,0);
      ArrayInitialize(Buffer_ProfitCountCurrent,0);
     }

A continuación, vienen las operaciones del primer flujo lógico, cuando se da la apertura de una nueva vela. En primer lugar, cargamos los datos actuales de los indicadores utilizados. Si se da un error de carga de los datos, salimos de la función hasta el siguiente tick, a la espera de que los indicadores sean recalculados.

   if(total>0)
     {
      total=MathMin(GetIndValue(total+2),rates_total);
      if(total<=0)
         return prev_calculated;

A continuación, asignamos el signo de la serie temporal a las matrices de precio entrantes.

      if(!ArraySetAsSeries(open,true) || !ArraySetAsSeries(high,true) || !ArraySetAsSeries(low,true) || !ArraySetAsSeries(close,true)
         || !ArraySetAsSeries(time,true) || !ArraySetAsSeries(spread,true))
         return prev_calculated;

Después, viene el ciclo principal de recálculo de cada barra. Al comienzo del ciclo, inicializamos los búferes de indicador para la barra recalculada.

      for(int i=total-3;i>=0;i--)
        {
         Buffer_TakeProfit[i]=TakeProfit*_Point;
         Buffer_StopLoss[i]=StopLoss*_Point;
         Buffer_DealsCount[i]=Buffer_DealsCountCurrent[i]=0;
         Buffer_ProfitCount[i]=Buffer_ProfitCountCurrent[i]=0;

Después, comprobamos si no hemos abierto una transacción más en la barra calculada. Si no es así, comprobamos las señales de los indicadores en la entrada a la posición llamando las funciones creadas anteriormente. Si hay una señal de apertura de posición, creamos una transacción virtual y anotamos la señal correspondiente en el búfer de señal.

         if(last_deal<time[i])
           {
            if(BuySignal(i))
              {
               double open_price=open[i]+spread[i]*_Point;
               double sl=open_price-StopLoss*_Point;
               double tp=open_price+TakeProfit*_Point;
               CDeal *temp=new CDeal(_Symbol,rates_total-i,POSITION_TYPE_BUY,time[i],open_price,sl,tp);
               if(temp!=NULL)
                  Deals.Add(temp);
               Buffer_TradeSignal[i]=1;
              }
            else /*BuySignal*/
            if(SellSignal(i))
              {
               double open_price=open[i];
               double sl=open_price+StopLoss*_Point;
               double tp=open_price-TakeProfit*_Point;
               CDeal *temp=new CDeal(_Symbol,rates_total-i,POSITION_TYPE_SELL,time[i],open_price,sl,tp);
               if(temp!=NULL)
                  Deals.Add(temp);
               Buffer_TradeSignal[i]=-1;
              }
            else /*SellSignal*/
               Buffer_TradeSignal[i]=0;
           }

A continuación, se realiza el trabajo con las posiciones abiertas. Y lo primero que hacemos es comprobar el marco temporal actual. Si el indiador funciona en el marco M1, la comprobación de la activación de las órdenes stop de las posiciones abiertas la realizaremos según los datos de las series temporales obtenidos en los parámetros de la función OnCalculate. En el caso contrario, tendremos que cargar los datos del marco temporal de minutos.

         if(Deals.Total()>0)
           {
            if(PeriodSeconds()!=60)
              {
               MqlRates rates[];
               int rat=CopyRates(_Symbol,PERIOD_M1,time[i],(i>0 ? time[i-1] : TimeCurrent()),rates);

Después de caragar las cotizaciones, organizamos el ciclo de comprobación de órdenes stop de las transacciones abiertas en cada barra de minutos. La suma de las transacciones cerradas y las transacciones rentables la sumamos a los búferes de indicador correspondientes para la barra a recalcular. El procesamiento directo de la matriz tiene lugar en la función CheckDeals, en cuyos parámetros transmitimos los datos de la vela de minutos comprobada. El algoritmo de funcionamiento de la función se comenta más abajo.

               int closed=0, profit=0;
               for(int r=0;(r<rat && Deals.Total()>0);r++)
                 {
                  CheckDeals(rates[r].open,rates[r].high,rates[r].low,rates[r].close,rates[r].spread,rates[r].time,closed,profit);
                  if(closed>0)
                    {
                     Buffer_DealsCountCurrent[i]+=closed;
                     Buffer_ProfitCountCurrent[i]+=profit;
                    }
                 }

A continuación, siguen los bloques alternativos análogos, que comprueban las transacciones según los datos del marco temporal actual en el caso de error de carga de las cotizaciones de minuto o el funcionamiento del indicador en el marco temporal М1.

               if(rat<0)
                 {
                  CheckDeals(open[i],high[i],low[i],close[i],spread[i],time[i],closed,profit);
                  Buffer_DealsCountCurrent[i]+=closed;
                  Buffer_ProfitCountCurrent[i]+=profit;
                 }
              }
            else /* PeriodSeconds()!=60 */
              {
               int closed=0, profit=0;
               CheckDeals(open[i],high[i],low[i],close[i],spread[i],time[i],closed,profit);
               Buffer_DealsCountCurrent[i]+=closed;
               Buffer_ProfitCountCurrent[i]+=profit;
              }
           } /* Deals.Total()>0 */

Al final del trabajo realizado, calcularemos las estadísticas del funcionamiento de nuetra estrategia. Calculamos el número de transacciones abiertas en el periodo simulado y cuántas de ellas se han cerrado con beneficios.

         Buffer_DealsCount[i+1]=NormalizeDouble(Buffer_DealsCount[i+2]+Buffer_DealsCountCurrent[i+1]-((i+HistoryDepth+1)<rates_total ? Buffer_DealsCountCurrent[i+HistoryDepth+1] : 0),0);
         Buffer_ProfitCount[i+1]=NormalizeDouble(Buffer_ProfitCount[i+2]+Buffer_ProfitCountCurrent[i+1]-((i+HistoryDepth+1)<rates_total ? Buffer_ProfitCountCurrent[i+HistoryDepth+1] : 0),0);
         Buffer_DealsCount[i]=NormalizeDouble(Buffer_DealsCount[i+1]+Buffer_DealsCountCurrent[i]-((i+HistoryDepth)<rates_total ? Buffer_DealsCountCurrent[i+HistoryDepth] : 0),0);
         Buffer_ProfitCount[i]=NormalizeDouble(Buffer_ProfitCount[i+1]+Buffer_ProfitCountCurrent[i]-((i+HistoryDepth)<rates_total ? Buffer_ProfitCountCurrent[i+HistoryDepth] : 0),0);

Si hay transacciones que se han activado, calculamos la probabilidad de obtener beneficios según la transacción y el factor de beneficio de la estrategia en el periodo simulado. Para prevenir cambios bruscos en la probabilidad de obtener beneficio, el índice será suavizado según la fórmula de media exponencial con uso del periodo de promediación indicado en los parámetros del indicador.

         if(Buffer_DealsCount[i]>0)
           {
            double pr=2.0/(AveragePeriod-1.0);
            Buffer_Probability[i]=((i+1)<rates_total && Buffer_Probability[i+1]>0 && Buffer_DealsCount[i+1]>=AveragePeriod ? Buffer_ProfitCount[i]/Buffer_DealsCount[i]*100*pr+Buffer_Probability[i+1]*(1-pr) : Buffer_ProfitCount[i]/Buffer_DealsCount[i]*100);
            if(Buffer_DealsCount[i]>Buffer_ProfitCount[i])
              {
               double temp=(Buffer_ProfitCount[i]*TakeProfit)/(StopLoss*(Buffer_DealsCount[i]-Buffer_ProfitCount[i]));
               Buffer_ProfitFactor[i]=((i+1)<rates_total && Buffer_ProfitFactor[i+1]>0 ? temp*pr+Buffer_ProfitFactor[i+1]*(1-pr) : temp);
              }
            else
               Buffer_ProfitFactor[i]=TakeProfit*Buffer_ProfitCount[i];
           }
        }
     }

El flujo de procesamiento de cada tick contiene una lógica análoga, por eso no resulta útil mostrar en el artículo su descripción completa. Podrá familiarizarse con el código completo de todas las funciones en los anexos.

Ya hemos escrito antes que la comprobación de la activación de las órdenes stop de las transacciones activas se realiza con la función CheckDeals. Vamos a analizar su algoritmo de funcionamiento. En los parámetros, la función recibe las cotizaciones de la barra analizada y dos enlaces a las variables para retornar el número de transacciones cerradas y rentables.

Al inicio de la función, reseteamos las variables retornadas y declaramos la variable lógica resultante.

bool CheckDeals(double open,double high,double low,double close,int spread,datetime time,int &closed, int &profit)
  {
   closed=0;
   profit=0;
   bool result=true;

A continuación en la función se organiza un ciclo de iteración de todas las transacciones en la matriz. En el ciclo se obtienen secuencialmente desde la matriz los punteros a los objetos de las transacciones. Si el puntero al objeto es erróneo, eliminamos esta transacción de la matriz y pasamos a la siguiente transacción. Si encontramos un error de ejecución de las operaciones, asignamos a la variable resultante el valor false.

   for(int i=0;i<Deals.Total();i++)
     {
      CDeal *deal=Deals.At(i);
      if(CheckPointer(deal)==POINTER_INVALID)
        {
         if(Deals.Delete(i))
            i--;
         else
            result=false;
         continue;
        }

En el siguiente paso, comprobamos si hay abierta una transacción en el momento de apertura de la vela. Si no la hay, pasamos a la siguiente transacción.

      if(deal.GetTime()>time)
         continue;

Y, finalmente, comprobamos de forma consecutiva la activación de las órdenes stop de la transacción para el precio de apertura, el máximo, el mínimo y el de cierre, llamando el método Tick de la transacción comprobada para cada precio. El algoritmo de funcionamiento de este método se describe más arriba, al inicio del presente apartado. En este caso, además, debemos tener en cuenta que para las transacciones de compra y venta, la secuencia de la comprobación es diferente. En primer lugar, se comprueba la activación del stop-loss, y después la del take-profit. Este enfoque puede reducir en parte la efectividad del comercio, pero permitirá reducir también las pérdidas en el comercio futuro. Al activarse alguna de las órdenes stop, aumenta el número de transacciones cerradas, y en el caso de obtener beneficios, aumentamos también el número de transacciones rentables. Después del cierre, la transacción se elimina de la matriz para prevenir un nuevo recálculo.

      if(deal.Tick(open,spread))
        {
         closed++;
         if(deal.GetProfit()>0)
            profit++;
         if(Deals.Delete(i))
            i--;
         if(CheckPointer(deal)!=POINTER_INVALID)
            delete deal;
         continue;
        }
      switch(deal.Type())
        {
         case POSITION_TYPE_BUY:
            if(deal.Tick(low,spread))
              {
               closed++;
               if(deal.GetProfit()>0)
                  profit++;
               if(Deals.Delete(i))
                  i--;
               if(CheckPointer(deal)!=POINTER_INVALID)
                  delete deal;
               continue;
              }
            if(deal.Tick(high,spread))
              {
               closed++;
               if(deal.GetProfit()>0)
                  profit++;
               if(Deals.Delete(i))
                  i--;
               if(CheckPointer(deal)!=POINTER_INVALID)
                  delete deal;
               continue;
              }
           break;
         case POSITION_TYPE_SELL:
            if(deal.Tick(high,spread))
              {
               closed++;
               if(deal.GetProfit()>0)
                  profit++;
               if(Deals.Delete(i))
                  i--;
               if(CheckPointer(deal)!=POINTER_INVALID)
                  delete deal;
               continue;
              }
            if(deal.Tick(low,spread))
              {
               closed++;
               if(deal.GetProfit()>0)
                  profit++;
               if(Deals.Delete(i))
                  i--;
               if(CheckPointer(deal)!=POINTER_INVALID)
                  delete deal;
               continue;
              }
           break;
        }
     }
//---
   return result;
  }

Podrá familiarizarse con el código del indicador y de todas sus funciones en los anexos.

4. Creando el asesor

Después de crear el simulador-indicador, ha llegado el momento de proceder directamente a la creación de nuestro experto. En los parámetros de nuestro asesor, indicadremos una serie de variables estáticas (comunes para todas las pasadas) y, por analogía con el simulador de estrategias, indicaremos los valores iniciales y finales de los parámetros a cambiar y la magnitud del salto de cambio de los valores. Asimismo, en los parámetros del asesor, indicaremos los criterios de selección de las señales de entrada en el mercado: la probabilidad mínima de obtener beneficio y el factor de beneficio mínimo en el periodo simulado. Además, para respetar la objetividad de los datos estadísticos obtenidos, indicaremos la cantidad mínima necesaria de transacciones en el periodo simulado.

input double               Lot                     =  0.01;
input int                  HistoryDepth            =  500;           //Depth of history(bars)
//--- Parámetros del indicador RSI
input int                  RSIPeriod_Start         =  5;             //RSI Period
input int                  RSIPeriod_Stop          =  30;            //RSI Period
input int                  RSIPeriod_Step          =  5;             //RSI Period
//---
input double               RSITradeZone_Start      =  30;            //Overbaying/Overselling zone size Start
input double               RSITradeZone_Stop       =  30;            //Overbaying/Overselling zone size Stop
input double               RSITradeZone_Step       =  5;             //Overbaying/Overselling zone size Step
//--- Parámetros del indicador WPR
input int                  WPRPeriod_Start         =  5;             //Period WPR Start
input int                  WPRPeriod_Stop          =  30;            //Period WPR Stop
input int                  WPRPeriod_Step          =  5;             //Period WPR Step
//---
input double               WPRTradeZone_Start      =  20;            //Overbaying/Overselling zone size Start
input double               WPRTradeZone_Stop       =  20;            //Overbaying/Overselling zone size Stop
input double               WPRTradeZone_Step       =  5;             //Overbaying/Overselling zone size Step
//--- Parámetros del indicador ADX
input int                  ADXPeriod_Start         =  5;             //ADX Period Start
input int                  ADXPeriod_Stop          =  30;            //ADX Period Stop
input int                  ADXPeriod_Step          =  5;             //ADX Period Step
//---
input int                  ADXTradeZone_Start      =  40;            //Flat Level ADX Start
input int                  ADXTradeZone_Stop       =  40;            //Flat Level ADX Stop
input int                  ADXTradeZone_Step       =  10;            //Flat Level ADX Step
//--- Deals Settings
input int                  TakeProfit_Start        =  600;           //TakeProfit Start
input int                  TakeProfit_Stop         =  600;           //TakeProfit Stop
input int                  TakeProfit_Step         =  100;           //TakeProfit Step
//---
input int                  StopLoss_Start          =  200;           //StopLoss Start
input int                  StopLoss_Stop           =  200;           //StopLoss Stop
input int                  StopLoss_Step           =  100;           //StopLoss Step
//---
input double               MinProbability          =  60.0;          //Minimal Probability
input double               MinProfitFactor         =  1.6;           //Minimal Profitfactor
input int                  MinOrders               =  10;            //Minimal number of deals in history

En las variables globales, declaramos la matriz para guardar los manejadores de los simuladores-indicadores y el ejemplar de la clase de las transacciones comerciales.

CArrayInt   ar_Handles;
CTrade      Trade;

En la función OnInit de nuestro experto, organizamos una serie de ciclos incorporados para iterar todas las variantes de los parámetros simulados, y añadimos aparte una simulación de transacciones de compra y venta. Este enfoque nos permitirá considerar la influencia de las tendencias globales, no monitoreadas por la estrategia simulada. Dentro de los ciclos, incializaremos los simuladores-indicadores. Si se da un error de carga del indicador, salimos de la función con el resultado INIT_FAILED. En el caso de cargar el indicador con éxito, añadiremos su manejador a la matriz.

int OnInit()
  {
//---
   for(int rsi=RSIPeriod_Start;rsi<=RSIPeriod_Stop;rsi+=RSIPeriod_Step)
      for(double rsi_tz=RSITradeZone_Start;rsi_tz<=RSITradeZone_Stop;rsi_tz+=RSITradeZone_Step)
         for(int wpr=WPRPeriod_Start;wpr<=WPRPeriod_Stop;wpr+=WPRPeriod_Step)
            for(double wpr_tz=WPRTradeZone_Start;wpr_tz<=WPRTradeZone_Stop;wpr_tz+=WPRTradeZone_Step)
               for(int adx=ADXPeriod_Start;adx<=ADXPeriod_Stop;adx+=ADXPeriod_Step)
                  for(double adx_tz=ADXTradeZone_Start;adx_tz<=ADXTradeZone_Stop;adx_tz+=ADXTradeZone_Step)
                     for(int tp=TakeProfit_Start;tp<=TakeProfit_Stop;tp+=TakeProfit_Step)
                        for(int sl=StopLoss_Start;sl<=StopLoss_Stop;sl+=StopLoss_Step)
                          for(int dir=0;dir<2;dir++)
                             {
                              int handle=iCustom(_Symbol,PERIOD_CURRENT,"::Indicators\\TestIndicator\\TestIndicator.ex5",HistoryDepth,
                                                                                                                        sl,
                                                                                                                        tp,
                                                                                                                        rsi,
                                                                                                                        rsi_tz,
                                                                                                                        wpr,
                                                                                                                        wpr_tz,
                                                                                                                        adx, 
                                                                                                                        adx_tz,
                                                                                                                        dir);
                              if(handle==INVALID_HANDLE)
                                 return INIT_FAILED;
                              ar_Handles.Add(handle);
                             }

Después de iniciar con éxito todos los simuladores-indicadores, incializamos la clase de transacciones comerciales y finalizamos la ejecución de la función.

   Trade.SetAsyncMode(false);
   if(!Trade.SetTypeFillingBySymbol(_Symbol))
      return INIT_FAILED;
   Trade.SetMarginMode();
//---
   return(INIT_SUCCEEDED);
  }

El fliltrado de las señales de entrada y las operaciones comerciales se realiza en la función OnTick. Puesto que antes ya hemos decidido que vamos a abrir las posiciones solo en la apertura de una barra, comprobamos también al inicio de la función la aparición de este evento.

void OnTick()
  {
//---
   static datetime last_bar=0;
   datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(cur_bar==last_bar)
      return;

Nuestra segunda limitación será la presencia de más de una transacción abierta en un cierto momento. Por eso, al darse una posición abierta, interrumpimos la ejecución de la función.

   if(PositionSelect(_Symbol))
     {
      last_bar=cur_bar;
      return;
     }

Después de comprobar los puntos de control, pasamos al ciclo principal de iteración de todos los indicadores, buscando señales. Al inicio del ciclo, intentamos cargar el búfer de señal del indicador. Si el indicador aún no ha sido calculado o no hay señal de ejecución de una transacción comercial, pasamos al siguiente indicador.

   int signal=0;
   double probability=0;
   double profit_factor=0;
   double tp=0,sl=0;
   bool ind_caclulated=false;
   double temp[];
   for(int i=0;i<ar_Handles.Total();i++)
     {
      if(CopyBuffer(ar_Handles.At(i),2,1,1,temp)<=0)
         continue;
      ind_caclulated=true;
      if(temp[0]==0)
         continue;

El siguiente paso es comprobar si la señal recibida no contradice la señal recibida anteriormente de otros indicadores. La presencia de señales contradictorias aumenta la probabilidad de error, por eso salimos del ciclo hasta que comience a formarse la siguiente vela.

      if(signal!=0 && temp[0]!=signal)
        {
         last_bar=cur_bar;
         return;
        }
      signal=(int)temp[0];

A continuación, comprobamos la presencia de la cantidad mínima de transacciones en el periodo simulado. Si la muestra resulta insuficuente, pasamos al siguiente indicador.

      if(CopyBuffer(ar_Handles.At(i),1,1,1,temp)<=0 || temp[0]<MinOrders)
         continue;

A continuación, se comprueba de forma análoga si es suficiente la probabilidad de obtención de una transacción rentable.

      if(CopyBuffer(ar_Handles.At(i),0,1,1,temp)<=0 || temp[0]<MathMax(probability,MinProbability))
         continue;

Si la divergencia de la probabilidad de obtención de una transacción rentable según el indicador analizado anteriormente es inferior a un 1 por ciento, de las dos pasadas se elegirá la mejor según su factor de beneficio y la proporción de beneficio con respecto al riesgo. Para el trabajo posterior, se guardan los datos de la mejor pasada.

      if(MathAbs(temp[0]-probability)<=1)
        {
         double ind_probability=temp[0];
//---
         if(CopyBuffer(ar_Handles.At(i),3,1,1,temp)<=0 || temp[0]<MathMax(profit_factor,MinProfitFactor))
            continue;
         double ind_profit_factor=temp[0];
         if(CopyBuffer(ar_Handles.At(i),5,1,1,temp)<=0)
            continue;
         double ind_tp=temp[0];
         if(CopyBuffer(ar_Handles.At(i),6,1,1,temp)<=0)
            continue;
         double ind_sl=temp[0];
         if(MathAbs(ind_profit_factor-profit_factor)<=0.01)
           {
            if(sl<=0 || tp/sl>=ind_tp/ind_sl)
               continue;
           }
//---
         probability=ind_probability;
         profit_factor=ind_profit_factor;
         tp=ind_tp;
         sl=ind_sl;
        }

Si la probabilidad de obtener una transacción rentable es claramente mayor, se comprueba el cumplimiento de los requisitos de la pasada según el factor de beneficio. Si todos los requisitos han sido respetados, la información se guarda para el trabajo posterior.

      else /* MathAbs(temp[0]-probability)<=1 */
        {
         double ind_probability=temp[0];
//---
         if(CopyBuffer(ar_Handles.At(i),3,1,1,temp)<=0 || temp[0]<MinProfitFactor)
            continue;
         double ind_profit_factor=temp[0];
         if(CopyBuffer(ar_Handles.At(i),5,1,1,temp)<=0)
            continue;
         double ind_tp=temp[0];
         if(CopyBuffer(ar_Handles.At(i),6,1,1,temp)<=0)
            continue;
         double ind_sl=temp[0];
         probability=ind_probability;
         profit_factor=ind_profit_factor;
         tp=ind_tp;
         sl=ind_sl;
        }
     }

Si después de comprobar todos los simuladores-indicadores, ni uno solo de ellos ha sido recalculado, salimos de la función hasta el siguiente tick, a la espera de que los indicadores sean recalculados.

   if(!ind_caclulated)
      return;

Después de comprobar con éxito los indicadores y si no hay señal activa para ejecutar transacciones comerciales, salimos de la función hasta que se forme una nueva barra.

   last_bar=cur_bar;
//---
   if(signal==0 || probability==0 || profit_factor==0 || tp<=0 || sl<=0)
      return;

Al final de la función, si hay una señal de apertura de posición, enviamos la orden de acuerdo con la mejor pasada.

   if(signal==1)
     {
      double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      tp+=price;
      sl=price-sl;
      Trade.Buy(Lot,_Symbol,price,sl,tp,"Real Time Optimizator");
     }
   else
      if(signal==-1)
        {
         double price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
         tp=price-tp;
         sl+=price;
         Trade.Sell(Lot,_Symbol,price,sl,tp,"Real Time Optimizator");
        }
  }

Podrá familiarizarse con el código completo del experto en los anexos.

5. Simulando el enfoque

Para mostrar el funcionamiento del método, vamos a realizar la simulación del asesor construido con una optimización paralela del experto clásico con simulación en tiempo real (forward test) al usar intervalos análogos de los parámetros a cambiar, y guardaremos el intervalo temporal total de la simulación. Para mantener la igualdad de condiciones, se ha construido un asesor que inicia solo un simulador-indicador y que solo abre una transacción según todas sus señales sin realizar filtrado de acuerdo con los datos estadísticos. La estructura de su construcción es análoga a la del asesor construido más arriba, con la excepción del bloque de filtrado de la consistencia de la señal. Podrá familiarizarse con el código completo del asesor en los anexos (ClassicExpert.mq5).

La simulación de los asesores se ha realizado en el marco temporal Н1 en 7 meses del año 2018. Para la simulación en tiempo real del asesor clásico, se ha dejado un 1/3 del periodo simulado.

Parámetros de la simulación

Como parámetros par la optimización se han tomado los periodos de cálculo de los indicadores. Para todos los indicadores se ha tomado un intervalo de valores de 5 a 30 con un salto de 5.

Parámetros de la simulación

Los resultados de la optimización realizada han mostrado la inconsistencia de la estrategia propuesta. Los valores de los parámetros que daban un pequeño beneficio en la optimización, han mostrado pérdidas en la simulación en tiempo real. En esta caso, además, ni una sola de las pasadas ha mostrado beneficios en el periodo analizado.

Resultados de la optimizacón

Los resultados del análisis gráfico de las optimizaciones realizadas y la simulación en tiempo real han mostrado un cambio en la estructura del movimiento del precio, como resultado del cual ha tenido lugar un desplazamiento de la zona de rentabilidad según el periodo de WPR.

Gráfico de optimización de WPR Gráfico de la optimización en tiempo real de WPR

Para realizar la simulación del experto construido según el método propuesto, hemos establecido parámetros de simulación análogos con guardado del periodo analizado. Para filtrar las señales de apertura de posición, hemos indicado una probabilidad mínima de transacción rentable de un 60%, y un factor mínimo de beneficio en el periodo de simulación igual a 2. Se ha indicado una profundidad de simulación de 500 velas.

Simulación del método propuesto

Como resultado de la simulación, el asesor ha mostrado beneficio en el periodo analizado con un factor de beneficio real de 1.66. Conviene destacar que, al realizar esta simulación en el modo visual, el agente de simulación ha ocupado 1250 Mb de memoria operativa.

Conclusión

En este artículo se ha propuesto una tecnología propia de construcción de asesores con optimización en tiempo real. La simulación ha demostrado un posible uso de este enfoque para el comercio real. El experto creado según el método propuesto ha mostrado la posibilidad de obtener beneficio en el periodo de rentabilidad de la estrategia, así como la posibilidad de detener la estrategia en su periodo de pérdidas. Al mismo tiempo, sigue siendo actual la exigencia del método propuesto con respecto a las características técnicas de la computadora usada. La velocidad del procesador de la computadora deberá ser capaz de realizar el recálculo de todos los indicadores cargados, y el volumen de la memoria operativa deberá contener todos los indicadores usados.

Enlaces

  1. Creamos una nueva estrategia comercial usando una tecnología de colocación de entradas a los indicadores

Programas usados en el artículo:

#
Nombre
Tipo
Descripción
1 Deal.mqh Biblioteca de clase Clase de las transacciones virtuales
2 TestIndicator.mq5 Indicador Simulador-indicador
3 RealTimeOptimization.mq5 Asesor Asesor construido según el método propuesto
4 ClassicExpert.mq5 Asesor Asesor construido según el método clásico para realizar la optimización comparada

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/5061

Archivos adjuntos |
MQL5.zip (360.42 KB)
Modelo de continuación de movimiento - búsqueda en el gráfico y estadísticas de ejecución Modelo de continuación de movimiento - búsqueda en el gráfico y estadísticas de ejecución
En este artículo vamos a describir la definición programática de uno de los modelos de continuación del movimiento. La base del trabajo viene constituida por dos ondas: la principal y la de corrección. Como extremos se usarán fractales, además de los llamados fractales potenciales, los extremos que no se han formado aún como fractales.
Rayos Elder (Bulls Power y Bears Power) Rayos Elder (Bulls Power y Bears Power)
Sistema comercial Rayos Elder basado en los indicadores Bulls Power, Bears Power y Moving Average (EMA — promediación exponencial). Este sistema fue descrito por Alexander Elder en su libro "Vivir del Trading" (Trading for a living).
Recetas MQL5 – Obteniendo las propiedades de una posición de cobertura abierta Recetas MQL5 – Obteniendo las propiedades de una posición de cobertura abierta
La plataforma MetaTrader 5 no es solo una plataforma multimercado, sino que también permite usar diferentes sistemas de registro de posiciones. Estas posibilidades amplian considerablemente el instrumental para la implementación y formalización de las ideas comerciales. En el artículo, vamos a hablar sobre cómo procesar y considerar las propiedades de las posiciones al llevar su registro de forma independiente ("cobertura"). Así, en el artículo proponemos una clase derivada, mostrando a continuación ejemplos de procesamiento y obtención de las propiedades de la posición de cobertura.
Combinando una estrategia de tendencia y una de flat Combinando una estrategia de tendencia y una de flat
Existen diferenets estrategias comerciales. Unas buscan la dirección del movimiento y comercian según la tendencia. Otras definen los intervalos de las oscilaciones de precio y comercian dentro de estos corredores. Así que nos surge la pregunta, ¿podemos combinar los dos enfoques para aumentar la rentabilidad de nuestro comercio?