Modelando series temporales con ayuda de símbolos personalizados según las leyes de distribución establecidas

Aleksey Zinovik | 26 noviembre, 2018

Contenido

Introducción

El terminal comercial MetaTrader 5 permite crear y usar símbolos personalizados. El tráder tiene a posibilidad de simular sus propias parejas de divisas y otros instrumentos financieros. En el artículo se ofrecen métodos de creación y eliminación de símbolos personalizados, generación de ticks y barras según las leyes de distribución.

Asimismo, se proponen métodos de formación de tendencia y diferentes patrones gráficos. Para trabajar con símbolos personalizados, se ofreceremos scripts preparados con ajustes mínimos, que permitirán a los tráders sin habilidades de programación en el lenguaje MQL5 usar todo el potencial de los símbolos personalizados.

Creación y eliminación de símbolos personalizados

En el artículo se muestra un método de creación de símbolos personalizados en la ventana "Símbolos" del terminal MetaTrader 5, basado en símbolos ya existentes. 

Asimismo, proponemos automatizar este proceso con la ayuda de un sencillo script con ajustes mínimos.

El script tiene 4 parámetros:

  • el nombre del símbolo personalizado,
  • el nombre breve de la pareja de divisas o instrumento financiero,
  • el nombre completo de la pareja de divisas o instrumento financiero,
  • el nombre breve de la divisa básica o el instrumento financiero, si el símbolo se crea mediante un símbolo básico,
Vamos a mostrar el código del script (el script se adjunta al artículo en el archivo CreateSymbol.mq5):

//+------------------------------------------------------------------+
//|                                                 CreateSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
input string CurrencyName="UCR";
input string CurrencyFullName="UserCurrency";
input string BaseName="EURUSD";
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
//creando el símbolo
   if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("¡El símbolo ",SName," ya existe!");
      else
         Print("Error al crear el símbolo. Código de error: ",GetLastError());
     }
   else
     {
      if(BaseName=="")//creamos uno nuevo
        {
         //propiedad del tipo String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //divisa básica
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //divisa del beneficio
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&&                         //divisa del margen
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&&                      //descripción de línea del símbolo (nombre completo)
            (SetProperty(SName,SYMBOL_BASIS,"","")) &&                                     //nombre del activo básico para el instrumento derivado
            (SetProperty(SName,SYMBOL_FORMULA,"","")) &&                                   //fórmula para la construcción del precio del instrumento personalizado
            (SetProperty(SName,SYMBOL_ISIN,"","")) &&                                      //nombre del símbolo comercial en el sistema ISIN
            (SetProperty(SName,SYMBOL_PAGE,"","")) &&                                      //dirección de la página de internet con la información del símbolo
            //propiedad del tipo Integer
            (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) &&             //construcción de gráficos al precio Bid
            (SetProperty(SName,SYMBOL_SPREAD,3,"")) &&                                     //spread
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) &&                            //spread flotante
            (SetProperty(SName,SYMBOL_DIGITS,5,"")) &&                                     //precisión
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) &&                           //tamaño de la profundidad de mercado
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&&                        //color de fondo con el que se destaca el símbolo en la Observación de mercado
            (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&&             //tipo de ejecución de las órdenes: acceso completo
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&&  //modo de finalización de las órdenes: ejecución inmediata
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,SYMBOL_ORDERS_GTC,""))&&              //plazo de expiración  del StopLoss y el TakeProfit de las órdenes: activos hasta cancelación
            (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&&               //tipo de ejecución de las órdenes: todo o nada
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&&         //modo de expiración de las órdenes: tiempo ilimitado hasta cancelación explícita
            (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) &&                               //tipos de órdenes: todos los tipos de órdenes
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&&        //método de cálculo del coste del contrato
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&&                   //modo de cálculo del margen cubierto por el lado mayor
            (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&&             //modelo de cálculo del swap: cálculo del swap en puntos
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) &&                 //día de la semana para el ingreso del swap triple
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) &&                                //tipo de opción
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) &&                               //derecho de opción
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) &&                          //distancia mínima en puntos con respecto al precio actual para colocar las órdenes Stop
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) &&                         //distancia de congelación de operaciones comerciales (en puntos)
            (SetProperty(SName,SYMBOL_START_TIME,0,"")) &&                                 //fecha de comienzo de las transacciones del instrumento (normalmente se usa para los futuros)
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) &&                            //fecha de finalización de las transacciones del instrumento (normalmente se usa para los futuros)
            //propiedad del tipo Double
            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) &&                              //precio de ejecución de la opción
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) &&                    //valor mínimo permitido del precio para la sesión 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) &&                    //valor máximo permitido del precio para la sesión
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) &&                   //precio de suministro para la sesión actual
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) &&                     //ingreso de cupón acumulado (para las obligaciones)
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) &&                           //coste nominal (para las obligaciones)
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) &&                       //coeficiente de liquidez (se usa para los instrumentos collateral)
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) &&                      //cambio mínimo del precio
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) &&                           //precio del tick
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) &&                   //tamaño del contrato comercial
            (SetProperty(SName,SYMBOL_POINT,0.00001,"")) &&                                //valor de un punto
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) &&                              //volumen mínimo para la realización de una transacción
            (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) &&                            //volumen máximo para la realización de una transacción
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) &&                             //salto mínimo de cambio de volumen para la realización de una transacción
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) &&                               //volumen conjunto máximo permitido de la posición abierta y las órdenes pendientes en una  misma dirección (compra o venta) para el símbolo dado 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) &&                             //margen inicial
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) &&                         //margen de mantenimiento 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) &&                         //tamaño del contrato o margen para un lote de posiciones en direcciones distintas de un mismo símbolo 
            (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) &&                               //valor del swap en la compra
            (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,"")))                                  //valor del swap en la venta
            Print("El símbolo ",SName," se ha creado con éxito");
         else
            Print("Error al establecer las propiedades del símbolo. Código de error: ",GetLastError());
        }
      else//creamos usando el básico
        {
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && 
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && 
            (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && 

            (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && 

            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName)))
            Print("El símbolo ",SName," se ha creado con éxito");
         else
            Print("Error al establecer las propiedades del símbolo. Código de error: ",GetLastError());
        }
      if(SymbolSelect(SName,true))
         Print("El símbolo ",SName," ha sido elegido en Market Watch");
      else
         Print("Error al elegir el símbolo en Market Watch. Código de error: ",GetLastError());
     }
  }
