
Modelando series temporales con ayuda de símbolos personalizados según las leyes de distribución establecidas
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,
//+------------------------------------------------------------------+ //| 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 CustomSymbolSetString, CustomSymbolSetInteger, CustomSymbolSetDouble. Para obtener las propiedades del símbolo básico, se usan las funciones SymbolInfoString, SymbolInfoInteger, SymbolInfoDouble.
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:
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.
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:
(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:
- BaseOCHL - valor básico para los precios OCHL,
- dOCHL - coeficiente de escala de cambio de los precios OCHL.
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 y 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:
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.
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;
- se forman los valores del volumen y el spread:
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.
- a la hora de apertura de la barra se le asegina el valor temporal de la creación del último tick con el valor de segundos reseteado:
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 tendencia | Fórmula |
---|---|
Lineal | |
Hiperbólico | |
Exponencial | |
De potencia | |
Parabólico | |
Lineal-periódico | |
Lineal.estocástico |
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:
Fig. 3. Parámetros del script GetTrendCandle
Después de ejecutar el script, abrimos el gráfico de minutos del símbolo ExampleCurrency:
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:
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:
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.
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.
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.
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.
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:
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:
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:
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:
Fig. 11. Patrón "Pico doble"
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:
- t1 - 00:00 02.01.2018
- t2 - 13:00 02.01.2018
- t3 - 13:00 03.01.2018
- t4 - 13:00 04.01.2018
- t5 - 00:00 05.01.2018
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
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
- 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
- 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.
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.
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.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4566





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso