Transferir el código de un indicador al código de un asesor experto. Estructura del indicador
Introducción
Para comprenderlo mejor, el autor recomienda la lectura del siguiente material:
1. MetaQuotes Software Corp. Features of Custom Indicators Creation.
2. Nikolay Kositsin. Multiple Null Bar Re-Count in Some Indicators.
Antes de comenzar con el tema del artículo indicado en el título, sería conveniente realizar primero la siguiente pregunta: "¿Qué debemos hacer para transferir el código de un indicador al código de un asesor experto, si en la mayoría de casos un asesor experto funciona con indicadores personalizados, parece mucho más fácil que su análogo, que contiene todo lo necesario para el funcionamiento de los indicadores personalizados dentro de su propio código? Especialmente si tenemos en cuenta el hecho de que si un código está escrito correctamente, los resultados en ambos casos serán completamente idénticos.
En mi opinión, es necesario en dos casos:
1. En el cálculo de un asesor experto, los valores calculados en una barra cero no se usan en absoluto, naturalmente nos gustaría omitir un recálculo innecesario de la barra cero y de la primera barra. Esto permite acortar el tiempo de optimización de dicho asesor experto, que es más importante en un código muy complejo e intensivo en recursos.
2. El uso comercial de un asesor experto con la máxima protección de su código frente a la decompilación.
Ejemplo de optimización del indicador
En primer lugar, me gustaría que prestara atención al siguiente fragmento de código de un indicador personalizado:
int start() { int limit; int counted_bars = IndicatorCounted(); //---- the last calculated bar will be recalculated if(counted_bars > 0) counted_bars--; limit = Bars - counted_bars - 1; //---- the main cycle for(int i = limit; i >= 0; i--) { //---- ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); } //---- return(0); }
En este caso, para nosotros es importante la siguiente línea:
if(counted_bars > 0) counted_bars--;
El significado de esta comprobación con disminución del valor de la variable 'counted_bars' en uno es el siguiente: si un indicador personalizado no incluye esta línea, puede enviar valores incorrectos desde sus búferes a un asesor experto cuando se cambia la barra cero. La curva del indicador en el asesor experto tendrá una forma bastante "arrugada".
Pero debe recordarse aquí que algunos algoritmos de ajuste son sensibles al punto de referencia a partir del cual comienza el ajuste. Es decir, para tener idénticos valores, los números de las barras más antiguas, a partir de las cuales comienza el recálculo de las barras, en ciclos en el indicador y en el código del indicador dentro del asesor experto deben coincidir.
Este es un ejemplo que explica este método de optimización del código de un indicador para un funcionamiento más rápido en un asesor experto. En el indicador principal el ciclo cambia de cero a uno, después de que el indicador deje de recalcular su valor en la barra cero.
// instead of for(int i = limit; i >= 0; i--) // write for(int i = limit; i >= 1; i--)
Como resultado, el código fuente tendrá el siguiente aspecto:
int start() { int limit; int counted_bars = IndicatorCounted(); //---- the last bar will be recalculated if(counted_bars > 0) counted_bars--; limit = Bars - counted_bars - 1; //---- the main cycle //now the cycle ends in 1 for(int i = limit; i >= 1; i--) { //---- ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, PRICE_MEDIAN, i); } //---- return(0); }
Si va a usar en serio su asesor experto en el trading real y confiar a este su dinero, debe comprobar cuidadosamente todos los detalles de su asesor experto, así como los indicadores con los que trabaja el asesor experto. Además, debe hacerlo usted mismo. Creo que es mucho más fácil y acertado dedicar un par de días a conocer en profundidad la estructura del indicador y los métodos de optimización de su código que usar con paciencia un asesor experto durante tres meses que reciba valores de un indicador escrito toscamente.
Por tanto, debe quedar claro que son necesarias serias razones para transferir el código de un indicador al código de un asesor experto. Si se ha escrito correctamente un indicador, el funcionamiento de un asesor experto no será mucho más lento sin este. Es más fácil escribir primero el código de un asesor experto usando indicadores personalizados y realizar la comprobación de esta forma. Y si el asesor experto muestra realmente resultados perfectos, puede optimizarse más el código uno a uno cambiando las llamadas a los indicadores personalizados a fragmentos de código del indicador.
Y debe recordarse que los valores probados de pérdidas y beneficios no deben modificarse después de cambiar el código del asesor experto.
El número de indicadores existentes es muy grande y cada uno de ellos tiene su código único. Por esa razón es difícil crear un método universal para transferir el código a todos los indicadores. El problema se agrava por el hecho de que los mismos indicadores personalizados pueden estar representados en el código de un asesor experto varias veces. Si el código de un indicador es más o menos simple, puede ser escrito en una función personalizada y cambiar los indicadores personalizados a funciones en tal caso es bastante fácil. Pero con mucha frecuencia el código de un asesor experto alcanza tales dimensiones que es casi imposible detectar un error en él. Y todos nuestros esfuerzos son en vano.
Esquema general de la estructura de un indicador
//+------------------------------------------------------------------+ //| IndicatorPlan.mq4 | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, MetaQuotes Software Corp." #property link "https://www.metaquotes.net/" //---- drawing the indicator in the main window #property indicator_chart_window //---- number of indicator buffers #property indicator_buffers 1 //---- indicator color #property indicator_color1 Gold //---- INPUT PARAMETERS OF THE INDICATOR extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- defining the graph execution style SetIndexStyle(0, DRAW_LINE); //---- 3 indicator buffers are used for calculation IndicatorBuffers(3); SetIndexBuffer(0, Ind_Buffer0); SetIndexBuffer(1, Ind_Buffer1); SetIndexBuffer(2, Ind_Buffer2); //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar,bar, counted_bars = IndicatorCounted(); //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= MaxBar; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation based on the external // variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] // and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0calculation // based on the values of the buffer Ind_Buffer2[] // and external variable0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
Ahora vamos a excluir del esquema elementos que no nos interesan en este contexto y son innecesarios en un asesor experto.
//+------------------------------------------------------------------+ //| IndicatorPlan1.mq4 | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT PARAMETERS OF THE INDICATOR extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar, bar, counted_bars = IndicatorCounted(); //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= MaxBar; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation // based on the external variable period1 Ind_Buffer1[bar]= Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] // and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 // based on the values of the buffer Ind_Buffer2[] // and external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
Este código puede colocarse fácilmente en el código de un asesor experto si no para un parte de pequeños errores lamentables:
1. En primer lugar, no debemos olvidar que la función IndicatorCounted() no funciona en los asesores expertos.
2. Tampoco podemos convertir matrices personalizadas en matrices de indicadores en el bloque de inicialización.
Por tanto, para preservar completamente el código del indicador primero tenemos que desarrollar una función análoga a IndicatorCounted() y de alguna forma emular a búferes de indicadores análogos en un asesor experto. Por desgracia es imposible emular directamente búferes de indicador en un asesor experto usando funciones estándar. Por ahora no hay análogas a SetIndexBuffer() y IndicatorBuffers() para los asesores expertos. Por tanto, este problema debe resolverse usando otros métodos. Además, hay opciones suficientes para ello en MQL4.
Emulación de búferes de indicadores en un asesor experto
1. Los valores asignados a las variables del búfer del indicador no se pierden entre ciclos, cuando el indicador está unido a un gráfico y trabaja un terminal.
2. Si se cambia una barra cero (la última) en un gráfico, se cambian todos los elementos del búfer del indicador.
3. Si llega una nueva barra, el valor del límite de la variable cambia desde uno a dos en el indicador y todos los elementos del búfer se cambian por uno, es decir, la barra cero se convierte en la primera, la primera en la segunda, y así sucesivamente.
4. Naturalmente, las dimensiones del indicador del búfer cambian. Si con el nuevo tick no cambia la barra, todos los elementos del búfer permanecen en sus lugares.
//---- INDICATOR BUFFERS EMULATION int NewSize = iBars(symbol, timeframe); //---- Checking the change of the zero bar if(ArraySize(Ind_Buffer0) < NewSize) { //---- Set the direct indexing direction in the array ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- Change the size of the emulated indicator buffers ArrayResize(Ind_Buffer0, NewSize); ArrayResize(Ind_Buffer1, NewSize); ArrayResize(Ind_Buffer2, NewSize); //---- Set the reverse indexing direction in the array ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } //----
Ahora viene la emulación de los búferes de los indicadores. Para este propósito usaremos las siguientes funciones estándar de MQL4: ArraySize(), ArrayResize() y ArraySetAsSeries(). El código de la emulación de los búferes de los indicadores es bastante simple y su principio de funcionamiento puede describirse: cuando cambia la barra cero, el orden directo de la definición de los elementos en los búferes se restaura, se añaden nuevas celdas a los búferes a partir de nuevas barras usando la función ArrayResize() y posteriormente se establece el orden inverso de la definición de los elementos en los búferes y las celdas vacías parecen estar entre las primeras en el búfer del indicador emulado.
A propósito, este método de emulación de los búferes de indicador puede usarse también en los indicadores cuando no son suficientes ocho búferes de indicador para los cálculos intermedios. Los ejemplos están en el archivo adjunto SMI.mq4 y SMI_New.mq4.
Sustitución de la función IndicatorCounted()
//+------------------------------------------------------------------+ //| NewIndicatorPlan1.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT INDICATOR PARAMETERS extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- initialization end return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- Checking whether the bars number is enough for further calculation if(Bars < period0 + period1 + period2) return(0); //---- INDICATOR BUFFERS EMULATION if(ArraySize(Ind_Buffer0) < Bars) { ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- ArrayResize(Ind_Buffer0, Bars); ArrayResize(Ind_Buffer1, Bars); ArrayResize(Ind_Buffer2, Bars); //---- ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } //----+ INSERTION OF A STATIC INTEGER MEMORY VARIABLE static int IndCounted; //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and getting calculated bars int limit, MaxBar, bar, counted_bars = IndCounted; //---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--; //----+ REMEMBERING THE AMOUNT OF ALL BARS OF THE CHART IndCounted = Bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = Bars - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = Bars - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = Bars - 1; bar >= 0; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation // based on the external variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] and external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] and external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
Transformación adicional del código del indicador y esquema final de su estructura
La tarea de procesar los datos y demás períodos de tiempo se resuelve también fácilmente. Sustituir las variables predeterminadas del tipo Bars por series de tiempo del tipo
iBars(string symbol, int timeframe);
NULL - para el símbolo string;, 0 (en series de tiempo) - para el período de tiempo int;, Close[bar] - para
iClose(string symbol, int timeframe, bar);
y así sucesivamente.
Ahora vamos a analizar las líneas del indicador:
//---- checking for possible errors if(counted_bars < 0) return(-1); //---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--;
Al igual que en la sustitución ofrecida para la función IndicatorCounted() en nuestra variante de la estructura del indicador, el indicador counted_bars nunca será inferior a cero. Por tanto, las líneas:
//---- checking for possible errors if(counted_bars < 0) return(-1);
en el código del asesor experto pueden omitirse. Con las siguientes dos líneas:
//---- the last calculated bar must be recalculated if(counted_bars > 0) counted_bars--;
es lo mismo. Deben borrarse, aunque solo ralentizan el trabajo del asesor experto mediante recálculos innecesarios de la primera barra y en la operación del asesor experto esta comprobación es absolutamente inútil. Después de eso, el código final para transferir a un asesor experto tiene la forma siguiente:
//+------------------------------------------------------------------+ //| NewIndicatorPlan2.mqh | //| Copyright © 2007, MetaQuotes Software Corp. | //| https://www.metaquotes.net/ | //+------------------------------------------------------------------+ //---- INPUT INDICATOR PARAMETERS extern int period0 = 15; extern int period1 = 15; extern int period2 = 15; //---- indicator buffers double Ind_Buffer0[]; double Ind_Buffer1[]; double Ind_Buffer2[]; //---- DECLARING VARIABLES FOR CHOOSING A CHART string symbol; int timeframe; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int init() { //---- CHOOSING A CHART FRO INDICATOR CALCULATION symbol = Symbol();//INITIALIZATION OF THE VARIABLE symbol; timeframe =240;//INITIALIZATION OF THE VARIABLE timeframe; //---- end of initialization return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { // GETTING THE AMOUNT OF ALL BARS OF THE CHART int IBARS = iBars(symbol, timeframe); //---- Checking whether the bars number is enough for further calculation if(IBARS < period0 + period1 + period2) return(0); // INDICATOR BUFFERS EMULATION if(ArraySize(Ind_Buffer0) < IBARS) { ArraySetAsSeries(Ind_Buffer0, false); ArraySetAsSeries(Ind_Buffer1, false); ArraySetAsSeries(Ind_Buffer2, false); //---- ArrayResize(Ind_Buffer0, IBARS); ArrayResize(Ind_Buffer1, IBARS); ArrayResize(Ind_Buffer2, IBARS); //---- ArraySetAsSeries(Ind_Buffer0, true); ArraySetAsSeries(Ind_Buffer1, true); ArraySetAsSeries(Ind_Buffer2, true); } // INSERTION OF A STATIC INTEGER MEMORY VARIABLE static int IndCounted; //----+ Insertion of variables with a floating point double Resalt0, Resalt1, Resalt2; //----+ Insertion of integer variables and GETTING ALREADY CALCULATED BARS int limit, MaxBar, bar, counted_bars = IndCounted; //----+ REMEMBERING THE AMOUNT OF ALL BARS OF THE CHART IndCounted = IBARS - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated limit = IBARS - counted_bars - 1; //---- defining the number of the oldest bar, // starting from which new bars will be recalculated MaxBar = IBARS - 1 - (period0 + period1 + period2); //---- initialization of zero if(limit > MaxBar) { limit = MaxBar; for(bar = IBARS - 1; bar >= 0; bar--) { Ind_Buffer0[bar] = 0.0; Ind_Buffer1[bar] = 0.0; Ind_Buffer2[bar] = 0.0; } } //----+ THE FIRST CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt1 calculation based on the external // variable period1 Ind_Buffer1[bar] = Resalt1; } //----+ THE SECOND CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt2 calculation // based on the values of the buffer Ind_Buffer1[] and the external variable period2 Ind_Buffer2[bar] = Resalt2; } //----+ THE MAIN CYCLE OF INDICATOR CALCULATION for(bar = limit; bar >= 0; bar--) { // Here code of the variable Resalt0 calculation // based on the values of the buffer Ind_Buffer2[] and the external variable period0 Ind_Buffer0[bar] = Resalt0; } return(0); } //+------------------------------------------------------------------+
Aquí debemos tener en cuenta un momento. Hay indicadores que en el recálculo múltiple de la barra cero, en la primera barra al comienzo de los ciclos de cálculo recuerdan los valores de algunas variables para devolver el código a su estado inicial (Artículo). En un asesor experto, después de borrar las últimas dos líneas este recordatorio debe producirse en la barra cero y no en la primera. Normalmente dichos indicadores contienen al comienzo de los ciclos de cálculo los siguientes fragmentos de código:
// Saving the variables values if(bar == 1) if(((limit == 1) && (time == Time[2])) || (limit > 1)) { time = Time[2]; // These variables are not interesting for us PRICE = price; TREND = trend; RESALT = Resalt; } //+------------------------------------------------------------------+
Este fragmento debe modificarse para guardar los valores de la variable no en la primera, sino en la barra cero. Todos los '1' deben sustituirse por '0' '2' - por '1'- Y si el código está diseñado para un funcionamiento no en el gráfico actual, la referencia a una matriz de series de tiempo debe cambiarse también
time = Time[1];
en
time = iTime(symbol, timeframe, 1);
Como resultado, obtendremos el siguiente fragmento cambiado:
// Saving variables values if(bar == 0) if(((limit == 0) && (time == iTime(symbol, timeframe, 1))) || (limit > 0)) { time = iTime(symbol, timeframe, 1); PRICE = price; TREND = trend; RESALT = Resalt; } //+------------------------------------------------------------------+
El código de un indicador también contiene las funciones de ajuste como XXXSeries(), desarrolladas por mí. Para usarlos en el código de un asesor experto los fragmentos con dichas funciones, estas deben sustituirse por sus asesores expertos análogos.
Conclusión
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1456
- 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