//función de establecimiento de propiedades del símbolo
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetString(SymName,SProp,PropValue))
         return true;
      else
         Print("Error al establecer la propiedad del símbolo: ",SProp," .Código de error: ",GetLastError());
     }
   else
     {
      string SValue=SymbolInfoString(BaseSymName,SProp);
      if(CustomSymbolSetString(SymName,SProp,SValue))
         return true;
      else
         Print("Error al establecer la propiedad del  símbolo: ",SProp," .Código de error: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetInteger(SymName,IProp,PropValue))
         return true;
      else
         Print("Error al establecer la propiedad del símbolo: ",IProp," .Código de error: ",GetLastError());
     }
   else
     {
      long IValue=SymbolInfoInteger(BaseSymName,IProp);
      if(CustomSymbolSetInteger(SymName,IProp,IValue))
         return true;
      else
         Print("Error al establecer la propiedad del símbolo: ",IProp," .Código de error: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetDouble(SymName,DProp,PropValue))
         return true;
      else
         Print("Error al establecer la propiedad del símbolo: ",DProp," .Código de error: ",GetLastError());
     }
   else
     {
      double DValue=SymbolInfoDouble(BaseSymName,DProp);
      if(CustomSymbolSetDouble(SymName,DProp,DValue))
         return true;
      else
         Print("Error al establecer la propiedad del símbolo: ",DProp," .Código de error: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+

Veamos el código del script con mayor detalle. Primero se intenta crear el símbolo con la ayuda de la función CustomSymbolCreate:

if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("¡El símbolo ",SName," ya existe!");
      else
         Print("Error al crear el símbolo. Código de error: ",GetLastError());
     }

El símbolo se crea en la carpeta Custom/Forex. Si usted crea su propia subcarpeta (grupo personalizado) en la carpeta Custom, indique su nombre en el segundo parámetro de la función CustomSymbolCreate. 

A continuación, se establecen las propiedades del símbolo creado. Si el parámetro BaseName no ha sido establecido, se definen los parámetros del símbolo que hayan sido establecidos por el usuario. Para el ejemplo, se indican las propiedades de la pareja de divisas EURUSD:

    //propiedad del tipo String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //divisa básica
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //divisa del beneficio
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&&                         //divisa del margen
...

Para mayor comodidad, las propiedades están divididas en grupos, primero se indican las propiedades del tipo String, después Integer, y después Double. En el caso de indicar con éxito las propiedades, en el log se registra un mensaje sobre la creación exitosa del símbolo, de lo contrario, en el log se registrará el código de error surgido al establecer las propiedades del símbolo. 

Si el valor del parámetro BaseName no está vacío, las propiedades del símbolo creado se copian desde las propiedades del símbolo básico, cuyo nombre se ha indicado con el parámetro BaseName, por ejemplo, pueden ser las parejas de divisas EURUSD, USDCAD, GBPUSD y otras.

La función SetProperty, descrita después del código de la función principal del script, es la encargada de definir las propiedades del símbolo. Esta función no se usa en otros scripts, por eso no la hemos ubicado en una clase conectable aparte.

Para las propiedades del tipo String, Integer y Double se han creado ejemplares aparte de la función SetProperty. Para establecer las propiedades del símbolo personalizado, se usan las funciones CustomSymbolSetStringCustomSymbolSetInteger, CustomSymbolSetDouble. Para obtener las propiedades del símbolo básico, se usan las funciones SymbolInfoStringSymbolInfoIntegerSymbolInfoDouble

El símbolo personalizado usado se elige en la Observación de mercado con la función SymbolSelect: tras establecer con éxito las propiedades.

  if(SymbolSelect(SName,true))
         Print("El símbolo ",SName," ha sido elegido en Market Watch");
      else
         Print("Error al elegir el símbolo en Market Watch. Código de error: ",GetLastError());

Para abrir el gráfico del símbolo creado, debemos cargar en el símbolo los ticks o barras; el script para generar los ticks o barras lo veremos más abajo. 

Ahora vamos a analizar el proceso de eliminación del símbolo personalizado. Si usted quiere eliminar un símbolo personalizado eligiéndolo en la pestaña Símbolos, no le resultará siempre posible hacerlo:

Intento de eliminación de un símbolo

Fig. 1. Intento de eliminación de un símbolo, cuando este se encuentra seleccionado en la Observación de mercado

Para eliminar un símbolo, es necesario quitarlo de la Observación de mercado; podrá conseguirlo haciendo doble clic sobre el símbolo en la venta Símbolos. En este caso, además, no deberán existir ni posiciones ni gráficos abiertos del símbolo que quiere desactivar, por eso deberá cerrar todos los gráficos y posiciones manualmente. Este proceso no es muy rápido, especialmente si usted tiene abiertos muchos gráficos del símbolo dado. Vamos a ver cómo implementar la eliminación de un símbolo con la ayuda de un script (el script se adjunta al artículo en el archivo DeleteSymbol.mq5):

//+------------------------------------------------------------------+
//|                                                 DeleteSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//si el símbolo existe
     {
      if(!CustomSymbolDelete(SName))//intentamos eliminarlo
        {
         if(SymbolInfoInteger(SName,SYMBOL_SELECT))//si está elegido en la Observación de mercado
           {
            if(SymbolSelect(SName,false))//Intentamos desactivar y eliminar
              {
               if(!CustomSymbolDelete(SName))
                  Print("Error al eliminar el símbolo ",SName," Código de error: ",GetLastError());
               else
                  Print("El símbolo ",SName," se ha eliminado con éxito");
              }
            else
              {
               //intentamos cerrar los gráficos con el símbolo
               int i=0;
               long CurrChart=ChartFirst();
               int i_id=0;
               long ChartIDArray[];
               while(CurrChart!=-1)
                 {
                  //iteramos por la lista de gráficos y guardamos los identificadores de los gráficos abiertos del símbolo SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }
               //cerramos todos los gráficos del símbolo SName
               for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Error al cerrar el gráfico del símbolo ",SName,". Código de error: ",GetLastError());
                     return;
                    }
                 }
               //desactivamos y eliminamos el símbolo 
               if(SymbolSelect(SName,false))
                 {
                  if(!CustomSymbolDelete(SName))
                     Print("Error al eliminar el símbolo ",SName," Código de error: ",GetLastError());
                  else
                     Print("El símbolo ",SName," se ha eliminado con éxito");
                 }
               else
                  Print("Error al desactivar el símbolo ",SName," en la Observación de mercado. Código de error: ",GetLastError());
              }//end else SymbolSelect 
           } //end if(SymbolSelect(SName,false))
         else
            Print("Error al eliminar el símbolo ",SName," Código de error: ",GetLastError());
        }
      else
         Print("El símbolo ",SName," se ha eliminado con éxito");
     }
   else
      Print("El símbolo ",SName," no existe");
  }
//+------------------------------------------------------------------+

Vamos a analizar el orden de trabajo del script:

  • primero comprobamos la presencia del símbolo con el nombre SName,
  • si encontramos el símbolo, se intentará eliminar el símbolo con la ayuda de la función CustomSymbolDelete,
  • si no se ha logrado eliminar el símbolo, tratamos de desactivarlo en la Observación de mercado con la ayuda de la función SimbolSelect,
  • si no se ha logrado desactivar el símbolo en la Observación de mercado, cerramos todos los gráficos del símbolo.
Para ello, iteramos por todos los gráficos abiertos y guardamos los identificadores de los gráficos abiertos con el nombre SName:
               while(CurrChart!=-1)
                 {
                  //iteramos por la lista de gráficos y guardamos los identificadores de los gráficos abiertos del símbolo SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }

Cerramos todos los gráficos con identificadores guardados en la matriz ChartIDArray:

              for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Error al cerrar el gráfico del símbolo ",SName,". Código de error: ",GetLastError());
                     return;
                    }
                 }
  • después de cerrar todos los gráficos, intentamos de nuevo desactivar el símbolo desde la Observación de mercado y eliminamos el símbolo, de lo contrario, en el log se registrará un mensaje de error.

