
Canal universal con interfaz gráfica
Contenido
- Introducción
- Tipos de línea central
- Tipos de límites
- Clases de cálculo de la amplitud y construcción del canal
- Creando el indicador del canal universal
- Creando las clases de la interfaz gráfica
- Clases de elementos de control de la línea central
- Clases de elementos de control del cálculo de la amplitud
- Clase del formulario
- Creando el indicador y la interfaz gráfica
- Conclusión
- Anexos
Introducción
Ya se escribió en su momento un artículo sobre la creación de un oscilador universal con interfaz gráfica. Como resultado, obtuvimos un indicador muy interesante, cómodo y útil, que simplificaba y aceleraba significativamente el análisis de gráficos. Aparte de los osciladores, existen otros tipos de indicadores de análisis técnico que despiertan en nosotros un interés semejante al de los osciladores. Se trata de los indicadores de tendencia, los indicadores de volatilidad, volúmenes y otros que, a su vez, también pueden dividirse en diferentes categorías. En este artículo analizaremos la creación de un indicador de canal universal.
El artículo del oscilador universal resultó bastante complejo, y se mostró conveniente para los programadores experimentados más que para los principiantes. Puesto que el tema de este artículo se aproxima en cierto grado al de la creación de un oscilador universal, para no hablar por duplicado sobre cuestiones generales ya analizadas, crearemos el canal universal basándonos en el oscilador universal. De esta forma, los programadores principantes también podrán crear sus indicadores universales realizando ciertas mejoras, sin sumergirse del todo en los sutiles matices que abarca la creación de indicadores universales con interfaz gráfica.
A pesar de las semejanzas con el oscilador universal, también nos encontraremos con diferencias significativas. Todos los indicadores de canales están constituidos por tres líneas: una central, una superior y otra inferior. Según su principio de construcción la línea central es idéntica a una media móvil, y en la mayoría de los casos para construir el canal se usa precisamente una media móvil. Las líneas superior e inferior se ubican a la misma distancia de la central. Esta distancia se puede definir simplemente en puntos, en tanto por ciento del precio (indicador Envelopes), se pueden usar valores de desviación estándar (franjas de Bollinger) o los valores del indicador ATR (canal de Keltner). Esto significa que el indicador inicial se construirá usando dos bloques independientes:
- El bloque de cálculo de la línea central
- El bloque de definición de la amplitud del canal (o de construcción de los límites)
Existen canales de un tipo un poco distinto, en concreto, el canal de Donchian (canal de precio). Su creación comienza normalmente con la construcción de las líneas de los límites (diapasón de precio), y ya después de esto se calcula el valor de la línea central (en la mitad del diapasón). Pero este canal se puede construir según el sistema expuesto más arriba: primero se construye la línea central, definida como la mitad del diapasón de precio, y a continuación se acaban de construir los límites a una distancia igual a la mitad del diapasón de precio. Por supuesto, para ello necesitaremos más de lo requerido durante la construcción simple. Puesto que el principal objetivo de este artículo es crear un indicador universal, podemos tolerar ciertas excepciones, tanto más, cuanto este enfoque aumenta el número de combinaciones posibles de la línea central y los límites. Por ejemplo, podremos obtener un indicador con una línea central como la del diapasón de precio, pero cuyos límites estén ubicados a la distancia de la desviación estándar, como sucede con las frnajas de Bollinger, etcétera.
Tipos de línea central
Para la línea central se usarán diferentes medias móviles. Vamos a ver qué tipos hay y cuántos parámetros tienen. Todas las variantes de la línea central que serán usadas en el indicador se muestran en el recuadro 1.
Recuadro 1. Tipos de línea central
Estándar función |
Nombre | Parámetros |
---|---|---|
iAMA | Adaptive Moving Average | 1 int ama_period — periodo AMA 2. int fast_ma_period — periodo de la media rápida 3. int slow_ma_period — periodo de la media lenta 4. int ama_shift — desplazamiento del indicador en horizontal 5. ENUM_APPLIED_PRICE applied_price — tipo de precio o handle |
iDEMA | Double Exponential Moving Average | 1 int ma_period — periodo de promediación 2. int ma_shift — desplazamiento del indicador en horizontal 3. ENUM_APPLIED_PRICE applied_price — tipo de precio |
iFrAMA | Fractal Adaptive Moving Average | 1 int ma_period — periodo de promediación 2. int ma_shift — desplazamiento del indicador en horizontal 3. ENUM_APPLIED_PRICE applied_price — tipo de precio |
iMA | Moving Average | 1 int ma_period — periodo de promediación 2. int ma_shift — desplazamiento del indicador en horizontal 3. ENUM_MA_METHOD ma_method — tipo de suavizado 4. ENUM_APPLIED_PRICE applied_price — tipo de precio |
iTEMA | Triple Exponential Moving Average | 1 int ma_period — periodo de promediación 2. int ma_shift — desplazamiento del indicador en horizontal 3. ENUM_APPLIED_PRICE applied_price — tipo de precio |
iVIDyA | Variable Index Dynamic Average | 1 int cmo_period — periodo Chande Momentum 2. int ema_period — periodo del factor de suavizado 3. int ma_shift — desplazamiento del indicador en horizontal 4. ENUM_APPLIED_PRICE applied_price — tipo de precio |
- | línea central del canal de precio | 1 int period |
Basándonos en el análisis de la columna "Parámetros" del recuadro 1, obtenemos el conjunto mínimo de parámetros necesarios (recuadro 2).
Recuadro 2. Conjunto universal de parámetros para calcular la línea central del canal
Tipo | Nombre |
---|---|
int | period1 |
int | period2 |
int | period3 |
int | shift |
ENUM_MA_METHOD | ma_method |
ENUM_APPLIED_PRICE | price |
Tipos de límites
Asimismo, al igual que sucede con la línea central, aclararemos las variantes de cálculo de los límites del canal (recuadro 3).
Recuadro 3. Variantes de cálculo de la amplitud del canal
Estándar función |
Nombre | Parámetros |
---|---|---|
iATR | Average True Range | 1 int ma_period — periodo de promediación |
iStdDev | Standard Deviation | 1 int ma_period — periodo de promediación 2. int ma_shift — desplazamiento del indicador en horizontal 3. ENUM_MA_METHOD — tipo de suavizado 4. ENUM_APPLIED_PRICE applied_price — tipo de precio |
- | en puntos | int width — amplitud en puntos |
- | en tanto por ciento (como en Envelopes) | double width — amplitud en tanto por ciento del precio |
- | como en el canal de precio | double width — coeficiente de escalado con respecto a la amplitud real del canal de precio |
Basándonos en la columna "Parámetros" del recuadro 3, obtenemos el conjunto de parámetros necesario (recuadro 4).
Recuadro 4. Conjunto universal de parámetros para calcular la amplitud del canal
Tipo | Nombre |
---|---|
int | period |
int | shift |
ENUM_MA_METHOD | ma_method |
ENUM_APPLIED_PRICE | price |
double | width |
Al calcular en puntos, es necesaria la variable int, pero no está en el recuadro 4, ya que en su lugar se puede usar una variable del tipo double. De esta forma, se acorta el número total de variables en la ventana de propiedades.
Los parámetros de cálculo de los límites en la ventana de propiedades del indicador tendrán los prefijos "w_".
Clases de la línea central
La clase básica de la línea central es idéntica en cuanto a su principio y conjunto de métodos a la clase CUniOsc del artículo sobre el oscilador universal con interfaz gráfica, por eso la tomaremos como base y la modificaremos un poco.
En la carpeta MQL5/Include crearemos la carpeta UniChannel, copiaremos en ella el archivo CUniOsc.mqh (de la carpeta Include/UniOsc) y la renombraremos como CUniChannel.mqh. Dejaremos en el archivo la clase básica (COscUni), la clase derivada Calculate1 (nombre completo COscUni_Calculate1) y su clase derivada COscUni_ATR, el resto de las clases las eliminaremos.
Renombramos las clases: sustituimos el fragmento "COscUni" por "CChannelUni". En la sustitución es cómodo usar la función del editor (Menú principal — Editar — Buscar y reemplazar — Reemplazar), pero no utilizando el botón "Reemplazar todo", sino reemplazando uno a uno, para controlar el proceso y estar seguros de que todos los reemplazos se realizan en el lugar necesario.
La clase de la línea central siempre dibuja una línea continua, por eso muchos métodos de la clase básica no son necesarios. Después de eliminar los métodos innecesarios, solo queda la siguiente clase básica:
protected:
int m_handle; // manejador del indicador
string m_name; // nombre del indicador
string m_label1; // nombre del búfer 1
string m_help; // pequeña guía de los parámetros del indicador
double m_width; // amplitud del canal
public:
// constructor
void CChannelUniWidth(){
m_handle=INVALID_HANDLE;
}
// destructor
void ~CChannelUniWidth(){
if(m_handle!=INVALID_HANDLE){
IndicatorRelease(m_handle);
}
}
// método principal, llamado desde la función OnCalculate() del indicador
virtual int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
return(rates_total);
}
// obteniendo el manejador del indicador cargado
int Handle(){
return(m_handle);
}
// método de comprobación del manejador, para saber si se ha cargado el indicador
bool CheckHandle(){
return(m_handle!=INVALID_HANDLE);
}
// obteniendo el nombre del indicador
string Name(){
return(m_name);
}
// obteniendo el texto del rótulo para los búferes
string Label1(){
return(m_label1);
}
// obteniendo las sugerencias de los parámetros
string Help(){
return(m_help);
}
};
Desde la clase Calculate se puede eliminar todo lo que respecta al segundo búfer, como resultado, en la clase se quedará solo el método Calculate:
public:
// método principal, llamado desde la función OnCalculate() del indicador
// los primeros dos parámetros son análogos a los dos primeros parámetros
// de la función OnCalculate() del indicador
// el tercer parámetro es el búfer de indicador para la línea central
int Calculate( const int rates_total,
const int prev_calculated,
double & buffer0[]
){
// definiendo el número de elementos a copiar
int cnt;
if(prev_calculated==0){
cnt=rates_total;
}
else{
cnt=rates_total-prev_calculated+1;
}
// copiando los datos al búfer de indicador
if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
return(0);
}
return(rates_total);
}
};
Escribiremos la clase derivada que usa el indicador iMA. La clase en el archivo CChannelUni_ATR la renombramos a CChannelUni_MA, sustituimos en ella el indicador llamado y eliminamos los elementos sobrantes. Como resultado, obtenemos la siguiente clase:
public:
// constructor
// los dos primeros parámetros, idénticos para todas las clases derivadas
// a continuación, los parámetros del indicador cargado
void CChannelUni_MA( bool use_default,
bool keep_previous,
int & ma_period,
int & ma_shift,
long & ma_method,
long & ma_price){
if(use_default){ // se ha elegido el uso de valores por defecto
if(keep_previous){
// no cambiar los parámetros usados con anterioridad
if(ma_period==-1)ma_period=14;
if(ma_shift==-1)ma_shift=0;
if(ma_method==-1)ma_method=MODE_SMA;
if(ma_price==-1)ma_price=PRICE_CLOSE;
}
else{
ma_period=14;
ma_shift=0;
ma_method=MODE_SMA;
ma_price=PRICE_CLOSE;
}
}
// carga del indicador
m_handle=iMA(Symbol(),Period(),ma_period,ma_shift,(ENUM_MA_METHOD)ma_method,(ENUM_APPLIED_PRICE)ma_price);
// formando la línea con el nombre del indicador
m_name=StringFormat( "iMA(%i,%i,%s,%s)",
ma_period,
ma_shift,
EnumToString((ENUM_MA_METHOD)ma_method),
EnumToString((ENUM_APPLIED_PRICE)ma_price)
);
// línea para el nombre del búfer
m_label1=m_name;
// sugerencia sobre los parámetros
m_help=StringFormat( "ma_period - c_Period1(%i), "+
"ma_shift - c_Shift(%i), "+
"ma_method - c_Method(%s)"+
"ma_price - c_Price(%s)",
ma_period,
ma_shift,
EnumToString((ENUM_MA_METHOD)ma_method),
EnumToString((ENUM_APPLIED_PRICE)ma_price)
);
}
};
Analicemos en mayor profundidad la formación de las líneas en las variables m_name y mlabel1. El nombre del indicador (variable m_name) de los indcadores ubicados en la subventana se hace visible en la esquina superior izquierda de la subventana. Puesto que el canal se representará en el gráfico de precios, su nombre será visible, por eso asignaremos a la variable m_label exactamente el mismo nombre que a la variable m_name, para que al colocar el cursor sobre el canal central, en la sugerencia emergente se vean todos sus parámetros.
Asimismo, como ocurre con la clase para el indicador iMA, se crean también las clases para todos los demás indicadores estándar. La excepción será el canal de precio. Puesto que el canal de precio no está presente entre los indicadores estándar del terminal, es imprescindible calcularlo. Aquí tenemos dos opciones:
- Crear una clase derivada del tipo Calculate y realizar los cálculos en ella
- Escribir un indicador adicional y llamarlo a través de la función iCustom
En los anexos al artículo se encuentra el archivo CUniChannel.mqh con las clases derivadas para todos los demás indicadores y el indicador iPriceChannel. En el indicador iPriceChannel los datos de la línea central se ubican en el búfer 0. Si alguien va a trabajar de forma adicional con la clase para algún otro indicador, cuyos datos necesarios no se ubiquen el búfer 0, entonces tendrá que crear otra clase derivada Calculate, o crear en la clase básica una variable para el índice del búfer y establecer en el constructor de la clase derivada el valor que necesite.
Clases de cálculo de la amplitud y construcción del canal
Como base de la clase básica tomaremos de nuevo CUniChannel. Al método Calculate de la clase se le transmitirá el búfer de indicador con los valores de la línea central, dos búferes para los límites del canal, que se rellenarán con los valores calculados en el método. Aquí, a diferencia de CUniChannel, para cada variante del cálculo de los límites existirán sus propias clases derivadas Calculate, que cargarán los indicadores, y en las que se formarán los nombres del indicador y de los búferes. También será necesario perfeccionar la clase básica: añadir la variable para la amplitud del canal, el valor de la variable se establecerá a través del constructor de la clase básica.
Guardamos el archivo CUniChannel.mqh con el nombre CUniChannelWidth.mqh e introducimos los cambios en él. Primero eliminamos todas las clases derivadas, dejando solo la clase básica Calculate. Renombramos la clase de CChannelUni a CChannelUniWidth (no olvidemos el constructor, el destructor y el nombre de la clase padre de la clase derivada, que también debemos modificar). Obtenemos la clase siguiente:
protected:
int m_handle; // manejador del indicador
string m_name; // nombre del indicador
string m_label1; // nombre del búfer 1
string m_help; // pequeña guía de los parámetros del indicador
double m_width; // amplitud del canal
public:
// constructor
void CChannelUniWidth(){
m_handle=INVALID_HANDLE;
}
// destructor
void ~CChannelUniWidth(){
if(m_handle!=INVALID_HANDLE){
IndicatorRelease(m_handle);
}
}
// método principal, llamado desde la función OnCalculate() del indicador
virtual int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
return(rates_total);
}
// obteniendo el manejador del indicador cargado
int Handle(){
return(m_handle);
}
// método de comprobación del manejador, para saber si se ha cargado el indicador
bool CheckHandle(){
return(m_handle!=INVALID_HANDLE);
}
// obteniendo el nombre del indicador
string Name(){
return(m_name);
}
// obteniendo el texto del rótulo para los búferes
string Label1(){
return(m_label1);
}
// obteniendo las sugerencias de los parámetros
string Help(){
return(m_help);
}
};
La clase CChannelUni_Calculate la renombramos a CChannelUni_Calculate_ATR y le añadimos un constructor. El constructor se puede tomar de la clase COscUni_ATR del oscilador universal, pero deberemos renombrarla y añadir el parámetro de amplitud. Necesitaremos otros cambios: es necesario añadir la formación de nombres del indicador y los búferes. Con respecto a la clase para el cálculo de los límites basada en el indicador ATR tendrá el aspecto siguiente:
public:
// constructor
// los primeros dos parámetros son estándar para todas las clases derivadas
// después vienen los parámetros del indicador cargado
// el último parámetro es la amplitud del canal
void CChannelUni_Calculate_ATR(bool use_default,
bool keep_previous,
int & ma_period,
double & ch_width){
if(use_default){ // se ha elegido el uso de valores por defecto
if(keep_previous){ // no cambiar los parámetros usados con anterioridad
if(ma_period==-1)ma_period=14;
if(ch_width==-1)ch_width=2;
}
else{
ma_period=14;
ch_width=2;
}
}
// guardando el parámetro de amplitud para usarlo en el método de cálculo
m_width=ch_width;
// carga del indicador
m_handle=iATR(Symbol(),Period(),ma_period);
// formando la línea con el nombre del indicador
m_name=StringFormat("ATR(%i)",ma_period);
// línea con el nombre del búfer
m_label1=m_name;
// sugerencia sobre los parámetros
m_help=StringFormat("ma_period - Period1(%i)",ma_period); // sugerencia
}
// método principal, llamado desde la función OnCalculate() del indicador
// los primeros dos parámetros corresponden a los dos primeros parámetros
// función OnCalculate()
// a continuación, se transmiten los búferes de indicador
int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
// definiendo el comienzo del cálculo
int start;
if(prev_calculated==0){
start=0;
}
else{
start=prev_calculated-1;
}
// ciclo principal de cálculo y rellenado de búferes
for(int i=start;i<rates_total;i++){
// obteniendo los datos del indicador para la barra calculada
double tmp[1];
if(CopyBuffer(m_handle,0,rates_total-i-1,1,tmp)<=0){
return(0);
}
// multiplicamos por el parámetro de amplitud
tmp[0]*=m_width;
// calculamos los valores del límite superior e inferior
bufferUpper[i]=bufferCentral[i]+tmp[0];
bufferLower[i]=bufferCentral[i]-tmp[0];
}
return(rates_total);
}
};
Preste atención a que el valor del indicador ATR se copia dentro del ciclo principal solo para una barra. Esta variante, por supuesto, es sustancialmente más lenta que el copiado de una serie de valores al búfer. Sin embargo, con este enfoque se ahorra un búfer completo, y la pérdida de velocidad podría corregirse fijando manualmente el indicador al gráfico. Pero un retraso de varias décimas de segundo no será relevante para el usuario. En el simulador, al inicio de la prueba en el gráfico hay una pequeña cantidad de barras, por eso la pérdida de tiempo en la copia de datos para cada barra por separado no será significativa.
Ciertas variantes de cálculo de la amplitud del canal no exigen del uso de indicadores adicionales, en particular, al usar una magnitud establecida en puntos o como en el indicador Envelope. En este caso, a la variable m_handle de la clase básica le asignamos el valor 0 (que se diferencia del valor INVALID_HANDLE).
En los anexos al artículo se encuentra completamente listo el archivo CUniChannelWidth.mqh con las clases derivadas para todas las variantes del cálculo del canal.
Creando el indicador del canal universal
Ahora, podemos crear el indicador del canal universal ubicando las clases creadas más arriba, pero sin interfaz gráfica, por el momento.
En el editor creamos un nuevo indicador de usuario con el nombre iUniChannel. Al crear un indicador en el editor MQL, elegimos las funciones: OnCalculate(...,open,high,low,close), OnTimer, OnChartEvent, creamos tres búferes del tipo Line.
Para elegir el tipo de la línea central y el tipo del canal es necesario crear dos enumeraciones. Estas se ubicarán en el archivo UniChannelDefines.mqh. Creamos las enumeraciones de acuerdo con los recuadros 1 y 3:
enum ECType{
UniCh_C_AMA,
UniCh_C_DEMA,
UniCh_C_FrAMA,
UniCh_C_MA,
UniCh_C_TEMA,
UniCh_C_VIDyA,
UniCh_C_PrCh
};
// enumeración de los tipos de los límites
enum EWType{
UniCh_W_ATR,
UniCh_W_StdDev,
UniCh_W_Points,
UniCh_W_Percents,
UniCh_W_PrCh
};
La enumeración de los tipos de línea central tiene el nombre ECType, y la enumeración de los tipos de la amplitud del canal tiene el nombre EWType. Conectamos al indicador el archivo con la enumeración y los dos archivos creados anteriormente con las clases:
#include <UniChannel/CUniChannel.mqh>
#include <UniChannel/CUniChannelWidth.mqh>
Declaramos las dos variables externas para elegir los tipos de la línea central y la amplitud del canal y las variables para los parámetros de acuerdo con los recuadros 2 y 4:
input ECType CentralType = UniCh_C_MA;
input int c_Period1 = 5;
input int c_Period2 = 10;
input int c_Period3 = 15;
input int c_Shift = 0;
input ENUM_MA_METHOD c_Method = MODE_SMA;
input ENUM_APPLIED_PRICE c_Price = PRICE_CLOSE;
// parámetros de los límites
input EWType WidthType = UniCh_W_StdDev;
input int w_Period = 20;
input int w_Shift = 0;
input ENUM_MA_METHOD w_Method = MODE_SMA;
input ENUM_APPLIED_PRICE w_Price = PRICE_CLOSE;
input double w_Width = 2.0;
Declaramos dos variables, que por ahora serán internas, pero en la versión con la interfaz gráfica se mostrarán en la ventana de propiedades:
bool KeepPrev = false;
La asignación de estas variables se describe en el artículo sobre el oscilador universal: con la variable UseDefault se activa un modo con el que cada indicador elegido de nuevo se carga con los parámetros por defecto, con la variable KeepPrev se activa el modo de guardado de los valores de los parámetros al cambiar los indicadores. En la versión del indicador sin interfaz gráfica, el indicador se carga con los parámetros de la ventana de propiedades, por eso el valor UseDefault es igual a false. Para la variable KeepPrev también se establece false, puesto que la interfaz gráfica por ahora está ausente y no hay ninguna conmuntación de los indicadores.
Al inicializar el indicador es necesario preparar los parámetros. Asimismo, al igual que en el oscilador universal, ejecutamos la preparación de los parámetros en una función aparte con el nombre PrepareParameters(), pero primero hacemos una copia de todos los parámetros externos:
int _ma_Period1;
int _ma_Period2;
int _ma_Period3;
int _ma_Shift;
long _ma_Method;
long _ma_Price;
EWType _WidthType;
int _w_Period;
int _w_Shift;
long _w_Method;
long _w_Price;
double _w_Width;
A continuación, escribimos la función de preparación de los parámetros:
_CentralType=CentralType;
_WidthType=WidthType;
if(UseDefault && KeepPrev){
_c_Period1=-1;
_c_Period2=-1;
_c_Period3=-1;
_c_Shift=0;
_c_Method=-1;
_c_Price=-1;
_w_Period=-1;
_w_Shift=0;
_w_Method=-1;
_w_Price=-1;
_w_Width=-1;
}
else{
_c_Period1=c_Period1;
_c_Period2=c_Period2;
_c_Period3=c_Period3;
_c_Shift=c_Shift;
_c_Method=c_Method;
_c_Price=c_Price;
_w_Period=w_Period;
_w_Shift=w_Shift;
_w_Method=w_Method;
_w_Price=w_Price;
_w_Width=w_Width;
}
}
Noten que al cumplirse la condición UseDefault && KeepPrev, a todas las variables se les asigna el valor -1, y a las variables Shift el valor 0, porque los valores de estas variables no se establecen desde los objetos de los indicadores, sino solo desde la interfaz de usuario (de la ventana de propiedades o de la interfaz gráfica).
Después de preparar los parámetros se pueden crear los objetos para el cálculo de la línea central y del canal. En el oscilador universal para esto se tenía la función LoadOscillator(). Aquí tendremos dos funciones: LoadCentral() y LoadWidth(), pero primero declararemos las variables-puntero:
CChannelUniWidth * width;
En ciertos indicadores existe un parámetro de desplazamiento en horizontal (shift), pero en otros indicadores no existe, aunque se pueden desplazar todos los indicadores. Por eso, declararemos la variable adicional shift0 con el valor 0, y la transmitiremos a los constructores de las clases. El desplazamiento se ejecutará a cuenta del desplazamiento de los búferes de indicador.
Función LoadCentral():
switch(_CentralType){ // dependiendo del tipo elegido, se crea la clase correspondiente
case UniCh_C_AMA:
central=new CChannelUni_AMA( UseDefault,
KeepPrev,
_c_Period1,
_c_Period2,
_c_Period3,
shift0,
_c_Price);
break;
case UniCh_C_DEMA:
central=new CChannelUni_DEMA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_FrAMA:
central=new CChannelUni_FrAMA(UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_MA:
central=new CChannelUni_MA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Method,
_c_Price);
break;
case UniCh_C_TEMA:
central=new CChannelUni_TEMA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_VIDyA:
central=new CChannelUni_VIDyA(UseDefault,
KeepPrev,
_c_Period1,
_c_Period2,
shift0,
_c_Price);
break;
case UniCh_C_PrCh:
central=new CChannelUni_PriceChannel( UseDefault,
KeepPrev,
_c_Period1);
break;
}
}
En una de las variantes de cálculo de la amplitud del canal (clase CChannelUni_Calculate_InPoints) existe un parámetro que se mide en puntos, mientras que en la clase se prevé la adaptación del valor de este parámetro de acuerdo con el signo de cantidad tras la coma en las cotizaciones. Para que la función de adaptación funcione, al crear un objeto se debe transmitir al constructor de la clase el multiplicador del parámetro. En las cotizaciones con 2 y 4 dígitos el valor del multiplicador será igual a 1, y para las de 3 y 5 dígitos, igual 10. En los parámetros externos declaramos la variable Auto5Digits del tipo bool:
Si Auto5Digits es igual a true, se ejecutará la corrección del parámetro, si es false, el valor se usará como está. Un poco más abajo de Auto5Digits declaramos otra variable para el multiplicador:
Al principio de la función OnInit() calculamos el valor mult:
mult=10; // multiplicaremos los parámetros medidos en puntos por 10
}
else{
mult=1; // los parámetros que se midan en puntos se quedan sin cambios
}
Ahora escribimos la función LoadWidth():
switch(_WidthType){ // dependiendo del tipo elegido, se crea la clase correspondiente
case UniCh_W_ATR:
width=new CChannelUni_Calculate_ATR(UseDefault,KeepPrev,_w_Period,_w_Width);
break;
case UniCh_W_StdDev:
width=new CChannelUni_Calculate_StdDev(UseDefault,KeepPrev,_w_Period,shift0,_w_Method,_w_Price,_w_Width);
break;
case UniCh_W_Points:
width=new CChannelUni_Calculate_InPoints(UseDefault,KeepPrev,_w_Width,mult);
break;
case UniCh_W_Percents:
width=new CChannelUni_Calculate_Envelopes(UseDefault,KeepPrev,_w_Width);
break;
case UniCh_W_PrCh:
width=new CChannelUni_Calculate_PriceChannel(UseDefault,KeepPrev,_w_Period,_w_Width);
break;
}
}
Después de crear cada uno de los objetos (de la línea central y la amplitud), comprobamos si se han creado con éxito. Si los objetos han sido creados, establecemos el nombre corto del indicador y a través de la función Print() mostramos la sugerencia de los parámetros:
Print("Width parameters matching:",width.Help());
El ajuste de los rótulos y el desplazamiento para los búferes se ejecutarán en la función SetStyles():
// nombres de los búferes
PlotIndexSetString(0,PLOT_LABEL,"Central: "+central.Label1());
PlotIndexSetString(1,PLOT_LABEL,"Upper: "+width.Label1());
PlotIndexSetString(2,PLOT_LABEL,"Lower: "+width.Label1());
// desplazamiento de los búferes
PlotIndexSetInteger(0,PLOT_SHIFT,_c_Shift);
PlotIndexSetInteger(1,PLOT_SHIFT,_w_Shift);
PlotIndexSetInteger(2,PLOT_SHIFT,_w_Shift);
}
En total, obtenemos la siguiente función OnInit():
// preparando el multiplicador para la corrección del parámetro, medido en puntos
if(Auto5Digits && (Digits()==3 || Digits()==5)){
mult=10;
}
else{
mult=1;
}
// preparando los parámetros
PrepareParameters();
// carga del indicador de la línea central
LoadCentral();
// comprobando si la carga de la línea central ha tenido éxito
if(!central.CheckHandle()){
Alert("Central line error "+central.Name());
return(INIT_FAILED);
}
// carga del indicador del cálculo de la amplitud
LoadWidth();
// comprobando si la carga del indicador de amplitud ha tenido éxito
if(!width.CheckHandle()){
Alert("Width error "+width.Name());
return(INIT_FAILED);
}
// mostrar las sugerencias de los parámetros
Print("Central line parameters matching: "+central.Help());
Print("Width parameters matching: "+width.Help());
// estableciendo el nombre
ShortName="iUniChannel";
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
// parte estándar de la función OnInit
SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);
SetIndexBuffer(2,Label3Buffer,INDICATOR_DATA);
// Estableciendo los rótulos y los desplazamientos de los búferes
SetStyles();
return(INIT_SUCCEEDED);
}
Una vez hecho esto, podemos considerar que el indicador sin interfaz gráfica está completamente listo. Con su ayuda se puede poner a prueba el funcionamiento de todas las clases y parámetros, y después de ello proceder a la creación de la interfzaz gráfica.
Durante la simulación del indicador se han detectado varias peculiaridades incómodas. Una de las características incómodas consiste en el control por separado del periodo de la línea central y del periodo de cálculo de la amplitud del canal. En sí, este tipo de control, por supuesto, amplía las posibilidades del indicador, pero en ciertos casos puede ser necesario el control simultáneo de ambos periodos con la ayuda de un solo parámetro. Vamos a mejorar un poco este elemento de forma que el periodo de la amplitud del canal sea igual a uno de los tres periodos de la línea central. La elección de una de las cuatro variantes se realizará a través de una enumeración (se ubica en el archivo UniChannelDefines.mqh):
LockTo_Off,
LockTo_Period1,
LockTo_Period2,
LockTo_Period3
};
Al elegir la variante LockTo_Off, los periodos se regulan por separado, y en el resto de los casos, el valor del parámetro w_Period es igual al periodo correspondiente de la línea central. Declaramos una variable del tipo ELockTo justo después de la variable w_Period:
Mejoramos la función PrepareParameters() añadiéndole al final del todo el siguiente código:
case LockTo_Period1:
_w_Period=_c_Period1;
break;
case LockTo_Period2:
_w_Period=_c_Period2;
break;
case LockTo_Period3:
_w_Period=_c_Period3;
break;
}
Otra incomodidad consiste en que los mensajes informativos sobre la correspondencia de los parámetros se muestran en una línea en la pestaña "Expertos", y en las pantallas estrechas, parte de la línea se sale de los límites. Vamos a realizar unas modificaciones para mostrar la línea en forma de columna. En lugar de la función Print, utilizaremos nuestra propia función PrintColl(). A esta función se le transmiten dos parámetros: el encabezamiento y la línea con la sugerencia. En la función, la línea con la sugerencia se divide y se muestra por partes:
Print(caption); // muestra del encabezamiento
string res[];
// dividimos el mensaje
int cnt=StringSplit(message,',',res);
// mostrar el mensaje por partes
for(int i=0;i<cnt;i++){
StringTrimLeft(res[i]);
Print(res[i]);
}
}
Por consisguiente, en la función OnInit() se cambian dos líneas de muestra de sugerencias:
PrintColl("Width parameters matching:",width.Help());
Ahora el indicador está completamente listo, el nombre del archivo está en la aplicación — "iUniChanhel". Vamos a proceder a la creación de la interfaz.
Creando las clases de la interfaz gráfica
La interfaz gráfica se creará usando como base la interfaz gráfica del oscilador universal. Copiamos el archivo UniOsc/UniOscGUI.mqh en la carpeta UniChannel y lo renombramos a UniChannelGUI.mqh. La interfaz gráfica del canal universal se diferenciará sustancialmente de la interfaz del oscilador universal, así que tendremos que trabajar bastante.
La principal diferencia reside en que en el canal universal se realiza la elección independiente de los dos indicadores (el de la línea central y el de los indicadores), por eso deberá haber dos listas principales de elección del tipo de indicador. Después de la primera lista, deberán ubicarse los elementos de control de los parámetros de la línea central, y a continuación ubicarse la segunda lista y los elementos de control de los parámetros de los límites. Eso significa que la segunda lista no tiene coordenadas fijas, estas deberán ser calculadas. Aparte de las dos listas para elegir los tipos, en el formulario siempre deberán existir dos campos para introducir los valores de desplazamiento, sus coordenadas tampoco son fijas. Otro momento al que hay que prestar atención es la lista para elegir la variante que corresponde al parámetro w_LockPeriod. En todos los casos en los que hay que representar el campo de edición del parámetro w_Period, en el grupo de elementos de control de los parámetros de amplitud es necesario representar una lista desplegable adicional.
Primero en el archivo UniChannelGUI.mqh ejecutamos los cambios generales:
1 La ruta al archivo con las enumeraciones:
deberemos cambiarla a la siguiente línea:
2. Añadimos la matriz con los valores de la enumeración ELockTo:
3. Eliminamos las matrices con las enumeraciones ENUM_APPLIED_VOLUME y ENUM_STO_PRICE.
Ahora procedemos a la modificación de la clase CUniOscControls.
Clase de los elementos de control de la línea central
1 La clase CUniOscControls la renombramos a CUniChannelCentralControls.
2. En la clase eliminamos la declaración de las variables m_volume y m_sto_price. Por consiguiente, eliminamos todo lo relacionado con estos elementos de control de los métodos SetPointers(), Hide(), Events().
3. Añadimos la variable m_last_y, en ella se fijará la coordenada Y del último elemento de control del grupo. Añadimos el método para obtener el valor de esta variable — GetLastY(). El método FormHeight() se hace innecesario, así que lo eliminamos, pero en su lugar añadimos el método ControlsCount(), que retornará el número de elementos de control en la clase derivada. Esta cantidad será necesaria a la hora de calcular la altura del formulario.
Como resultado, obtenemos la siguiente clase padre:
protected:
CSpinInputBox * m_value1; // para el periodo 1
CSpinInputBox * m_value2; // para el periodo 2
CSpinInputBox * m_value3; // para el periodo 3
CComBox * m_price; // para el precio
CComBox * m_method; // para el método
int m_last_y; // posición Y del último elemento de control
public:
// obtener la posición Y del último elemento de control
int GetLastY(){
return(m_last_y);
}
// método para transmitir al objeto los punteros al objeto
void SetPointers(CSpinInputBox & value1,
CSpinInputBox & value2,
CSpinInputBox & value3,
CComBox & price,
CComBox & method){
m_value1=GetPointer(value1);
m_value2=GetPointer(value2);
m_value3=GetPointer(value3);
m_price=GetPointer(price);
m_method=GetPointer(method);
}
// grupos ocultos de elementos de control
void Hide(){
m_value1.Hide();
m_value2.Hide();
m_value3.Hide();
m_price.Hide();
m_method.Hide();
}
// procesando los eventos
int Event(int id,long lparam,double dparam,string sparam){
int e1=m_value1.Event(id,lparam,dparam,sparam);
int e2=m_value2.Event(id,lparam,dparam,sparam);
int e3=m_value3.Event(id,lparam,dparam,sparam);
int e4=m_price.Event(id,lparam,dparam,sparam);
int e5=m_method.Event(id,lparam,dparam,sparam);
if(e1!=0 || e2!=0 || e3!=0 || e4!=0 || e5!=0){
return(1);
}
return(0);
}
// método de inicialización de los elementos de control (para cambiar los rótulos)
virtual void InitControls(){
}
// representación del grupo de elementos de control
virtual void Show(int x,int y){
}
// obteniendo el número de elementos de control en el grupo
virtual int ControlsCount(){
return(0);
}
};
Cambiamos la clase derivada CUniOscControls_ATR:
1 La renombramos a CUniChannelCentralControls_AMA, esta será la clase para el indicador AMA.
2. De acuerdo con la columna "Parámetros" del recuadro 1, inicializamos los elementos de control en el método InitControls(), y en el método Show() llamamos los métodos Show() de todos los elementos de control. A la variable m_last_y le asignamos el valor del último elemento de control.
3. Eliminamos el método FormHeight(), en su lugar añadimos el método ControlsCount().
Obtenemos esta clase:
void InitControls(){
// inicializando los elementos de control
m_value1.Init("c_value1",SPIN_BOX_WIDTH,1," ama_period");
m_value2.Init("c_value2",SPIN_BOX_WIDTH,1," fast_ma_period");
m_value3.Init("c_value3",SPIN_BOX_WIDTH,1," slow_ma_period");
}
// representación
void Show(int x,int y){
m_value1.Show(x,y);
y+=20;
m_value2.Show(x,y);
y+=20;
m_value3.Show(x,y);
y+=20;
m_price.Show(x,y);
m_last_y=y;
}
// obteniendo el número de elementos en el grupo
int ControlsCount(){
return(4);
}
};
De forma análoga creamos las clases para todos los demás indicadores de la línea central y eliminamos todas las clases derivadas del oscilador.
Clases de elementos de control del cálculo de la amplitud
Basándonos en la clase CUniChannelCentralControls obtenida, creamos la clase para controlar los parámetros de la amplitud del canal. Hacemos una copia de la clase CUniChannelCentralControls, la renombramos a CUniChannelWidthControls. En esta clase serán necesarios dos campos de edición (periodo y amplitud), dos enumeraciones estándar para el tipo de promediación y el precio, así como la enumeración del parámetro w_LockPeriod. Como resultado de los cambios, obtenemos la clase siguiente:
protected:
CSpinInputBox * m_value1; // para el periodo
CSpinInputBox * m_value2; // para la amplitud
CComBox * m_price; // para el precio
CComBox * m_method; // para el método
CComBox * m_lockto; // para el tipo de bloqueo
int m_last_y; // posición Y del último elemento de control
public:
// obtener la posición Y del último elemento de control
int GetLastY(){
return(m_last_y);
}
// método para transmitir al objeto los punteros al objeto
void SetPointers(CSpinInputBox & value1,
CSpinInputBox & value2,
CComBox & price,
CComBox & method,
CComBox & lockto){
m_value1=GetPointer(value1);
m_value2=GetPointer(value2);
m_price=GetPointer(price);
m_method=GetPointer(method);
m_lockto=GetPointer(lockto);
}
// grupos ocultos de elementos de control
void Hide(){
m_value1.Hide();
m_value2.Hide();
m_price.Hide();
m_method.Hide();
}
// procesando los eventos
int Event(int id,long lparam,double dparam,string sparam){
int e1=m_value1.Event(id,lparam,dparam,sparam);
int e2=m_value2.Event(id,lparam,dparam,sparam);
int e4=m_price.Event(id,lparam,dparam,sparam);
int e5=m_method.Event(id,lparam,dparam,sparam);
int e6=m_lockto.Event(id,lparam,dparam,sparam);
if(e1!=0 || e2!=0 || e4!=0 || e5!=0 || e6){
return(1);
}
return(0);
}
// método de inicialización de los elementos de control (para cambiar los rótulos)
virtual void InitControls(){
}
// representación del grupo de elementos de control
virtual void Show(int x,int y){
}
// obteniendo el número de elementos de control en el grupo
virtual int ControlsCount(){
return(0);
}
};
Creamos para ella las clases derivadas. La principipal diferencia de la clase de la línea central reside en que, tras el campo de edición del perido, es necesario crear una lista desplegable para el parámetro w_LockPeriod. Obtenemos esta clase para calcular la amplitud de ATR:
void InitControls(){
// inicializando el elemento de control
m_value1.Init("w_value1",SPIN_BOX_WIDTH,1," period");
}
// representando los grupos de elementos de control
void Show(int x,int y){
m_value1.Show(x,y);
y+=20;
m_lockto.Show(x,y);
m_last_y=y;
}
// obteniendo el número de elementos de control en el grupo
int ControlsCount(){
return(2);
}
};
De forma análoga han sido creadas para el resto de las variantes del cálculo de la amplitud del canal.
Ahora en el archivo UniChannelGUI.mqh se encuentran dos clases básicas de elementos de control, multitud de sus clases derivadas y la clase del formulario. Con la última tendremos que trabajar un poco. Debido al considerable tamaño del archivo, el trabajo posterior con el mismo puede resultar incómodo, por eso introduciremos las clases de los elementos de control en otros archivos. Creamos el archivo UniChannel/CUniChannelCentralControls.mqh y trasladamos a él la clase CUniChannelCentralControls y todas sus clases derivadas e incluimos los archivos adicionales:
#include <UniChannel/UniChannelDefines.mqh>
La definición de las constantes FORM_WIDTH, SPIN_BOX_WIDTH, COMBO_BOX_WIDTH la trasladamos al archivo UniChannelDefines.mqh. Después de ello, el archivo CUniChannelCentralControls se puede compilar para comprobar los archivos. De la misma forma trasladaremos la clase CUniChannelWidthControls a un archivo aparte. Después de ello, será cómodo trabajar con la clase del formulario.
Clase del formulario
Incluimos en el archivo UniChannelGUI.mqh los dos archivos recién creados:
#include <UniChannel/CUniChannelWidthControls.mqh>
Renombramos la clase CUniOscForm a CUniChannelForm. En la sección public eliminamos la variable-puntero del tipo CUniOscControls, en lugar de ella, declaramos otras dos variables-puntero: CUniChannelCentralControls y CUniChannelWidthControls, resolvemos lo concerniente a los otros elementos de control de la clase del formulario. En conclusión, en la sección public ubicamos las siguientes variables:
CSpinInputBox m_c_value1; // campo de edición del periodo 1
CSpinInputBox m_c_value2; // campo de edición del periodo 2
CSpinInputBox m_c_value3; // campo de edición del periodo 3
CComBox m_c_price; // lista de elección del precio
CComBox m_c_method; // lista de elección del método
CSpinInputBox m_c_shift; // campo de edición del desplazamiento
CComBox m_w_cmb_main; // lista de elección de los límites
CSpinInputBox m_w_value1; // campo de edición del periodo
CSpinInputBox m_w_value2; // campo edición de la amplitud
CComBox m_w_price; // lista de elección del precio
CComBox m_w_method; // lista de elección del método
CComBox m_w_lockto; // lista de elección de la variedad de bloqueo
CSpinInputBox m_w_shift; // campo de edición del desplazamiento
// grupo de control de los elementos de la línea central
CUniChannelCentralControls * m_central_controls;
// grupo de control de los elementos de los límites
CUniChannelWidthControls * m_width_controls;
En el método MainProperties() cambiamos los valores de las variables m_Name y m_Caption, el resto de las variables permanecerán sin modificaciones:
m_Name = "UniChannelForm";
m_Width = FORM_WIDTH;
m_Height = 150;
m_Type = 0;
m_Caption = "UniChannel";
m_Movable = true;
m_Resizable = true;
m_CloseButton = true;
}
En el método OnInitEvent() llamamos los métodos Init() de todos los elementos de control de los que no se cambian los rótulos (el conjunto de elementos de control correspondiente al indicador elegido) y rellenamos las listas desplegables:
// inicializando los elementos de control que no entran en el grupo
m_c_cmb_main.Init("cb_c_main",COMBO_BOX_WIDTH," select central");
m_w_cmb_main.Init("cb_w_main",COMBO_BOX_WIDTH," select bands");
m_c_price.Init("c_price",COMBO_BOX_WIDTH," price");
m_c_method.Init("c_method",COMBO_BOX_WIDTH," method");
m_c_shift.Init("c_shift",COMBO_BOX_WIDTH,1," shift");
m_w_price.Init("w_price",COMBO_BOX_WIDTH," price");
m_w_method.Init("w_method",COMBO_BOX_WIDTH," method");
m_w_shift.Init("w_shift",COMBO_BOX_WIDTH,1," shift");
m_w_lockto.Init("cb_w_lockto",COMBO_BOX_WIDTH," lock period");
m_w_value2.Init("w_value2",SPIN_BOX_WIDTH,0.001," width");
// rellenando las listas desplegables
for(int i=0;i<ArraySize(e_price);i++){
m_c_price.AddItem(EnumToString(e_price[i]));
m_w_price.AddItem(EnumToString(e_price[i]));
}
for(int i=0;i<ArraySize(e_method);i++){
m_c_method.AddItem(EnumToString(e_method[i]));
m_w_method.AddItem(EnumToString(e_method[i]));
}
for(int i=0;i<ArraySize(e_lockto);i++){
m_w_lockto.AddItem(EnumToString(e_lockto[i]));
}
// permitir la edición de desplazamientos desde el teclado
m_c_shift.SetReadOnly(false);
m_w_shift.SetReadOnly(false);
}
En los métodos OnShowEvent() representamos los elementos de control, además, después de representar un grupo de elementos, obtenemos la coordenada Y, y de acuerdo con esta, representamos con ella los siguientes elementos de control:
m_c_cmb_main.Show(aLeft+10,aTop+10); // lista de elección del tipo de línea central
m_central_controls.Show(aLeft+10,aTop+30); // grupo de elementos de control de los parámetros de la línea central
int m_y=m_central_controls.GetLastY(); // obteniendo las coordenadas del último elemento de control
m_c_shift.Show(aLeft+10,m_y+20); // campo de edición del parámetro de desplazamiento
m_w_cmb_main.Show(aLeft+10,m_y+40); // lista de elección del canal
m_width_controls.Show(aLeft+10,m_y+60); // grupo de elementos de control de los parámetros del canal
m_y=m_width_controls.GetLastY(); // obteniendo las coordenadas del último elemento de control
m_w_value2.Show(aLeft+10,m_y+20); // campo de edición de la amplitud
m_w_shift.Show(aLeft+10,m_y+40); // campo de edición del desplazamiento
}
En el método OnHideEvent() ocultamos los elementos de control:
m_c_cmb_main.Hide(); // lista de elección del tipo de línea central
m_central_controls.Hide(); // grupo de elementos de control de los parámetros de la línea central
m_c_shift.Hide(); // campo de edición del desplazamiento
m_w_cmb_main.Hide(); // lista de elección del canal
m_width_controls.Hide(); // grupo de elementos de control de los parámetros del canal
m_w_shift.Hide(); // campo de edición del desplazamiento
m_w_lockto.Hide(); // elegir el tipo de bloqueo del periodo
m_width_controls.Hide(); // campo de edición de la amplitud
}
Introducimos los cambios en el métdo SetValues(). Cambiamos el conjunto de parámetros del método, en el método establecemos para todos los elementos de control los valores correspondientes a estos parámetros:
int c_value2,
int c_value3,
long c_method,
long c_price,
long c_shift,
int w_value1,
int w_value2,
long w_method,
long w_price,
long w_lockto,
long w_shift
){
// campo de edición de los parámetros de la línea central
m_c_value1.SetValue(c_value1);
m_c_value2.SetValue(c_value2);
m_c_value3.SetValue(c_value3);
m_c_shift.SetValue(c_shift);
// campo de edición de los parámetros del canal
m_w_value1.SetValue(w_value1);
m_w_value2.SetValue(w_value2);
m_w_shift.SetValue(w_shift);
// representar los tipos elegidos en las listas de elección de los métodos de suavizado
for(int i=0;i<ArraySize(e_method);i++){
if(c_method==e_method[i]){
m_c_method.SetSelectedIndex(i);
}
if(w_method==e_method[i]){
m_w_method.SetSelectedIndex(i);
}
}
// representar los tipos elegidos en las listas de elección del tipo de precio
for(int i=0;i<ArraySize(e_price);i++){
if(c_price==e_price[i]){
m_c_price.SetSelectedIndex(i);
}
if(w_price==e_price[i]){
m_w_price.SetSelectedIndex(i);
}
}
// representación del tipo elegido de bloqueo del periodo del canal
for(int i=0;i<ArraySize(e_lockto);i++){
if(w_lockto==e_lockto[i]){
m_w_lockto.SetSelectedIndex(i);
break;
}
}
}
En lugar del método SetType(), crearemos dos métodos: SetCentralType() para establecer el tipo de línea central y SetWidthType() para establecer el tipo de límites. Al final de cada método, después de crear los objetos, para los elementos de control se establecen las propiedades que permiten introducir valores desde el teclado. Asimismo, indicamos los valores mínimos posibles y llamamos el método privado de cálculo de la altura del formulario:
Метод SetCentralType():
// si el objeto ya se ha creado con anterioridad, lo eliminamos
if(CheckPointer(m_central_controls)==POINTER_DYNAMIC){
delete(m_central_controls);
m_central_controls=NULL;
}
switch((ECType)type){ // dependiendo del tipo elegido se crea el objeto
case UniCh_C_AMA:
m_central_controls=new CUniChannelCentralControls_AMA();
break;
case UniCh_C_DEMA:
m_central_controls=new CUniChannelCentralControls_DEMA();
break;
case UniCh_C_FrAMA:
m_central_controls=new CUniChannelCentralControls_FrAMA();
break;
case UniCh_C_MA:
m_central_controls=new CUniChannelCentralControls_MA();
break;
case UniCh_C_TEMA:
m_central_controls=new CUniChannelCentralControls_TEMA();
break;
case UniCh_C_VIDyA:
m_central_controls=new CUniChannelCentralControls_VIDyA();
break;
case UniCh_C_PrCh:
m_central_controls=new CUniChannelCentralControls_PrCh();
break;
}
// transmitir los punteros a los objetos de los elementos de control
m_central_controls.SetPointers(m_c_value1,m_c_value2,m_c_value3,m_c_price,m_c_method);
// inicializando los elementos de control de los grupos
m_central_controls.InitControls();
// permitir la edición desde el teclado
m_c_value1.SetReadOnly(false);
m_c_value2.SetReadOnly(false);
m_c_value3.SetReadOnly(false);
// estableciendo los valores mínimos permitidos
m_c_value1.SetMinValue(1);
m_c_value2.SetMinValue(1);
m_c_value3.SetMinValue(1);
// cálculo de la altura del formulario
this.SolveHeight();
}
Метод SetWidthType():
// si el objeto ya se ha creado con anterioridad, lo eliminamos
if(CheckPointer(m_width_controls)==POINTER_DYNAMIC){
delete(m_width_controls);
m_width_controls=NULL;
}
switch((EWType)type){ // dependiendo del tipo elegido se crea el objeto
case UniCh_W_ATR:
m_width_controls=new CUniChannelWidthControls_ATR();
break;
case UniCh_W_StdDev:
m_width_controls=new CUniChannelWidthControls_StdDev();
break;
case UniCh_W_Points:
m_width_controls=new CUniChannelWidthControls_InPoints();
break;
case UniCh_W_Percents:
m_width_controls=new CUniChannelWidthControls_Envelopes();
break;
case UniCh_W_PrCh:
m_width_controls=new CUniChannelWidthControls_PrCh();
break;
}
// transmitir los punteros a los objetos de los elementos de control
m_width_controls.SetPointers(m_w_value1,m_w_value2,m_w_price,m_w_method);
// inicializando los elementos de control de grupos
m_width_controls.InitControls();
// estableciendo los valores mínimos permitidos
m_w_value1.SetReadOnly(false);
m_w_value2.SetReadOnly(false);
// estableciendo los valores mínimos permitidos
m_w_value1.SetMinValue(1);
m_w_value2.SetMinValue(0);
// cálculo de la altura del formulario
this.SolveHeight();
}
Al final de los métodos SetCentralType() y SetWidthType() se llama el método de cálculo de la altura del formulario SolveHeight():
// si existen ambos objetos (de la línea central y de la amplitud)
if(CheckPointer(m_central_controls)==POINTER_DYNAMIC && CheckPointer(m_width_controls)==POINTER_DYNAMIC){
m_Height=(m_width_controls.ControlsCount()+m_central_controls.ControlsCount()+6)*20+10;
}
}
Procedemos a la conexión del indicador y la interfaz gráfica
Creando el indicador y la interfaz gráfica
Guardamos el indicador iUniChannel con el nombre iUniChannelGUI. Por analogía con el indicador iUniOscGUI, añadimos a la parte superior de su ventana de propiedades el parámetro UseGUI. Después ubicamos las variables UseDefault, KeepPrev, les asignamos los valores por defecto y los mostramos en la ventana de propiedades:
input bool UseDefault = true;
input bool KeepPrev = true;
Incluimos el archivo con la interfaz gráfica (en el mismo lugar en que se incluyen los archivos con las clases de los indicadores):
En la parte más inferior de OnInit() añadimos el código de carga de la interfaz gráfica, pero antes de hacerlo, necesitaremos las matrices con los tipos de la línea central y de los límites. Las añadiremos debajo de los parámetros externos del indicador:
ECType ctype[]={
UniCh_C_AMA,
UniCh_C_DEMA,
UniCh_C_FrAMA,
UniCh_C_MA,
UniCh_C_TEMA,
UniCh_C_VIDyA,
UniCh_C_PrCh
};
// matriz con los tipos de los límites
EWType wtype[]={
UniCh_W_ATR,
UniCh_W_StdDev,
UniCh_W_Points,
UniCh_W_Percents,
UniCh_W_PrCh
};
Aquí añadimos una variable-puntero a la clase del formulario:
En la función OnInit(), al final del todo, ejecutamos la creación del objeto de la interfaz gráfica:
// crear e inicializar el objeto del formulario
frm=new CUniChannelForm();
frm.Init();
// variables auxiliares
int ind1=0;
int ind2=0;
// búsqueda del tipo elegido de línea central en la matriz de tipos de la línea central
for(int i=0;i<ArraySize(ctype);i++){
frm.m_c_cmb_main.AddItem(EnumToString(ctype[i]));
if(ctype[i]==_CentralType){
ind1=i;
}
}
// búsqueda del tipo elegido de límites del canal en la matriz de tipos de límite
for(int i=0;i<ArraySize(wtype);i++){
frm.m_w_cmb_main.AddItem(EnumToString(wtype[i]));
if(wtype[i]==_WidthType){
ind2=i;
}
}
// representación en lista del tipo elegido de línea central
frm.m_c_cmb_main.SetSelectedIndex(ind1);
// preparando los elementos de control correspondientes al tipo
frm.SetCentralType(_CentralType);
// representación en la lista del tipo elegido de límites
frm.m_w_cmb_main.SetSelectedIndex(ind2);
frm.SetWidthType(_WidthType);
// establecer valores
frm.SetValues(
_c_Period1,
_c_Period2,
_c_Period3,
_c_Method,
_c_Price,
_c_Shift,
_w_Period,
_w_Width,
_w_Method,
_w_Price,
_w_LockPeriod,
_w_Shift
);
// estableciendo las propiedades del formulario
frm.SetSubWindow(0);
frm.SetPos(10,30);
// representando el formulario
frm.Show();
}
Aparte de la creación del objeto del formulario, se rellenan las listas para elegir los indicadores y se les asignan las variantes elegidas. Asimismo, establecemos el resto de los valores en los elementos de control. Después de ello, al colocar el indicador en el gráfico se representará el formulario con los elementos de control (fig. 1).
Fig. 1 Formulario con los elementos de control del canal universal
Los elementos de control se han representado, ahora es necesario proporcionar acciones a los botones del formulario. En la función OnChartEvent() se procesan seis eventos diferentes. El procesamiento de algunos de ellos es bastante complejo y voluminoso, por eso se ha trasladado a funciones aparte:
const long &lparam,
const double &dparam,
const string &sparam)
{
// eventos del formulario
if(frm.Event(id,lparam,dparam,sparam)==1){
EventForm();
}
// eligiendo el tipo de línea central
if(frm.m_c_cmb_main.Event(id,lparam,dparam,sparam)==1){
EventCentralTypeChange();
}
// eligiendo el tipo de límites
if(frm.m_w_cmb_main.Event(id,lparam,dparam,sparam)==1){
EventWidthTypeChange();
}
// cambiar los parámetros de la línea central
if(frm.m_central_controls.Event(id,lparam,dparam,sparam)==1){
EventCentralParametersChange();
}
// cambiar los parámetros de los límites
if(frm.m_width_controls.Event(id,lparam,dparam,sparam)==1){
EventWidthParametersChange();
}
// cambiar los parámetros de desplazamiento
if(frm.m_c_shift.Event(id,lparam,dparam,sparam)!=0 ||
frm.m_w_shift.Event(id,lparam,dparam,sparam)
){
EventShift();
}
}
Vamos a analizar todas estas funciones. Función EventForm():
int win=ChartWindowFind(0,ShortName); // definir la subventana del indicador
ChartIndicatorDelete(0,win,ShortName); // eliminar el indicador
ChartRedraw();
}
Esta función se ejecuta al cerrar el formulario con el botón del aspa, además, se produce la búsqueda de la ventana del indicador según su nombre corto y se elimina el indicador.
Función EventCentralTypeChange():
// obtener en la variable el nuevo tipo
_CentralType=ctype[frm.m_c_cmb_main.SelectedIndex()];
// eliminar el objeto antiguo y crear uno nuevo
delete(central);
LoadCentral(true);
// comrpobando cómo se ha cargado el indicador
if(!central.CheckHandle()){
Alert("Error al cargar el indicador "+central.Name());
}
// estableciendo los desplazamientos y los nombres de los búferes
SetStyles();
// estableciendo en la lista el nuevo tipo
frm.SetCentralType(ctype[frm.m_c_cmb_main.SelectedIndex()]);
// actualizar los valores de los parámetros en el formulario
frm.SetValues(
_c_Period1,
_c_Period2,
_c_Period3,
_c_Method,
_c_Price,
_c_Shift,
_w_Period,
_w_Width,
_w_Method,
_w_Price,
_w_LockPeriod,
_w_Shift
);
// actualizar el formulario
frm.Refresh();
// iniciar el temporizador para recalcular los indicadores
EventSetMillisecondTimer(100);
}
En esta función se ejecuta el cambio del tipo de indicador de la línea central. Primero se obtiene el tipo de indicador elegido, se elimina el objeto antiguo y se crea uno nuevo. Al crear un nuevo objeto, algunos de sus parámetros pueden modificarse (debido a la función UseDefault), por eso se llama el método SetValues(), para establecer los nuevos valores para los elementos de control, la representación del formulario se actualiza (método Refresh()). Al final se inicia el temporizador para recalcular el indicador.
La función EventWidthTypeChange() es análoga a la función EventCentralTypeChange(), no la analizaremos en profundidad.
En las funciones EventCentralParametersChange() y EventWidthParametersChange() se posibilita la reacción de los indicadores al cambio de los valores de los parámetros. Estas dos funciones son idénticas una a otra por su funcionalidad. Sin embargo, el cambio de parámetros exige prestar especial atención al bloqueo de periodos y la corrección de los parámetros de acuerdo con el mismo, por eso las funciones tienen sus propias diferencias únicas. Ambas serán analizadas.
// variable que indica la necesidad de reiniciar el indicador de límites
bool dolock=false;
// cambio de valor en el periodo 1
if((int)frm.m_c_value1.Value()>0){
// asignar a la variable el valor obtenido del elemento de control
_c_Period1=(int)frm.m_c_value1.Value();
// si el periodo 1 está relacionado con el periodo del indicador de amplitud
if(_w_LockPeriod==LockTo_Period1){
// asignamos a la variable con el periodo del indicador de amplitud el valor del periodo 1
_w_Period=_c_Period1;
// lo representamos en el formulario
frm.m_w_value1.SetValue(_w_Period);
// indicamos que hay que reiniciar el segundo indicador
dolock=true;
}
}
// el cambio de valor del periodo 2 es análogo al cambio del periodo 1
if((int)frm.m_c_value2.Value()>0){
_c_Period2=(int)frm.m_c_value2.Value();
if(_w_LockPeriod==LockTo_Period2){
_w_Period=_c_Period2;
frm.m_w_value1.SetValue(_w_Period);
dolock=true;
}
}
// el cambio de valor del periodo 3 es análogo al cambio del periodo 1
if((int)frm.m_c_value3.Value()>0){
_c_Period3=(int)frm.m_c_value3.Value();
if(_w_LockPeriod==LockTo_Period3){
_w_Period=_c_Period3;
frm.m_w_value1.SetValue(_w_Period);
dolock=true;
}
}
// cambios de método
if(frm.m_c_method.SelectedIndex()!=-1){
_c_Method=e_method[frm.m_c_method.SelectedIndex()];
}
// cambio de precio
if(frm.m_c_price.SelectedIndex()!=-1){
_c_Price=e_price[frm.m_c_price.SelectedIndex()];
}
// eliminar un objeto antiguo y crear uno nuevo
delete(central);
LoadCentral(false);
if(!central.CheckHandle()){
Alert("Error al cargar el indicador "+central.Name());
}
// eliminar y crear un nuevo objeto del segundo indicador
if(dolock){
delete(width);
LoadWidth(false);
if(!width.CheckHandle()){
Alert("Error al cargar el indicador "+width.Name());
}
}
// estableciendo los desplazamientos y los nombres de los búferes
SetStyles();
// iniciar el temporizador para recalcular el indicador
EventSetMillisecondTimer(100);
}
En esta función, al cambiar cualquiera de los tres periodos se comprueba el valor del parámetro de bloqueo, y si se está ejecutando el bloqueo, se cambia el parámetro para el indicador de límites, se actualiza en el formulario y a la variable dolock se le asigna el valor true. Al final se elimina el objeto antiguo del indicador, se crea uno nuevo, y, si la variable dolock es igual a true, se elimina y se crea un objeto de límites. Después de todo esto, se inicia el temporizador, que espera el recálculo de los indicadores.
// variable que indica la necesidad de reiniciar el indicador de la línea central
bool dolock=false;
// cambio de periodo
if((int)frm.m_w_value1.Value()>0){
// asignar a la variable el valor obtenido del elemento de control
_w_Period=(int)frm.m_w_value1.Value();
// ejecutar bloqueo
// el parámetro de amplitud está relacionado con el primer periodo de la línea central
if(_w_LockPeriod==LockTo_Period1){
// asignar un nuevo valor a la variable del indicador de la línea central
_c_Period1=_w_Period;
// actualizar el valor en el formulario
frm.m_c_value1.SetValue(_c_Period1);
// indicamos la necesidad de reiniciar el indicador de amplitud
dolock=true;
}
else if(_w_LockPeriod==LockTo_Period2){ // si el bloqueo tiene un periodo 2
_c_Period2=_w_Period;
frm.m_c_value2.SetValue(_c_Period2);
dolock=true;
}
else if(_w_LockPeriod==LockTo_Period3){ // si el bloqueo tiene un periodo 3
_c_Period3=_w_Period;
frm.m_c_value3.SetValue(_c_Period3);
dolock=true;
}
}
// cambiar el parámetro de amplitud del canal
if((double)frm.m_w_value2.Value()>0){
_w_Width=(double)frm.m_w_value2.Value();
}
// cambio de método
if(frm.m_w_method.SelectedIndex()!=-1){
_w_Method=e_method[frm.m_w_method.SelectedIndex()];
}
// cambio de precio
if(frm.m_w_price.SelectedIndex()!=-1){
_w_Price=e_price[frm.m_w_price.SelectedIndex()];
}
// evento de cambio en la lista de elección del tipo de bloqueo de los periodos
if(frm.m_w_lockto.SelectedIndex()>=0){
// asignar a la variable el valor del elemento de control
_w_LockPeriod=e_lockto[frm.m_w_lockto.SelectedIndex()];
// si se ha elegido el bloqueo con alguno de los periodos,
// se copia su valor y se actualiza en el formulario
if(_w_LockPeriod==LockTo_Period1){
_w_Period=_c_Period1;
frm.m_w_value1.SetValue(_w_Period);
}
else if(_w_LockPeriod==LockTo_Period2){
_w_Period=_c_Period2;
frm.m_w_value1.SetValue(_w_Period);
}
else if(_w_LockPeriod==LockTo_Period3){
_w_Period=_c_Period3;
frm.m_w_value1.SetValue(_w_Period);
}
}
// eliminar un objeto antiguo y crear uno nuevo
delete(width);
LoadWidth(false);
if(!width.CheckHandle()){
Alert("Error al cargar el indicador "+width.Name());
}
// eliminar y crear un nuevo objeto del segundo indicador
if(dolock){
delete(central);
LoadCentral(false);
if(!central.CheckHandle()){
Alert("Error al cargar el indicador "+central.Name());
}
}
// estableciendo los desplazamientos y los nombres de los búferes
SetStyles();
// iniciar el temporizador para recalcular el indicador
EventSetMillisecondTimer(100);
}
En esta función, al cambiar el periodo se comprueba el tipo de bloqueo, y, si es necesario, se cambia el periodo del indicador de la línea central. Si tiene lugar el evento de la lista de elección del tipo de bloqueo, a la variable del periodo del indicador se le asigna del valor de la variable del indicador correspondiente de la línea central.
El procesamiento del evento de cambio de valores del desplazamiento es bastante sencillo:
// obtener nuevos valores en las variables
_c_Shift=(int)frm.m_c_shift.Value();
_w_Shift=(int)frm.m_w_shift.Value();
// establecer nuevos estilos
SetStyles();
// actualizar el gráfico
ChartRedraw();
}
A las variables se les asignan los valores de los elementos de control, se llaman las funciones SetStyles() y se actualiza el gráfico.
Hecho esto, el indcador con interfaz gráfica se puede considerar casi finalizado.
Durante la simulación del indicador, se ha detectado un defecto en el mismo. Cuando el parámetro externo UseDefault está activado y se usa el bloqueo de periodo, el bloqueo no funcionaba. Esto se debe a que, al cargar el segundo indicador (el indicador de amplitud), en su constructor se ejecuta el cambio de parámetros. Para corregir este error, hemos tenido que modificar un poco algunas clases derivadas de los indicadores de amplitud. Se ha añadido a los constructores de las clases CChannelUni_Calculate_ATR, CChannelUni_Calculate_StdDev y CChannelUni_Calculate_PriceChannel el parámetro opcional locked con el valor por defecto false (si el parámetro no se transmite a la clase, todo funcionará sin cambios). Al establecer locked=true y use_default=true, los parámetros del periodo en el constructor no se cambian (con la condición locked=true). Analizaremos un fragmento de la clase CChannelUni_Calculate_ATR:
if(keep_previous){
if(ma_period==-1 && !locked)ma_period=14; // cambio
if(ch_width==-1)ch_width=2;
}
else{
if(!locked)ma_period=14; // cambio
ch_width=2;
}
}
A la variable ma_period se le asigna el valor por defecto solo si la variable locked es igual a false. Así, hemos completado la corrección de la función LoadWidth(). Al inicio de la función se calcula el valor Locked:
A continuación, esta variable se transmite a los constructores de las clases al crear los objetos.
Asimismo, como hemos hecho en el oscilador universal, añadiremos la posibilidad de cambiar el esquema de color y posibilitaremos el guardado de parámetros del indicador al cambiar de marco temporal. No vamos a analizar el uso de los esquemas de color, pues ya se estudió al crear el oscilador universal. Vamos a trabajar con el guardado de parámetros.
En la función OnDeinit() del indicador, si el cambio de gráfico es el motivo de la inicialización, crearemos objetos gráficos con los valores de los parámetros. Estos objetos gráficos los crearemos fuera de los límites visibles del gráfico:
// si no se trata del cambio del gráfico, eliminamos los bjetos gráficos
if(reason!=REASON_CHARTCHANGE){
ObjectDelete(0,"_CentralType");
ObjectDelete(0,"_c_Period1");
ObjectDelete(0,"_c_Period2");
ObjectDelete(0,"_c_Period3");
ObjectDelete(0,"_c_Shift");
ObjectDelete(0,"_c_Method");
ObjectDelete(0,"_c_Price");
ObjectDelete(0,"_WidthType");
ObjectDelete(0,"_w_Period");
ObjectDelete(0,"_w_LockPeriod");
ObjectDelete(0,"_w_Shift");
ObjectDelete(0,"_w_Method");
ObjectDelete(0,"_w_Price");
ObjectDelete(0,"_w_Width");
}
else{ // al cambiar el gráfico, creamos objetos gráficos con los valores de los parámetros
SaveParameter("_CentralType",(string)_CentralType);
SaveParameter("_c_Period1",(string)_c_Period1);
SaveParameter("_c_Period2",(string)_c_Period2);
SaveParameter("_c_Period3",(string)_c_Period3);
SaveParameter("_c_Shift",(string)_c_Shift);
SaveParameter("_c_Method",(string)_c_Method);
SaveParameter("_c_Price",(string)_c_Price);
SaveParameter("_WidthType",(string)_WidthType);
SaveParameter("_w_Period",(string)_w_Period);
SaveParameter("_w_LockPeriod",(string)_w_LockPeriod);
SaveParameter("_w_Shift",(string)_w_Shift);
SaveParameter("_w_Method",(string)_w_Method);
SaveParameter("_w_Price",(string)_w_Price);
SaveParameter("_w_Width",(string)_w_Width);
}
}
// función auxiliar para guardar un parámetro en el objeto gráfico
void SaveParameter(string name,string value){
if(ObjectFind(0,name)==-1){
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,0);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,-30);
}
ObjectSetString(0,name,OBJPROP_TEXT,value);
}
En la función OnInit(), justo después de la llamada de PrepareParameters(), llamaremos la función LoadSavedParameters():
// si existen todos los objetos con los parámetros
if(ObjectFind(0,"_CentralType")==0 &&
ObjectFind(0,"_c_Period1")==0 &&
ObjectFind(0,"_c_Period2")==0 &&
ObjectFind(0,"_c_Period3")==0 &&
ObjectFind(0,"_c_Shift")==0 &&
ObjectFind(0,"_c_Method")==0 &&
ObjectFind(0,"_c_Price")==0 &&
ObjectFind(0,"_WidthType")==0 &&
ObjectFind(0,"_w_Period")==0 &&
ObjectFind(0,"_w_LockPeriod")==0 &&
ObjectFind(0,"_w_Shift")==0 &&
ObjectFind(0,"_w_Method")==0 &&
ObjectFind(0,"_w_Price")==0 &&
ObjectFind(0,"_w_Width")==0
){
// obteniendo los valores de los objetos gráficos
_CentralType=(ECType)ObjectGetString(0,"_CentralType",OBJPROP_TEXT);
_c_Period1=(int)ObjectGetString(0,"_c_Period1",OBJPROP_TEXT);
_c_Period2=(int)ObjectGetString(0,"_c_Period2",OBJPROP_TEXT);
_c_Period3=(int)ObjectGetString(0,"_c_Period3",OBJPROP_TEXT);
_c_Shift=(int)ObjectGetString(0,"_c_Shift",OBJPROP_TEXT);
_c_Method=(long)ObjectGetString(0,"_c_Method",OBJPROP_TEXT);
_c_Price=(long)ObjectGetString(0,"_c_Price",OBJPROP_TEXT);
_WidthType=(EWType)ObjectGetString(0,"_WidthType",OBJPROP_TEXT);
_w_Period=(int)ObjectGetString(0,"_w_Period",OBJPROP_TEXT);
_w_LockPeriod=(long)ObjectGetString(0,"_w_LockPeriod",OBJPROP_TEXT);
_w_Shift=(int)ObjectGetString(0,"_w_Shift",OBJPROP_TEXT);
_w_Method=(long)ObjectGetString(0,"_w_Method",OBJPROP_TEXT);
_w_Price=(long)ObjectGetString(0,"_w_Price",OBJPROP_TEXT);
_w_Width=(double)ObjectGetString(0,"_w_Width",OBJPROP_TEXT);
return(true);
}
else{
return(false);
}
}
En la función se comprueba si existen estos objetos, y si existen, se usan sus valores, además, la función retorna true. Si la función ha retornado true, llamaremos las funciones LoadCentral() y LoadWidth() con el parámetro false (para que no se establezcan los parámetros por defecto). Fragmento de la función OnInit():
LoadCentral(!ChartCange);
De la misma forma se llama la función LoadWidth():
Así, hemos finalizado por completo la creación del canal universal.
Conclusión
A pesar de que hemos usado una gran cantidad de código ya preparado del oscilador universal, la creación del canal universal ha requerido de un volumen considerable de trabajo adicional. La principal diferencia con respecto al oscilador universal reside en la existencia de dos bloques independientes: el de la línea central y el de los límites. Las complicaciones derivadas de este hecho han aumentado el volumen de trabajo a casi el doble. Asimismo, se ha complicado el agoritmo de cambio de parámetros debido a la función de bloqueo de periodos. También se ha complicado la propia carga de los nuevos indicadores, porque ahora son dos. Además, se ha añadido una nueva posibilidad funcional: el guardado de parámetros al cambiar de marco temporal. Como resultado, hemos obtenido un indicador tan útil y cómodo como el oscilador universal. Aparte, este indicador amplía significativamente las posibilidades de la propia idea del canal, pues ahora se pueden elegir por separado la línea central y el método de construcción de los límites. En definitiva, esto nos da un gran número de diferentes combinaciones posibles. El aumento de la velocidad de uso del indicador gracias a la interfaz gráfica permitirá investigar de forma visual todas estas combinaciones.
Anexos
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2888





- 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