Como podemos notar, el script no prevé el cierre automático de posiciones del símbolo seleccionado. Esto se ha hecho para que un eventual inicio casual del script no influya en las operaciones comerciales del usuario. Si usted quiere eliminar un símbolo que tiene posiciones abiertas, ciérrelas preliminarmente por sí mismo.

Tras analizar la creación y eliminación de posiciones, vamos a pasar a la descripción del proceso de creación de ticks y barras.

Generación de ticks y barras

Después de crear el símbolo, deberemos cargar en el mismo la historia comercial: podemos cargar barras y simular los asesores e indicadores usando el modo de generación de ticks integrado en el simulador de estrategias, o bien cargar los ticks y barras y realizar la simulación con los ticks cargados. En este artículo se muestra un método de carga de ticks y barras basado en los datos de los precios disponibles. Los scripts propuestos generan automáticamente ticks y barras según leyes de distribución establecidas.

Aquí mostramos el código del script para la generación de barras (el archivo del script es GetCandle.mq5):

//+------------------------------------------------------------------+
//|                                                    GetСandle.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property link      ""
#property version   "1.00"
#property script_show_inputs 

#include </Math/Stat/Beta.mqh>
#include </Math/Stat/Binomial.mqh>
#include </Math/Stat/Cauchy.mqh>
#include </Math/Stat/ChiSquare.mqh>
#include </Math/Stat/Exponential.mqh>
#include </Math/Stat/F.mqh>
#include </Math/Stat/Gamma.mqh>
#include </Math/Stat/Geometric.mqh>
#include </Math/Stat/Hypergeometric.mqh>
#include </Math/Stat/Logistic.mqh>
#include </Math/Stat/Lognormal.mqh>
#include </Math/Stat/NegativeBinomial.mqh>
#include </Math/Stat/NoncentralBeta.mqh>
#include </Math/Stat/NoncentralChiSquare.mqh>
#include </Math/Stat/NoncentralF.mqh>
#include </Math/Stat/NoncentralT.mqh>
#include </Math/Stat/Normal.mqh>
#include </Math/Stat/Poisson.mqh>
#include </Math/Stat/T.mqh>
#include </Math/Stat/Uniform.mqh>
#include </Math/Stat/Weibull.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum Distribution
  {
   Beta,
   Binomial,
   Cauchy,
   ChiSquare,
   Exponential,
   F,
   Gamma,
   Geometric,
   Hypergeometric,
   Logistic,
   Lognormal,
   NegativeBinomial,
   NoncentralBeta,
   NoncentralChiSquare,
   NoncentralF,
   NoncentralT,
   Normal,
   Poisson,
   T,
   Uniform,
   Weibull
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
/*input params*/
input string SName="ExampleCurrency";
input datetime TBegin=D'2018.01.01 00:00:00';    //Hora de inicio de generación de las barras
input datetime TEnd=D'2018.02.01 00:00:00';      //Hora de finalización de generación de las barras
input int BarForReplace=1000;                    //Número de barras, transcurridas las cuales se ejecuta la sustitución de barras
input double BaseOCHL=1;                         //Valor básico del precio OCHL
input double dOCHL=0.001;                        //Coeficiente de variación del precio OCHL
input ulong BaseRealVol=10000;                   //Valor básico del volumen
input ulong dRealVol=100;                        //Coeficiente de variación del volumen real
input ulong BaseTickVol=100;                     //Valor básico del volumen
input ulong dTickVol=10;                         //Coeficiente de variación del volumen de ticks
input ulong BaseSpread=0;                        //Valor básico del spread
input ulong dSpread=1;                           //Coeficiente de variación del spread
input Distribution DistOCHL=Normal;              //Tipo de distribución para los precios OCHL
input Distribution DistRealVol = Normal;         //Tipo de distribución para el volumen real
input Distribution DistTickVol = Normal;         //Tipo de distribución para el volumen de ticks
input Distribution DistSpread = Uniform;         //Tipo de distribución para el spread
input bool DiffCandle=false;                     //Generar velas de distintos tipos
input double DistOCHLParam1=0;                   //Parámetro 1 de distribución para los precios OCHL
input double DistOCHLParam2=1;                   //Parámetro 2 de distribución para los precios OCHL
input double DistOCHLParam3=0;                   //Parámetro 3 de distribución para los precios OCHL
input double DistRealParam1=0;                   //Parámetro 1 de distribución para el volumen real
input double DistRealParam2=1;                   //Parámetro 2 de distribución para el volumen real
input double DistRealParam3=0;                   //Parámetro 3 de distribución para el volumen real
input double DistTickParam1=0;                   //Parámetro 1 de distribución para el volumen de ticks
input double DistTickParam2=1;                   //Parámetro 2 de distribución para el volumen de ticks
input double DistTickParam3=0;                   //Parámetro 3 de distribución para el volumen de ticks
input double DistSpreadParam1=0;                 //Parámetro 1 de distribución para el spread
input double DistSpreadParam2=50;                //Parámetro 2 de distribución para el spread
input double DistSpreadParam3=0;                 //Parámetro 3 de distribución para el spread
input bool FiveDayOfWeek=true;                   //true - no formar ticks en los días festivos
/*----input params----*/
int i_bar=0;                                     //contador, barras de minuto
MqlRates MRatesMin[];                            //matriz para guardar barras como barras
MqlDateTime  StructCTime;                        //estructura para el trabajo con el tiempo
int DistErr=0;                                   //número de error
bool IsErr=false;                                //error
double DistMass[4];                              //matriz para guardar los valores generados de OCHL
int ReplaceBar=0;                                //número de barras sustituidas
double BValue[1];                                //matriz para copiar el último precio Close
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;                                      //contador del ciclo
   double MaxVal,MinVal;                         //valores High y Low
   int i_max,i_min;                              //índices de los valores mayor y menor de la matriz DistMass 
   datetime TCurrent=TBegin;
   BValue[0]=BaseOCHL;
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//si el símbolo existe
     {
      while(TCurrent<=TEnd)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("No hay transacciones en el intervalo dado");
                  return;
                 }
              }
           }
         ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
         MRatesMin[i_bar].open=0;
         MRatesMin[i_bar].close=0;
         MRatesMin[i_bar].high=0;
         MRatesMin[i_bar].low=0;
         //rellenamos Open      
         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }
         //generamos los precios High, Low
         MaxVal=2.2250738585072014e-308;
         MinVal=1.7976931348623158e+308;
         i_max=0;
         i_min=0;
         for(i=0;i<3;i++)
           {
            DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            if(IsErrCheck(DistErr)) return;
            if(MaxVal<DistMass[i])
              {
               MaxVal=DistMass[i];
               i_max=i;
              }
            if(MinVal>DistMass[i])
              {
               MinVal=DistMass[i];
               i_min=i;
              }
           }
         if(MaxVal<MRatesMin[i_bar].open)
            MRatesMin[i_bar].high=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].high=MaxVal;
         if(MinVal>MRatesMin[i_bar].open)
            MRatesMin[i_bar].low=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].low=MinVal;
         //rellenamos Close
         for(i=0;i<3;i++)
            if((i!=i_max) && (i!=i_min))
              {
               MRatesMin[i_bar].close=DistMass[i];
               break;
              }
         //generamos el volumen, spread
         MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3));
         if(IsErrCheck(DistErr)) return;
         //registramos la hora
         MRatesMin[i_bar].time=TCurrent;
         if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }
         //comprobamos si es momento de sustituir las barras  
         if(i_bar>=BarForReplace-1)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
            TCurrent=TCurrent+60;
            BValue[0]=MRatesMin[i_bar].close;
            i_bar=0;
            ArrayFree(MRatesMin);
           }
         else
           {
            i_bar++;
            TCurrent=TCurrent+60;
           }
        }
      if(i_bar>0)
        {
         i_bar--;
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
        }
     }
   else
      Print("El símbolo ",SName," no existe");
  }
//+------------------------------------------------------------------+

void ReplaceHistory(datetime DBegin,datetime DEnd)
  {
   ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin);
   if(ReplaceBar<0)
      Print("Error al sustituir las barras. Código de error: ",GetLastError());
   else
      PrintFormat("La historia de precios en el periodo comprendido desde %s hasta %s se ha formado con éxito. Se han creado %i barras, se han añadido (sustituido) %i barras",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar);
  }
//+------------------------------------------------------------------+

double GetDist(Distribution d,double p1,double p2,double p3)
  {
   double res=0;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   switch(d)
     {
/*Ditribución beta*/
      //p1,p2 - primer y segundo parámetro 
      case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;}
/*Distribución binomial*/
      //p1 - número de pruebas, p2 - probabilidad de éxito para cada prueba    
      case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;};
/*Distribución de Cauchy*/
      //p1 - coeficiente de desplazamiento, p2 - coeficiente de escala
      case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;};
/*Distribución chi cuadrado*/
      //p1 - número de grados de libertad
      case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;};
/*Distribución exponencial*/
      //p1 - parámetro(lambda) de distribución 
      case Exponential: {res=MathRandomExponential(p1,DistErr); break;};
/*Distribución de Fisher*/
      //p1, p2 - número de grados de libertad
      case F: {res=MathRandomF(p1,p2,DistErr); break;};
/*Distribución Gamma*/
      //p1 - parámetro de distribución(número entero), p2 - coeficiente de escala
      case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;};
/*Distribución geométrica*/
      //p1 - probabilidad de éxito (aparición de un evento en la prueba)
      case Geometric: {res=MathRandomGeometric(p1,DistErr); break;};
/*Distribución hipergeométrica*/
      //p1 - número total de objetos, p2 - número de objetos con la característica deseada, p3 - número de objetos en la muestra
      case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;};
/*Distribución logística*/
      //p1 - esperanza matemática,p2 - coeficiente de escala 
      case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;};
/*Distribución log-normal*/
      //p1 -logaritmo de esperanza matemática,p2 - logaritmo de desviación media cuadrática
      case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;};
/*Distribución binomial negativa*/
      //p1 - número de pruebas con éxito, p2 - probabilidad de éxito  
      case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;};
/*Distribución beta no central*/
      //p1,p2 - primer y segundo parámetro, p3 - parámetro de no-centralidad       
      case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;};
/*Distribución chi cuadrado no central*/
      //p1 - número de grados de libertad, p2 - parámetro de no-centralidad
      case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;};
/*Distribución F no central*/
      //p1, p2 - número de grados de libertad, p3 - parámetro de no-centralidad
      case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;};
/*Distribución T no central*/
      //p1 - número de grados de libertad, p2 - parámetro de no-centralidad
      case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;};
/*Distribución normal*/
      //p1 - esperanza matemática, p2 - desviación media cuadrática
      case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;};
/*Distribución de Poisson*/
      //p1 - esperanza matemática
      case Poisson: {res=MathRandomPoisson(p1,DistErr); break;};
/*Distribución de Student*/
      //p1 - número de grados de libertad
      case T: {res=MathRandomT(p1,DistErr); break;};
/*Distribución uniforme*/
      //p1 - límite inferior del intervalo, p2 - límite superior del intervalo
      case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;};
/*Distribución de Weibull*/
      //p1 - parámetro del formulario, p2 - parámetro de la escala
      case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;};
     }
   if(DistErr!=0)
      return -1;
   else
      return res;
  }
//+------------------------------------------------------------------+
bool IsErrCheck(int Err)
  {
//Comrpobamos si hay error en la generación de números aleatorios
   switch(DistErr)
     {
      case(1):
        {
         MessageBox("Los valores establecidos para la distribución no son números reales","Error en los parámetros de entrada",MB_ICONWARNING);
         return true;
        }
      case(2):
        {
         MessageBox("Los valores establecidos para la distribución no están permitidos","Error en los parámetros de entrada",MB_ICONWARNING);
         return true;
        }
      case(4):
        {
         MessageBox("Error de división por cero","Error en los parámetros de entrada",MB_ICONWARNING);
         return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+

Vamos a analizar el funcionamiento del script. El script usa archivos de la biblioteca estándar del directorio Estadísticas que implementan diferentes distribuciones estadísticas. Para elegir la ley (tipo) de distribución según la cual van generarse los números pseudoaleatorios para formar los parámetros de cada barra, se ha creado la enumeración Distribution.

Con la ayuda de números pseudoaleatorios, se generan los precios Close, High, Low, el volumen real y el spread según la siguiente fórmula:

form1 (1)

donde P(i) es el valor del parámetro, Base es valor básico del parámetro, step es el coeficiente de escala (salto) del cambio de la magnitud pseudoaleatoria, DistValue(i) es la magnitud pseudoaleatoria generada, distribuida según la ley establecida. Los parámetros Base y step son establecidos por el usuario. Como ejemplo, vamos a mostrar el código para formar el valor del precio de apertura:

         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }

Si antes del inicio del script no hay barras o, por cualquier otro motivo, CopyClose no ha podido copiar la última barra de la historia de precios, el precio de apertura de la primera barra se formará según la fórmula descrita más arriba:

Para las barras posteriores, el precio Open es igual al precio Close anterior, es decir, la nueva barra se abre en el cierre de la anterior. 

La función GetDist es la responsable de la generación del valor de la magnitud pseudoaleatoria. A la entrada de dicha función llega el tipo de distribución y el valor de sus parámetros.

Para procesar los errores surgidos al generar la magnitud pseudoaleatoria, se ha creado la función IsErrCheck. A la entrada de la función llega el código del error que se define al ejecutar la función GetDist. Si surge un error, la ejecución del script se interrumpe, y se registrará el error en el log. El código de las funciones GetDist IsErrCheck se muestra al final del script.   

El script genera ticks de minuto y posee las siguientes pecualiaridades:

e1) Los ticks se generan solo en el intervalo de tiempo establecido, existe la posibilidad de no generar los ticks en los fines de semana (parámetro de entrada FiveDayOfWeek=true)

En el código que vemos a continuación, se implementa la desactivación de la generación de ticks los fines de semana:

if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("No hay transacciones en el intervalo dado");
                  return;
                 }
              }
           }

Si la hora actual cae en día festivo, esta se desplazará hasta el siguiente día comercial. 

2) El script permite sustituir las barras por partes, liberando memoria después de la sustitución de las barras generadas

El número de barras tras cuya sustitución reseteamos la matriz de ticks generados, se establece en BarForReplace. La sustitución de barras se implementa en el código siguiente:

if(i_bar>=BarForReplace)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
            i_bar=0;
            ArrayFree(MRatesMin);
           }

Para sustituir las barras, se ha creado la función ReplaceHistory, cuyo código se muestra al final del script. La sustitución de barras se ejecuta con la función CustomRatesReplace. Después de sustituir las barras, el contador de barras se resetea y se libera el búfer de la matriz dinámica MRatesMin, que guarda las barras creadas. 

3) El script permite generar velas de diferentes tipos

La generación de diferentes tipos de vela se ha implementado de la forma siguiente (parámetro DiffCande = true):

 if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Doji
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Hammer
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Star
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Maribozu
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }

De etsa forma, el script permite generar velas normales, ya sean estas largas o cortas, "doji", "hammer", "star" y "maribozu", con la misma probabilidad.

Vamos a mostrar el funcionamiento del script, para ello, iniciamos este con los siguientes parámetros de entrada:

input

Fig. 1. Parámetros de entrada del script

Como resultado del funcionamiento, aparecerá el nuevo símbolo ExampleCurrency. En el proceso de funcionamiento se han generado 33121 barras de minuto. En la figura 2 se muestra un fragmento del gráfico de minutos del símbolo ExampleCurrency.

ExChart

Fig. 2. Gráfico de minutos del símbolo ExampleCurrency

A veces, las barras de minuto pueden resultar insuficientes para realizar la simulación del asesor o indicador, y la prueba se ejecuta con ticks reales o modelados. 

Vamos a analizar un script que modela ticks y forma barras de minuto usando como base los ticks modelados. El código completo del script se muestra en el archivo GetTick.mq5, adjunto al artículo. Vamos a mostrar y analizar el código de la función OnStart():

void OnStart()
  {
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//si el símbolo existe
     {
      MqlDateTime  StructCTime;
      long TBeginMSec=(long)TBegin*1000;      //hora de comienzo de la generación de ticks en ms
      long TEndMSec=(long)TEnd*1000;          //hora de finalización de la generación de ticks en ms
      int ValMsec=0;                          //variable para la generación del desplazmaiento temporal aleatorio en ms
      int SumSec=0;                           //contador de segundos
      int SumMSec=0;                          //contador de milisegundos
      int PrevTickCount=0;                    //variable para guardar el anterior número de ticks por minuto      
      datetime TCurrent=TBegin;
      bool   NewMinute=false;
       //copiamos el valor del precio a partir del cual comenzará la generación de ticks
      if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)
         BValue[0]=Base;
      //rellenamos la estructura LastTick
      LastTick.ask=BValue[0];
      LastTick.bid=BValue[0];
      LastTick.last=BValue[0];
      LastTick.volume=baseVol;

      while(TBeginMSec<=TEndMSec)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6))
              {
               if(StructCTime.day_of_week==0)
                 {
                  TCurrent=TCurrent+86400;
                  TBeginMSec=TBeginMSec+86400000;
                 }
               else
                 {
                  TCurrent=TCurrent+2*86400;
                  TBeginMSec=TBeginMSec+2*86400000;
                 }
               if(TBeginMSec>=TEndMSec)
                  break;
              }
           }
         GetTick(TCurrent,TBeginMSec);
         if(IsErrCheck(DistErr)) return;
         i_tick++;

         if(RandomTickTime)
           {
            //generamos el desplazamiento temporal aleatorio
            
            ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
            SumSec=SumSec+ValMsec;
            SumMSec=SumMSec+ValMsec;
            if(i_tick-PrevTickCount>=MaxTickInMinute)
              {
               TimeToStruct(TCurrent,StructCTime);
               StructCTime.sec=0;
               TCurrent=StructToTime(StructCTime)+60;
               TBeginMSec=TBeginMSec+60000-SumSec+ValMsec;
               SumSec=0;
               SumMSec=0;
               NewMinute=true;
              }
            else
              {
               if(SumSec>=60000)
                 {
                  //reseteamos el contador de ticks por minuto
                  SumSec=SumSec-60000*(SumSec/60000);
                  NewMinute=true;
                 }
               //formando una nueva hora de tick    
               TBeginMSec=TBeginMSec+ValMsec;
               if(SumMSec>=1000)
                 {
                  TCurrent=TCurrent+SumMSec/1000;
                  SumMSec=SumMSec-1000*(SumMSec/1000);
                 }
              }
           }
         else
           {
            TBeginMSec=TBeginMSec+60000/MaxTickInMinute;
            SumSec=SumSec+60000/MaxTickInMinute;
            SumMSec=SumMSec+60000/MaxTickInMinute;
            if(SumMSec>=1000)
              {
               TCurrent=TCurrent+SumMSec/1000;
               SumMSec=SumMSec-1000*(SumMSec/1000);
              }
            if(SumSec>=60000)
              {
               SumSec=SumSec-60000*(SumSec/60000);
               NewMinute=true;
              }
           }
         if(NewMinute)
           {
            //añadimos una nueva barra a la matriz 
            ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
            if(ArraySize(MRatesMin)==1)//si es el primer minuto
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }
            MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
            if(ValHigh>MRatesMin[i_bar].open)
               MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
            else
               MRatesMin[i_bar].high=MRatesMin[i_bar].open;
            if(ValLow<MRatesMin[i_bar].open)
               MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
            else
               MRatesMin[i_bar].low=MRatesMin[i_bar].open;
            MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
            MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
            TimeToStruct(MTick[i_tick-1].time,StructCTime);
            StructCTime.sec=0;
            MRatesMin[i_bar].time=StructToTime(StructCTime);
            i_bar++;
            PrevTickCount=i_tick;
            ValHigh=2.2250738585072014e-308;
            ValLow=1.7976931348623158e+308;
            NewMinute=false;
            if(i_bar>=BarForReplace)
              {
               ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
               LastTick.bid=MTick[i_tick-1].bid;
               LastTick.ask=MTick[i_tick-1].ask;
               LastTick.last=MTick[i_tick-1].last;
               LastTick.volume=MTick[i_tick-1].volume;
               i_tick=0;
               i_bar=0;
               PrevTickCount=0;
               ArrayFree(MRatesMin);
               ArrayFree(MTick);
              }
           }
        }//end while
      if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
     }
   else
      Print("El símbolo ",SName," no existe");
  }

Al inicio de la función OnStart() se ejecuta la inicialización de variables. La generación de ticks y barras se ejecuta en el ciclo principal de funcionamiento del script:

while(TBeginMSec<=TEndMSec)
{
...
}

Al inicio del ciclo, si el parámetro FiveDayOfWeek = true, se ejecuta la desactivación de la generación de ticks en fines de semana; la hora del tick, en este caso, se desplaza 1 día (si la hora del tick se corresponde con el domingo) o en 2 días (si la hora del tick se corresponde con el sábado):

if(FiveDayOfWeek)
{
...
}

A continuación, se ejecuta la generación del tick con la ayuda de la función GetTick:

 GetTick(TCurrent,TBeginMSec);
	if(IsErrCheck(DistErr)) return;
 	i_tick++;

Si al generar el tick ha surgido un error (el valor de la función IsErrCheck = true), se interrumpirá la ejecución del script. La función IsErrCheck se describe más arriba en el código del script GetCandle.

Vamos a analizar la función  GetTick:

void GetTick(datetime TDate,long TLong)
  {
   ArrayResize(MTick,ArraySize(MTick)+1);
//rellenamos la nueva hora  
   MTick[i_tick].time=TDate;
   MTick[i_tick].time_msc=TLong;
//rellenamos el tick actual con los valores del anterior
   if(ArraySize(MTick)>1)
     {
      MTick[i_tick].ask=MTick[i_tick-1].ask;
      MTick[i_tick].bid=MTick[i_tick-1].bid;
      MTick[i_tick].volume=MTick[i_tick-1].volume;
      MTick[i_tick].last=MTick[i_tick-1].last;
     }
   else
     {
      MTick[i_tick].ask=LastTick.ask;
      MTick[i_tick].bid=LastTick.bid;
      MTick[i_tick].last=LastTick.last;
      MTick[i_tick].volume=LastTick.volume;
     }
//rellenamos el tick actual  
   if(RandomTickValue)
     {
      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);
      if(RBid>=0.5)
        {
         if(i_tick>0)
            MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
         MTick[i_tick].last=MTick[i_tick].bid;
         MTick[i_tick].flags=10;
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=MTick[i_tick].flags+4;
           }
         if(RVolume>=0.5)
           {
            MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
            MTick[i_tick].flags=MTick[i_tick].flags+16;
           }
        }
      else
        {
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=4;
            if(RVolume>=0.5)
              {
               MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
               MTick[i_tick].flags=MTick[i_tick].flags+16;
              }
           }
        }
     }//end if(RandomTickValue)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   else
     {
      MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].last=MTick[i_tick].bid;
      MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
      MTick[i_tick].flags=30;
     }//end if(RandomTickValue)  
  //guardamos los valores mayor y menor para formar las barras de minuto  
   if(MTick[i_tick].bid>ValHigh)
      ValHigh=MTick[i_tick].bid;
   if(MTick[i_tick].bid<ValLow)
      ValLow=MTick[i_tick].bid;
  }//end 

A la entrada de la función llega la hora del tick en el formato datetime y en milisegundos. En primer lugar, el tick actual se rellena con los valores del anterior, a continuación, los valores del tick actual se modificarán de la forma siguiente:

e1) Si el valor del parámetro RandomTickValue = true, cada uno de los parámetros Ask, Bid y Volume será modificado con una probabilidad de 0.5. Para ello, se generan 3 magnitudes aleatorias homogéneamente distribuidas:

      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);

Si RBid>0.5, RAsk>0.5, el precio Bid y/o Ask cambiará según la fórmula (1), descrita más arriba para el script GetCandle. El cambio en el volumen según la fórmula (1) se ejecuta si ha cambiado el precio Ask o Bid y el parámetro RVolume > 0.5. Al precio Last se le asigna el valor del precio Bid cuando el precio cambia.

2) Si el valor del parámetro RandomTickValue = false, los valores de los parámetros Ask, Bid y Volume se calculan según la fórmula (1). 

El valor de la bandera de ticks (flags) se establece de la forma siguiente:

  • cambio del precio Bid - flags=flags+2
  • cambio del precio Ask - flags=flags+4
  • cambio del precio Last - flags=flags+8
  • cambio del precio Volume - flags=flags+16

Después de cambiar los precios Ask, Bid o Volume en las variables ValHigh y ValLow, se guardan los precios Bid máximo y mínimo. Los valores de las variables ValHigh y ValLow se usan para formar los precios High y Low de la barra de minuto. 

Vamos a continuar analizando el código de la función OnStart().

Después de generar el tick actual, se ejecuta la formación de la hora de aparición del nuevo tick:

e1) Si el parámetro RandomTickTime = true, la nueva hora del tick se formará de la forma siguiente:

ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);

A continuación, se ejecuta la comprobación de la llegada de la nueva barra de minutos, así como la comprobación de la hora actual en el número de segundos formado. El número de ticks que puede formarse durante una barra de minuto, está limitado por la variable MaxTickInMinute. Si el número de ticks generados supera el valor de la variable MaxTickInMinute, los contadores de segundos (SumSec) y milisegundos (SumMSec) se resetean, y a continuación se forma una nueva barra de minuto (NewMinute = true). 

2) Si el parámetro RandomTickTime = false, en cada barra de minuto se genera el mismo número de ticks establecido en la variable MaxTickInMinute.

La formación de una barra de minuto basada en los ticks generados tiene lugar de la forma siguiente:

  • la matriz de barras de minuto aumenta en 1

 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);

  • se forma el valor del precio de apertura de la barra actual y el volumen de ticks:

            if(ArraySize(MRatesMin)==1)//si es el primer minuto
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }

Al formar una nueva barra de minuto, al precio de apertura se le asigna el precio Bid del último tick de la anterior sustitución de ticks (la sustitución de barras y ticks es ejecutada por la función ReplaceHistory) o el valor básico del precio Bid (parámetro Base), si la sustitución de ticks y barras no se ha ejecutado anteriormente. Al formarse las posteriores barras de minuto, al precio de apertura se le asignará el valor normalizado del precio Bid del último tick (precio de cierre) del minuto anterior. Entendemos por normalizado el redondeo hasta la exactitud de medición del precio del símbolo del gráfico actual en el que está iniciado el scrip. 

  • se forma el valor del precio Close - el precio Bid normalizado del último tick:

MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);

  • se forma el valor de los precios High y Low. En este caso, además, se tiene en cuenta que el precio de apertura puede ser superior al valor mayor (ValHigh) o menor al valor menor(ValLow) del precio Bid de los ticks generados:

 if(ValHigh>MRatesMin[i_bar].open)
     MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
 else
     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
 if(ValLow<MRatesMin[i_bar].open)
     MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
 else
     MRatesMin[i_bar].low=MRatesMin[i_bar].open;

MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);

Al volumen de la barra actual se le asigna el valor del volumen del último tick. El spread se calcula como la diferencia entre los precios Bid y Ask del último tick. 

TimeToStruct(MTick[i_tick-1].time,StructCTime);
StructCTime.sec=0;
MRatesMin[i_bar].time=StructToTime(StructCTime);

Con esto, el proceso de formación de los parámetros de la barra de minuto actual se ha terminado (variable i_bar); a las variables ValHigh y ValLow se les asignan los valores máximo y mínimo del tipo de datos y se resetea la bandera de la barra de minuto (NewMinute). A continuación, se comprueba si es momento de sustituir el número formado de barras de minuto y ticks. El número de barras que pone en marcha su sustitución, se establece con la ayuda de la variable BarForReplace

Después de salir del ciclo principal, se realiza la sustitución de las barras restantes, si tales existen:

     if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);

En la función ReplaceHistory se implementa la sustitución de ticks y barras. La sustitución de ticks la ejecuta la función CustomTicksReplace, la sustitución de barras, CustomRatesReplace.

De esta forma, el script GetTick, descrito más arriba, permite generar los ticks según las leyes de predeterminación establecidas, y también formar barras de minuto a partir de los ticks y cargar los datos obtenidos en la historia de precios del símbolo personalizado.

Modelado de tendencia

Los scripts GetCandle y GetTick, analizados en el apartado anterior, permiten generar la historia de precios sin grandes oscilaciones de precio, es decir, en flat. Para formar situaciones de mercado más complejas y simular con ellas asesores e indicadores, es imprescindible modelar el movimiento de tendencia del precio.

Para estos objetivos se han escrito GetCandleTrend (adjunto al artículo en el archivo GetCandleTrend.mq5) y GetTickTrend (adjunto al artículo en el archivo GetTickTrend.mq5), que permiten modelar tendencias ascendentes o descendentes según la ley establecida de movimiento de precios. El script GetCandleTrend ha sido pensado para generar barras de minuto ascendentes o descendentes, el script GetTickTrend genera ticks ascendentes o descendentes, a continuación, de forma análoga al script GetCandleTrend , forma las barras de minuto. 

Vamos a analizar el funcionamiento del script GetCandleTrend. La formación de las barras de minuto tiene lugar de forma análoga al script GetCandle, por eso, vamos a analizar solo el modo de formación de la tendencia. Los datos de entrada del script contienen los siguientes parámetros de tendencia:

input TrendModel TModel = Linear;                //Modelo de la tendencia
input TrendType TType =  Increasing;             //Tipo de tendencia (ascendente/descendente,aleatorio)
input double RandomTrendCoeff=0.5;               //Coeficiente de tendencia(con RandomTrendCoeff<0.5, predomina la tendencia descendente, con RandomTrendCoeff>0.5, la ascendente)
input double Coeff1=0.1;                         //Coeficiente k1 del modelo de tendencia
input double Coeff2=0.1;                         //Coeficiente k2 del modelo de tendencia
input double Coeff3=0.1;                         //Coeficiente k3 del modelo de tendencia
input int CountCandle=60;                        //Intervalo de cambio aleatorio de la dirección de la tendencia (en barras)

Al usuario se le ofrece elegir el modelo de tendencia establecido en la enumeración TrendModel:

enum TrendModel
  {
   Linear,
   Hyperbolic,
   Exp,
   Power,
   SecondOrderPolynomial,
   LinearAndPeriodic,
   LinearAndStochastic
  };

y el tipo de tendencia establecido en la enumeración TrendType:

enum TrendType
  {
   Increasing,
   Decreasing,
   Random
  };

La tendencia se forma según los siguientes modelos: lineal, hiperbólico, exponencial, de potencia, parabólico, lineal-periódico y lineal-estocástico. Las fórmulas según las cuales se forma la tendencia, se muestran en el recuadro 1:

Modelo de la tendenciaFórmula
LinealLinear
Hiperbólicof2
Exponencialf3
De potenciaf4
Parabólicof5
Lineal-periódicof6
Lineal.estocásticof7

T(i) es el valor actual de la tendencia; k1, k2, k3, son los coeficientes que influyen en la velocidad de crecimiento(descendente) de la tendencia; N(0,1) es magnitud aleatoria distruibuida según la ley normal con esperanza matemática cero y una varianza unitaria.

Como tipo de tendencia, se ofrece elegir el ascendente, el descendente o el aleatorio. Entendemos por tendencia aleatoria aquella cuya dirección cambia transcurrido un cierto número de velas determinado (el número de velas se establece con el parámetro CountCandle). 

La formación de la tendencia se implementa en la función ChooseTrend:

double ChooseTrend()
  {
   switch(TType)
     {
      case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 2:
        {
         if((i_trend%CountCandle==0) && (i_trend!=0))
           {
            if(i_bar!=0)
               BValue[0]=MRatesMin[i_bar-1].close;
            LastRand=MathRandomUniform(0,1,DistErr);
            i_trend=0;
           }
         if(LastRand>RandomTrendCoeff)
            return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
         else
            return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
        }
      default:return 0;
     }
  }

Con la tendencia aleatoria, tras cada N velas de minuto, establecidas con el parámetro CountCandle, se genera una magnitud aleatoria LastRand, distribuida homogéneamente en un intervalo de 0 a 1. Si el valor LastRand es superior al parámetro RandomTrendCoeff, la tendencia será ascendente, de lo contrario, descendente. El parámetro RandomTrendCoeff permite variar la probabilidad de cambio de tendencia. Si RandomTrendCoeff<0.5 , predominará la tendencia ascendente, si RandomTrendCoeff>0.5 , predominará la descendente. 

La formación de la tendencia de este modelo se implementa en la función GetModelTrend:

double GetModelTrend()
  {
   switch(TModel)
     {
      case Linear: return Coeff1+Coeff2*i_trend;
      case Hyperbolic:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+Coeff2/i_trend;
        }
      case Exp:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+MathExp(Coeff2*i_trend);
        }
      case Power:return Coeff1+MathPow((double)i_trend,Coeff2);
      case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend;
      case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend);
      case LinearAndStochastic:
        {
         LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue;
         return LastValue;
        }
      default:
         return -1;
     }
  }

En esta función se han implementado los modelos de tendencia mostrados en el recuadro 1.

Ahora analizaremos la formación de los gráficos de precio según diferentes modelos de tendencia. Bien, vamos a formar una tendencia lineal, para ello, iniciamos el script GetTrendCandle con los siguientes parámetros:

input2

Fig. 3. Parámetros del script GetTrendCandle

Después de ejecutar el script, abrimos el gráfico de minutos del símbolo ExampleCurrency:

Linear

Fig. 4. Gráfico de minutos del símbolo ExampleCurrency para el modelo lineal de tendencia

En el gráfico podemos ver que se ha generado una tendencia lineal, variando los coeficientes k1 y k2 se puede cambiar el ángulo de inclinación (velocidad de crecimiento/decrecimiento) de la tendencia. 

En los parámetros del script, indicamos el modelo hiperbólico de tendencia: TModel = Hyperbolic, Coeff1 = 1, Coeff2 = 1000. Tras iniciar el script, obtenemos el gráfico siguiente:

Params

Fig. 4. Gráfico de minutos del símbolo ExampleCurrency para el modelo hiperbólico de tendencia

La peculiaridad de este modelo es la siguiente: debido a que la función hiperbólica pertenece a la clase de funciones inversas, al elegir la tendencia ascendente (TType = Increasing), la tendencia resultará descendente. 

Vamos a analizar el modelo exponencial de tendencia: TModel =Exp, Coeff1 = 1, Coeff2 = 0,1. Tras iniciar el script, obtenemos el gráfico siguiente:

exp

Fig. 5. Gráfico de minutos del símbolo ExampleCurrency para el modelo exponencial de tendencia

Como se podía esperar, en el gráfico se ve una tendencia exponencial ascendente con un número de velas creciente.

Analicemos ahora los otros modelos de tendencia:

Modelo de tendencia de potencia:  TModel =Power, Coeff1 = 1, Coeff2 = 2. 

Power

Fig. 6. Gráfico de minutos del símbolo ExampleCurrency para el modelo de tendencia de potencia

Podemos ver que el gráfico se parece al modelo exponencial de tendencia, pero crece de forma más suave.

Modelo parabólico de tendencia:  TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0,05, Coeff3 = 0,05. 

Parab

Fig. 7. Gráfico de minutos del símbolo ExampleCurrency para el modelo parabólico de tendencia

El modelo parabólico es análogo al de potencia, pero tiene una velocidad mayor de crecimiento/decrecimiento de tendencia.

Modelo lineal-periódico de tendencia:  TModel = LinearAndPeriodic, Coeff1 = 0.05, Coeff2 = 0,1, Coeff3 = 0,1.

Periodic

Fig. 8. Gráfico de minutos <s0>del símbolo ExampleCurrency para el modelo lineal-periódico de tendencia</s0> 

En el modelo lineal-periódico, la tendencia, al crecer o disminuir, cambia su dirección según la ley periódica. 

Modelo lineal-estocástico de tendencia:  TModel = LinearAndStochastic, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1.

Stoh

Fig. 8. Gráfico de minutos <s0>del símbolo ExampleCurrency para el modelo lineal-estocástico de tendencia</s0> 

En el modelo lineal-estocástico, la tendencia aumenta o disminuye realizando oscilaciones aleatorias alrededor de una línea recta cuyo ángulo de inclinación se determina con el coeficiente k1.

Los modelos de tendencia analizados más arriba poseen las propiedades establecidas solo en los intervalos de minuto; en los intervalos М15, М30, H1 y superiores, los gráficos de precio formados según estos modelos, tendrán el mismo aspecto que en la función lineal. Para obtener una tendencia que cambie de dirección en otros intervalos, excepto M1, será necesario elegir el tipo aleatorio de tendencia (TType =  Random) e indicar el número de velas de minuto transcurrido el cual se intentará cambiar la dirección de la tendencia.

Iniciamos el script con los siguientes parámetros:  TModel = LinearAndStochastic, TType =  Random, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1, RandomTrendCoeff=0.5, CountCandle=60. Obtenemos el siguiente gráfico en el intervalo H1:

H1_1

Fig. Gráfico de 9 horas del símbolo ExampleCurrency con el cambio aleatorio de tendencia

Establecemos el parámetro RandomTrendCoeff = 0.7 e iniciamos el script:

H1_low

Fig. Gráfico de 10 horas del símbolo ExampleCurrency con el cambio aleatorio de tendencia con RandomTrendCoeff = 0.7

Podemos ver que se observa una tendencia descendente, cambiamos RandomTrendCoeff = 0.3 y conseguimos una tendencia ascendente:

H1_high

Fig. Gráfico de 10 horas del símbolo ExampleCurrency con el cambio aleatorio de tendencia con RandomTrendCoeff = 0.3

De esta forma, con la ayuda del script GetCandleTrend, podemos formar una tendencia con diferentes intervalos comerciales, generando en este caso barras de minuto. 

El script GetTickTrend permite generar ticks y luego formar barras de minuto a partir de ellos, además, posee las mismas capacidades que GetCandleTrend.

Modelado de patrones gráficos

Los patrones gráficos se usan ampliamente en el análisis técnico del mercado. Muchos tráders usan patrones típicos para buscar los puntos de entrada y salida del mercado, asimismo, se desarrollan diferentes indicadores y expertos que analizan los patrones en el gráfico de precios. 

En este apartado, vamos a mostrar cómo crear patrones gráficos con la ayuda de los scripts descritos más arriba. Como ejemplo, vamos a analizar el proceso de creación del patrón "Pico doble" y "Valle doble". El aspecto de los patrones se muestra en las figuras siguientes:

pattern2

Fig. 11. Patrón "Pico doble"

pattern2

Fig. 12. Patrón "Valle doble"

Para crear los patrones, se usará el script GetCandleTrend. Formará los patrones en el periodo H1. Para formar cada patrón, necesitaremos iniciar cuatro veces el script GetCandleTrend con diferentes parámetros de entrada. Seleccionamos los siguientes intervalos temporales, designados en las figuras 11 y 12 como t1-t5:

Para generar el patrón "Pico doble", establecemos los siguientes parámetros del script:

  • Hora de inicio de la generación de barras: 00:00 02.01.2018
  • Hora de finalización de la generación de barras: 12:00 02.01.2018
  • Modelo de tendencia: LinearAndStochastic
  • Tipo de tendencia: Random
  • Coeficiente de tendencia: 0.15 
  • Coeficiente k1 del modelo de tendencia: 0.15
  • Coeficiente k2 del modelo de tendencia: 1
  • Coeficiente k3 del modelo de tendencia: 1 
  • Intervalo de cambio aleatorio de dirección de la tendencia: 60
El resto de los ajustes los vamos a dejar por defecto. Después, iniciamos el script. A continuación, cambiamos los siguientes ajustes para el segundo, tercer y cuarto inicio del script

Inicio №2:

  • Hora de inicio de la generación de barras: 13:00 02.01.2018
  • Hora de finalización de la generación de barras: 12:00 03.01.2018
  • Coeficiente de tendencia: 0.85 
Inicio №3:

  • Hora de inicio de la generación de barras: 13:00 03.01.2018
  • Hora de finalización de la generación de barras: 12:00 04.01.2018
  • Coeficiente de tendencia: 0.15 
Inicio №4:

  • Hora de inicio de la generación de barras: 13:00 04.01.2018
  • Hora de finalización de la generación de barras: 00:00 05.01.2018
  • Coeficiente de tendencia: 0.85 

Como resultado, después de cuatro inicios del script GetCandleTrend, obtendremos el gráfico de precios mostrado en la figura 13.

2high

Fig. 13. Patrón modelado "Pico doble" en el periodo H1

De forma análoga, modelamos el patrón "Valle doble". Para ello, iniciamos el script GetCandleTrend cuatro veces con los ajustes indicados para el patrón "Pico doble", cambiando solo el coeficiente de tendencia: para el primer inicio, 0.85, para los siguientes 0.15, 0.85, 0.15. El resultado del funcionamiento del script se muestra en la figura 14.

2low

Fig. 14. Patrón modelado "Valle doble" en el periodo H1

De forma análoga se pueden modelar otros patrones. Los patrones más realistas se obtienen en el gráfico de minutos. Para formar patrones en otros intervalos temporales, será necesario indicar en el parámetro "Intervalo de cambio aleatorio de dirección de la tendencia" del script GetCandleTrend, el número de barras de minuto contenidas en el intervalo elegido, por ejemplo, para el intervalo H1 - 60, para el intervalo H4 - 240.

Conclusión

Los símbolos personalizados son una herramienta cómoda y útil para poner a prueba asesores e indicadores. En el artículo hemos creado y analizado scripts que poseen las siguientes capacidades:

e1) Creación y eliminación de símbolos personalizados

Hemos mostrado los métodos de creación de un símbolo personalizado basado en otro existente, o en uno nuevo con propiedades establecidas manualmente. El script que implementa la eliminación del símbolo permite cerrar todos los gráficos con el símbolo dado, o desactivarlo desde la "Observación de mercado".

2) Generación de ticks y barras

Los scripts permiten generar barras de minuto en el intervalo temporal establecido, con la posibilidad de desactivar la generación de barras en los días festivos (no comerciales). Hemos implementado la generación de diversas velas: velas largas o cortas, "doji", "hammer", "star" y "maribozu", y la sustitución de barras y ticks para ahorrar memoria al generar grandes matrices de barras y ticks.

3) Modelación de la tendencia

Los scripts permiten modelar la tendencia según diferentes modelos: lineal hiperbólico, exponencial, de potencia, parabólico, lineal-periódico y lineal-estocástico. Asimismo, tenemos la posibilidad de modelar la tendencia en diferentes periodos.

4) Modelado de patrones gráficos

Hemos mostrado un ejemplo de uso del script GetCandleTrend para crear los patrones "Pico doble" y "Valle doble".

Los scripts propuestos pueden usarse para crear una historia de precios propia a partir de barras de minuto y ticks, así como para simular y optimizar asesores e indicadores.