
Aprendiendo MQL5 de principiante a profesional (Parte V): Operadores básicos para redirigir el flujo de comandos
Introducción
En el presente artículo de la serie hablaremos de cómo enseñar a un programa a tomar decisiones. Para ello crearemos un indicador, digamos, que muestra barras internas o externas.
- Consideraremos la barra que ha roto el rango de la vela anterior en ambas direcciones como una barra exterior.
- Consideraremos una barra interna aquella que permanezca dentro del rango de la vela anterior tras el cierre.
- Marcaremos cada vela cerrada. Si la vela sigue cambiando, no le pondremos marcas.
- Los marcadores se colocarán solo en velas "largas", es decir, para la barra "exterior", en la barra actual; para la "interior", en la barra anterior. Esta solución puede resultar controvertida, por lo que si el indicador le parece útil, pero no está de acuerdo con esta solución, no dude en cambiar los índices correspondientes en el lugar necesario del código.
- Vamos a convertir nuestro indicador en universal. Haremos que sea capaz de mostrar ambas opciones, y ya el usuario se encargará de elegir entre ellas usando los parámetros de entrada.
Al crear este indicador, tendremos que usar los operadores comentados en el artículo. El indicador es simple, por lo que solo se utilizarán operadores de ciclo y condición, y eso es realmente todo lo que necesitaremos en este caso. Pero para practicar puede intentar reescribir este indicador:
- utilizando otros tipos de ciclos;
- utilizando el operador switch-case en lugar de algunos (o todos) los operadores de condición.
Hasta ahora, nuestros programas no han resultado muy útiles porque no tenían elección y debían seguir nuestras instrucciones de forma lineal, paso a paso. Después de leer este artículo, sus programas podrán ser mucho más satisfactorios.
Y empezaremos con un análisis detallado de las expresiones booleanas (es decir, lógicas), porque suponen la base para entender el resto del material de este artículo.
Expresiones lógicas con detalle
Permítame recordarle que los datos de tipo booleano (bool) solo tienen dos valores: true (verdadero) o false (falso).
Podemos decir que en cada caso de aplicación de los operadores lógicos, el programa hace una pregunta a sus datos actuales: "¿Es cierto que (condición)?". ("¿Es cierto que ahora (prev_calculated == rates_total)? Si la respuesta es sí, ¡entonces es hora de que finalice la función actual! Si no, tendré que volver a calcular algo.....").
En MQL5, el número 0 se usa para representar el valor false; respectivamente, todos los demás números en las expresiones lógicas se convertirá en true, no importa si son positivos o negativos.
Las líneas no se pueden convertir a tipo bool; si intentamos convertirlas al tipo bool, obtendremos un mensaje de error del compilador. Pero también podemos compararlas.
Los datos lógicos suelen derivarse de expresiones lógicas. El caso más sencillo de una expresión de este tipo será la operación de comparación. Permítame recordarle que en MQL5 se utilizan los mismos operadores de comparación que en las matemáticas escolares: mayor que (>), menor que (<), igual a (==), no igual a (!=), mayor o igual a (>=), menor o igual a (<=). En consecuencia, la entrada (a==b) tomará el valor "true" si y solo si ambos elementos de la expresión son iguales entre sí. Creo que eso resulta comprensible.
Al comparar líneas, la línea mayor será la que tenga al principio los caracteres con números más altos en la tabla de caracteres. Por ejemplo, ("A" > "1") es verdadero, mientras que ("Y" < "A") es falso. Si hay muchos caracteres en una línea, se mantendrá el principio, solo que primero se compararán los primeros caracteres, luego los segundos, y así sucesivamente, hasta la divergencia. La línea vacía ("") es más pequeña que todas las demás, es decir, si alguna línea tiene menos caracteres pero los caracteres iniciales son los mismos, se considerará más pequeña al realizar la comparación.
Existen otras tres operaciones lógicas:
- La negación lógica (también conocida como operación "no" o inversión) se indica mediante un signo de exclamación (!);
convierte un significado lógico en su opuesto. - La multiplicación lógica (también conocida como operación "y" o conjunción) se indica mediante dos símbolos ampersand consecutivos (&&);
con este operador se dará el resultado verdadero solo si ambas partes de la expresión, tanto la izquierda como la derecha, son verdaderas. - La suma lógica (también conocida como operación "o" o disyunción) se indica mediante dos guiones verticales (||);
una expresión con este operador será verdadera si al menos una parte de ella es verdadera.
Tabla 1. Negación lógica (!) | Tabla 2. Multiplicación lógica (&&) | Tabla 3. Suma lógica (||) |
![]() | ![]() | ![]() |
Para reforzar el material, intente determinar por usted mismo cuál sería el resultado de cada una de las expresiones siguientes. Existen pistas en los comentarios, pero le recomiendo no mirarlas al principio y usarlas solo para comprobar su propia respuesta.
int a = 3; int b = 5; bool e = ( a*b ) > ( a+b+b ); // true Print ( (a>b) ); // false Print ( (a<b) || ((a/b) > (3/2)) ); // true Print ( !a ); // false Print ( ! (!e) ); // true Print ( (b>a) && ((a<=3) || (b<=3)) ); // true Print ( "Trust" > "Training" ); // true Print ( a==b ); // false Print ( b=a ); // 3 (!!!) (therefore, for any logical operator this is always true) // In the last example, a very common and hard-to-detect mistake has been made. // Instead of a comparison, an assignment was used, which results in a value of type int! // Fortunately, the compiler will usually warn about this substitution.
Ejemplo 1. Ejemplos de uso de operadores lógicos
Si en una expresión aparecen varios operadores sin paréntesis, generalmente se ejecutarán de izquierda a derecha (salvo la asignación), y el orden de ejecución será el siguiente:
- Todos los operadores aritméticos se ejecutarán en primer lugar
- *, /, %
- +, -
- A continuación, se ejecutarán los operadores de comparación (==, !=, >, <, >=, <=).
- Y luego se ejecutarán el resto de operadores lógicos, en el orden en que aparecen en la lista:
- Inversión (!);
- Conjunción (&&);
- Disyunción (||);
- El operador de asignación (=) se ejecutará en último lugar.
Si no está seguro de qué parte de su código se ejecutará antes, utilice paréntesis como en mi ejemplo 1. Siempre le servirán de ayuda porque las operaciones entre paréntesis se hacen primero. Sí, y resultará más visual si dejamos espacios....
El operador if
El operador if también se denomina operador de condición, operador de ramificación, operador de ramificación... En español, "if" significa "si", "else" significa "de lo contrario".
Su forma es muy sencilla:
if (condition) action_if_condition_is_true; else action_in_all_other_cases; // The "else" branch is optional
Ejemplo 2. Plantilla de declaración de condición.
¿Por qué "ramificación"? Si representamos gráficamente este operador, obtendremos algo así:
Figura 1. Operador de ramificación
Si tiene buena imaginación, podrá ver cómo del "tronco" principal, gracias a este operador, "crece" un "vástago" (rama) adicional. En los esquemas más antiguos (desde el punto de vista de la época en que se inventaron), los algoritmos parecen "arbustos" completos. Por ejemplo, ¿qué hará usted si quiere leer un libro pero la lámpara de su escritorio no está encendida? Primero intentará encender la bombilla, a ver si se ilumina. Si no lo logra, comprobará si hay una bombilla en el casquillo: ¿quizá el niño la haya desenroscado porque la necesitaba para algo? Si hay una bombilla y no se enciende, comprobará si está fundida. Si está fundida, intentará cambiarla. Si todas estas medidas no han servido de nada, intentará conseguir otra bombilla. Este algoritmo puede dibujarse así:
Figura 2. Ilustración de la ramificación
Aquí, para ilustrar la idea de "ramificación", el algoritmo comienza desde abajo, desde el punto verde "Inicio". Creo que este esquema ya muestra claramente cómo se produce la ramificación en cada punto de elección. Como en la figura anterior, los puntos de selección están marcados en azul.
Tenga en cuenta que "action_if_condition_is_true" y "action_in_all_other_cases" en MQL5 solo puede ser un operador, pero este operador puede ser compuesto, de ser necesario.
Déjeme que se lo explique. En MQL5, las acciones se pueden encerrar (o no) entre llaves. Las llaves son como una "entidad" separada y encierran un bloque de código independiente. Las variables declaradas dentro de este bloque no resultan visibles para otros bloques. En algunos casos, las llaves están estrictamente prescritas por la sintaxis, pero con mucha frecuencia pueden disponerse casi arbitrariamente en el código. El contenido de estas llaves será tratado como un todo por el resto del programa. Por ejemplo, las variables descritas dentro de un bloque no resultarán visibles para ningún operador externo. Este es el tipo de "bloque" de llaves que yo llamo "operador compuesto". Puede haber tantas acciones como quieras dentro de él, pero los comandos como if las ejecutarán hasta que se encuentren con la llave que cierra el bloque principal. Sin embargo, si no hay llaves, solo se ejecutará un operador cuando se cumpla la condición, mientras que todos los demás operadores se ejecutarán "de forma general".
Los comandos subordinados a un operador (en nuestro caso if o else) suelen denominarse "cuerpo del operador".
En la plantilla del ejemplo 2, el cuerpo del operador if sería "action_if_condition_is_true", mientras que el cuerpo del operador else sería "action_in_all_other_cases".
Aquí tenemos un ejemplo práctico. Este código comprueba si hay una nueva vela en el indicador, cuya plantilla se muestra en el ejemplo 16. El algoritmo se basa en la comparación del número de velas ya calculadas (prev_calculated) y el número total de velas del gráfico (rates_total):
if (prev_calculated == rates_total) // _If_ they are equal, { // _then_ do nothing, wait for a new candlestick Comment("Nothing to do"); // Display a relevant message in a comment return(rates_total); // Since nothing needs to be done, exit the function // and inform the terminal that all bars have been calculated (return rates_total) } // A new candle has arrived. Execute necessary actions Comment(""); // Clear comments Print("I can start to do anything"); // Log a message // Since the required actions will be executed // only if prev_calculated is not equal to rates_total, // else branch is not needed in this example. // We just perform the actions.
Ejemplo 3. Ejemplo de espera de una nueva vela comparando prev_calculado y rates_total
Aquí, el cuerpo del operador if incluye dos comandos: Comment y return. Aquí se omite el operador else.
Experimentando con este ejemplo, podemos ver el mismo principio descrito anteriormente. Dentro de las llaves, podemos usar tantos operadores como queramos. Pero si se eliminan estos paréntesis, solo se ejecutará la llamada a la función Comment("Nothing to do") según la condición. En este caso, el operador de retorno se ejecutará incondicionalmente, mientras que el mensaje sobre su preparación para funcionar nunca aparecerá (recomiendo comprobarlo en un gráfico de minutos).
Mi recomendación a la hora de codificar el cuerpo de los operadores será la siguiente:
Use siempre llaves para resaltar el cuerpo de un operador if y otros operadores, aunque solo haya un operador en ese cuerpo.
Siga esta recomendación hasta que adquiera suficiente experiencia, por ejemplo, leyendo el código de otras personas en el CodeBase. Con los paréntesis, la depuración de aplicaciones resultará más visible y evitará la experiencia de pasar horas "pescando" errores obvios. (Es obvio que estos errores solo resultarán "evidentes" para los programadores experimentados). Además, si pone llaves incluso para un operador, será más fácil añadir acciones adicionales que no estaban previstas al escribir la primera versión del código, pero que han sido necesarias más tarde. Ejemplos de estos "añadidos" son las acciones para mostrar información de depuración o un mensaje al usuario, aunque, obviamente, la lista no es ni mucho menos exhaustiva.
Operador ternario
A veces usamos operadores de selección para asignar uno de dos valores posibles a una variable. Por ejemplo:
int a = 9; int b = 45; string message; if( (b%a) == 0 ) // (1) { message = "b divides by a without remainder"; // (2) } else { message = "b is NOT divisible by a"; // (3) } Print (message);
Ejemplo 4. Ejemplo de una condición adecuada para la conversión a un operador ternario
En este caso, podemos acortar ligeramente la entrada usando un operador ternario (que consta de tres partes):
int a = 9; int b = 45; string message; message = ( (b%a) == 0 ) /* (1) */ ? "b divides by a without remainder" /* (2) */ : "b is NOT divisible by a" /* (3) */ ; Print (message);
Ejemplo 5. Operador ternario
La forma aquí es la misma que en el operador if: condición -> (signo de interrogación) -> valor_si_condición_true-> (dos puntos - en lugar de else) -> valor_si_condición_false. Evidentemente, nadie puede impedirle usar operadores ternarios anidados si resulta adecuado para su tarea.
La principal diferencia entre este operador y el if "básico" es que el operador ternario debe retornar exactamente el valor correspondiente al tipo de la variable situada a la izquierda del signo de asignación y, por tanto, solo puede utilizarse en expresiones, mientras que el if "básico" contiene expresiones dentro de su cuerpo y no puede devolver un valor directamente.
El operador switch — case
Hay tareas en las que se debe elegir no entre dos, sino entre muchas más opciones. Por supuesto, puede organizar muchos operadores if anidados, pero la claridad del código se reducirá considerablemente. Para este tipo de tareas existe el operador switch (conmutador). Se construye según el siguiente patrón:
switch (integer_variable) { case value_1: operation_list_1; case value_2 operation_list_2; // … default: default_operation_list; }
Ejemplo 6. Estructura del operador switch-case
Funciona así. Si el valor "tipo_variable_entero" coincide con uno de los valores ("valor_1", "valor_2" ...), se ejecutará la lista de operaciones posterior a esta coincidencia y, a continuación, se ejecutarán todas las listas de operaciones posteriores. La mayoría de las veces no hará falta, bastará con ejecutar un solo bloque. Por lo tanto, después de cada lista de operaciones lo más habitual será añadir un operador break, que finalizará inmediatamente el operador switch. Si ninguna de las opciones coincide, se ejecutará la sección default. Esta sección deberá colocarse siempre al final de la lista.
En MQL5, el operador switch-case se usa más a menudo para procesar los errores de negociación en los asesores expertos, así como para manejar eventos como la pulsación de teclas o el desplazamiento del ratón. A modo de ejemplo, aquí tenemos un fragmento de código típico de una función para el procesamiento de errores:
void PrintErrorDescription() { int lastError = GetLastError(); // If (lastError == 0), there are no errors… if(lastError == 0) { return; // …no need to load cpu with unnecessary computations. } // If there are errors, output an explanatory message to the log. switch(lastError) { case ERR_INTERNAL_ERROR: Print("Unexpected internal error"); // You can select any appropriate action here break; case ERR_WRONG_INTERNAL_PARAMETER: Print("Wrong parameter in the inner call of the client terminal function"); break; case ERR_INVALID_PARAMETER: Print("Wrong parameter when calling the system function"); break; case ERR_NOT_ENOUGH_MEMORY: Print("Not enough memory to perform the system function"); break; default: Print("I don't know anything about this error"); } }
Ejemplo 7. Un esquema típico para la gestión de errores
Operador de ciclo con una condición previa (while)
Un ciclo es un operador que realiza acciones repetitivas.
Muy a menudo (casi todo el tiempo) en los problemas de programación hay secciones que requieren realizar algunas acciones de manera repetida pero con datos que cambian ligeramente. Por ejemplo, para buscar todos los elementos de un array, digamos, de un búfer de indicador, de forma que se pueda ver el funcionamiento del indicador en la historia. O leer los parámetros de un EA complejo desde un archivo, línea por línea. Y así sucesivamente.
Existen tres tipos de ciclos en MQL5, dependiendo de la tarea. Todos ellos son intercambiables y, si lo desea, siempre podrá sustituir uno por otro sin perder funcionalidad, pero sigue resultando más cómodo utilizar cada uno en su propio caso.
El primer operador será un ciclo while (mientras). Con frecuencia se denomina "ciclo precondicionado" porque comprueba la condición antes de que se ejecuten todas las demás acciones del cuerpo del ciclo. Gráficamente, tiene el aspecto que sigue:
Figura 3. Esquema del ciclo while
El patrón de código de este operador es muy simple:
while (condition)
action_if_condition_is_true;
Ejemplo 8. Patrón del ciclo While
Aquí "action_if_condition_is_true" es un operador simple o compuesto.
Si un programa encuentra un operador while, comprobará la condición, y si la condición es verdadera, realizará las acciones del cuerpo del ciclo y volverá a comprobar la condición. Así, el ciclo se ejecutará una y otra vez hasta que la condición se vuelva falsa. Dado que la condición se comprueba antes de que se ejecuten los operadores del cuerpo, puede haber situaciones en las que el ciclo no se ejecute ni una sola vez.
Normalmente, este operador se usa cuando no se puede calcular el número exacto de repeticiones. Por ejemplo, cuando es necesario leer un archivo de texto, el programa no puede saber cuántas líneas hay en él, y tiene que guiarse por una señal especial de fin de archivo. Mientras no se reciba la señal de fin de archivo, habrá que leer la línea siguiente y procesarla de alguna manera. Una vez recibida esa señal, dejaremos de leer. Y estas situaciones en programación resultan bastante frecuentes.
En cualquier caso, dentro del cuerpo del ciclo deberá cambiar al menos un parámetro que afecte a la condición entre paréntesis. Si no existe tal parámetro, el ciclo se volverá infinito, y entonces solo podrá interrumpirse cerrando la ventana del terminal. Cuando se ejecuta un ciclo infinito, cerrar el programa que lo está ejecutando podría no resultar a veces una tarea trivial, porque el procesador puede estar ocupado solo por nuestro programa, y no quedará tiempo para otras tareas como reaccionar ante las pulsaciones del teclado o a los movimientos del ratón. En este, el peor de los casos, solo un reinicio por hardware del ordenador (con un botón) servirá de ayuda. Por lo tanto, compruebe cuidadosamente sus ciclos y asegúrese de que deja al menos una oportunidad para finalizar el programa. Por ejemplo, añada una condición a la comprobación (&& ! IsStopped() ) que detendrá el ciclo si se finaliza el programa.
Para comparar la forma de escribir diferentes ciclos, tomaremos como ejemplo el propio problema de sumar números del 1 al 5. Supongamos que no conocemos las progresiones.
Usando un ciclo while, este problema se resolverá de la siguiente manera:
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations while (a <= 5) // While a is less than 5 { sum += a; // Add the value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Ejemplo 9. Uso del ciclo while
Aquí, por supuesto, el número de pasadas puede contarse fácilmente. Pero para entender la forma de funcionamiento del ciclo, creo que el ejemplo sigue siendo adecuado.
Ciclo con postcondición (do... while)
El ciclo do… while (ejecutar… mientras) utiliza un comprobador de condiciones después de que todas las acciones de su cuerpo hayan sido elaboradas. Gráficamente, este ciclo puede representarse de la forma siguiente:
Figura 4. Esquema del ciclo do-while
El patrón de operadores del código no es más complicado que el anterior:
do actions_if_condition_is_true; while (condition);
Ejemplo 10. Plantilla de operador do-while
A diferencia de while, en esta operador las acciones dentro del cuerpo del ciclo siempre se ejecutan al menos una vez. Pero, en esencia, nada más cambia: el programa ejecuta una acción, comprueba la condición que le indica si debe volver al principio y, si es necesario, ejecuta de nuevo todas las acciones del cuerpo del ciclo.
No existen tantas tareas en las que esta forma de ciclo resulte conveniente como para un ciclo con una precondición, pero las hay. En nuestro ámbito, podría tratarse, por ejemplo, de comprobar los instrumentos comerciales en la ventana de observación de mercado (Market watch). El algoritmo podría ser como el siguiente:
- tomamos el primer instrumento de la lista (si estamos absolutamente seguros de que existe),
- hacemos lo que tengamos que hacer con él (por ejemplo, comprobar las órdenes establecidas)
- y, a continuación, comprobamos si otros instrumentos también están en la lista;
- si hay, debemos coger un segundo instrumento, luego un tercero... Y así sucesivamente hasta agotar la lista.
La tarea de la suma con esta forma de ciclo tendrá casi el mismo aspecto que en el caso anterior, solo que se sustituirán dos líneas, la descripción del propio ciclo:
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations do // Execute { sum += a; // Add value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } while (a <= 5) // While a is less than 5 //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Ejemplo 11. Uso del ciclo do-while
El ciclo for
El ciclo for (para) es el más común porque se utiliza para recorrer todo tipo de secuencias, en situaciones en las que se puede calcular fácilmente el número de elementos a recorrer. Su plantilla se organiza del siguiente modo:
for (initialize_counter ; conditions ; change_counter)
action_if_condition_is_true;
Ejemplo 12. Plantilla para el ciclo for
A diferencia de las formas de ciclo anteriores, en el ciclo for podemos ver claramente qué parámetro está cambiando (en la plantilla lo he llamado contador de parámetros) y según qué ley, de modo que podemos concentrarnos en resolver la tarea principal (por ejemplo, enumerar los elementos de un array) en el cuerpo del ciclo.
En esencia, este ciclo es igual que while, es decir, en él también se realiza una comprobación antes de ejecutar los operadores del cuerpo. El problema de la suma de números usando este ciclo podría resolverse de la siguiente manera:
//--- Variables declaration int a; // do NOT initialize the variable parameter, only describe // (this is optional as you can describe it in the loop header) int sum = 0; // result string message = "The sum of "; // a shorter message for the user, // as the value is not yet known //--- Perform main operations for (a=1; a<=5; a++)// For (each `a` from 1 to 5) /loop header/ { sum += a; // Add `a` to the sum if ( a != 1) // the first value does not contain "+" { message += " + "; // add "+" before all sequence members starting from the second } message += string (a); // Add sequence elements to the message // Changes to the counter are described not here, but in the loop header as the last parameter. } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Ejemplo 13. Uso del ciclo for
En este ejemplo, vemos muchos más cambios en comparación con los anteriores (que resaltamos en amarillo). Una vez más, me gustaría llamar su atención sobre el hecho de que todos los pasos de trabajo con el contador (el parámetro variable) se colocan en el encabezado: inicialización (a=1), comprobación de la condición para la ejecución del ciclo (a<=5) (si la condición es verdadera, el funcionamiento principal - el cuerpo del ciclo - se ejecutará), y luego, al final, el valor (a++) se cambia y la condición se comprueba de nuevo.
Operadores break y continue
A veces no es necesario ejecutar todos los pasos de un ciclo.
Por ejemplo, cuando se busca un valor dentro de un array, no es necesario recorrerlo hasta el final si el valor que se busca está en el medio. En este caso, cuando necesitamos interrumpir completamente la ejecución de un ciclo, podemos utilizar el operador break (interrumpir), que finaliza inmediatamente la ejecución del ciclo y pasa a la siguiente acción que sigue al ciclo. Por ejemplo:
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { break; } message += " " + IntegerToString( i ); } Print(message); // Result: 0 1 (no further execution)
Ejemplo 14. Uso del operador break
También hay casos en los que no es necesario ejecutar el cuerpo del ciclo en determinadas condiciones, pero el ciclo no finaliza, simplemente se saltan determinadas situaciones. Por ejemplo, si en el ejemplo 14 no queremos que salga un dos, pero necesitamos todos los demás dígitos, tendremos que modificar un poco el código sustituyendo break por continue (continuar). El operador continue hace que el programa proceda inmediatamente a comprobar la siguiente condición para los ciclos while y do-while, o al siguiente cambio de parámetro para un ciclo for.
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { continue; } message += " " + IntegerToString( i ); } Print(message); // Output: 0 1 3 (the number 2 is skipped)
Ejemplo 15. Uso del operador continue
El operador break puede usarse tanto dentro de un ciclo como en los operadores case; el operador continue solo puede utilizarse dentro de ciclos.
Y todo gira en torno a los operadores. Queda por ver cómo pueden ayudar estos operadores a crear un programa real.
Ahora diremos un par de palabras sobre el asistente de creación de ficheros (en el modo de creación de indicadores)
Es de esperar que ya haya descubierto cómo crear indicadores usando el asistente, incluso después del primer artículo. No obstante, seguiré describiendo brevemente las pantallas del asistente para crear indicadores, haciendo hincapié en cosas ligeramente diferentes a cuando describí esta herramienta en nuestro primer artículo.
Me saltaré la primera ventana del asistente. No vemos nada interesante o nuevo en comparación con el primer artículo. La segunda ventana tampoco es nueva, pero ahora ya conoce los ajustes globales del programa, que podemos organizar mediante parámetros de entrada. A veces resulta más visual introducir estos parámetros con la ayuda del asistente, ya que se utiliza de todos modos... Permítame recordarle que el nombre del parámetro puede ser cualquiera. Prefiero añadir el prefijo inp_ para que al revisar el código pueda ver inmediatamente dónde uso exactamente estos parámetros.
Figura 5. Añadimos los parámetros de programa mediante el asistente
Lo segundo que quiero enfatizar, es la elección de la forma del método OnCalculate (Figura 6). Permítame recordarle que el método OnCalculate es el método principal de cualquier indicador en MQL5. Se llama cada vez que el terminal calcula este indicador. Por consiguiente, esta función puede tomar un número distinto de parámetros de entrada, dependiendo de su elección en este paso del asistente.
Figura 6. Selección de la forma del método OnCalculate
La opción superior resulta adecuada para la mayoría de las situaciones, porque la función en este caso adopta arrays automáticamente generados open, high, low, close, volume, time, y el usuario puede hacer lo que quiera con ellos.
Sin embargo, hay casos especiales en los que queremos dar al usuario la posibilidad de elegir qué curva usar para calcular este indicador. Por ejemplo, un indicador de media móvil puede calcularse partiendo del precio del mínimo, del máximo o, en general, a partir de la curva de un indicador ya existente en el gráfico. En este caso, deberemos elegir la opción inferior, y entonces un array con los datos de la curva se transmitirá a OnCalculate, mientras que la pestaña "parámetros" aparecerá en la ventana de parámetros de entrada en el indicador compilado, precisamente para seleccionar la curva deseada. La figura 7 muestra las ventanas de inicio de los indicadores listos para las variantes superior (Upper) e inferior (Lower).
Figura 7. Comparación de las ventanas de inicio de los indicadores con diferentes formas de OnCalculate
Un último punto del asistente sobre el que quiero llamar su atención. En el último paso de la ventana de diálogo al crear un indicador, podemos elegir cómo se mostrará el indicador (figura 8). Para mostrar el indicador, deberemos añadir el llamado "búfer": un array especial que contiene datos para el dibujado. Existen muchas opciones de dibujado: desde una simple línea (como una media móvil) hasta diferentes histogramas y velas multicolores. Y si le apetece de verdad, podrá incluso hacer sus propios trazados (pero esto, por supuesto, no lo ofrece el asistente).
Figura 8. Parámetros del dibujado del indicador
En cualquier caso, si quiere que el indicador dibuje algo y que al mismo tiempo los resultados de su trabajo sean fácilmente accesibles para otros programas (por ejemplo, asesores expertos), necesitará añadir al menos un búfer.
El búfer es simplemente un array de números que almacenan los datos calculados por nuestro indicador para cada vela. En general, estos datos no están necesariamente destinados de forma específica al dibujado. A veces, los búferes almacenan colores o algunos datos de cálculo intermedios necesarios para los cálculos de otros búferes.
Los búferes también se pueden añadir de forma manual, pero entonces tendrá que hacer un trabajo adicional poco complicado pero monótono. Ahora, para los programadores principiantes, recomiendo que los búferes se sigan añadiendo usando el asistente.
Normalmente, el dibujado de líneas requiere un mínimo de un búfer para cada curva que se vaya a calcular. Dibujar flechas suele requerir al menos dos búferes: uno para cada dirección. Aunque se puede hacer con uno, si el icono de la flecha no indica la dirección, y solo habrá un valor calculado para cualquier vela. Existen casos más complicados, pero hablaré de ellos, si alguna vez lo hago, en otros artículos. Por ahora, la información facilitada será suficiente.
Y ahora vamos a crear un indicador
- con el nombre InsideOutsideBar,
- con un parámetro que se llamará inp_barsTypeSelector, tendrá el tipo int y un valor por defecto 0,
- seleccionaremos el formato superior de la función OnCalculate (donde está la lista de arrays) para el indicador de la tercera pantalla del asistente,
- y añadiremos dos búferes de dibujado en la sección "Plots" ("Plots" en la figura 8) con los nombres Up y Down y el tipo de dibujado "Flecha".
Análisis del código del indicador generado por el asistente
Si ha hecho todo correctamente en la sección anterior, debería obtener un código como este:
//+------------------------------------------------------------------+ //| InsideOutsideBar.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot Up #property indicator_label1 "Up" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrMediumPurple #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- input parameters input int inp_barsTypeSelector=0; //--- indicator buffers double UpBuffer[]; double DownBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,159); PlotIndexSetInteger(1,PLOT_ARROW,159); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+
Ejemplo 16. Código de indicador generado por el asistente de código MQL5
El primer bloque, como de costumbre, será el encabezado que contiene los parámetros. Los parámetros para describir la autoría ya no interesan. A continuación viene una línea que informa de que el indicador se colocará en la ventana del gráfico: tampoco es nada nuevo. Pero las dos líneas al final de este bloque son un mensaje al compilador de que nuestro indicador tiene dos búferes para los cálculos y dos búferes para el dibujado (como comprenderá, en este caso son los mismos búferes, aunque en general el número de búferes puede diferir):
#property indicator_buffers 2 #property indicator_plots 2
Ejemplo 17. Descripción de los búferes de indicador para el cálculo y el dibujado
El siguiente bloque de parámetros describe cómo debe ser el patrón creado por cada búfer:
//--- plot Up #property indicator_label1 "Up" // Display name of the buffers #property indicator_type1 DRAW_ARROW // Drawing type - arrow #property indicator_color1 clrMediumPurple // Arrow color #property indicator_style1 STYLE_SOLID // Line style - solid #property indicator_width1 1 // Line width (arrow size) //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1
Ejemplo 18. Parámetros de dibujado
El siguiente bloque de código describe los parámetros de entrada de nuestro indicador. En nuestro caso, el parámetro será solo uno: inp_barsTypeSelector:
//--- input parameters input int inp_barsTypeSelector=0;
Ejemplo 19. Parámetros de entrada del indicador
Luego viene el código que crea una variable para los búferes de dibujado. Tenga en cuenta que para este búfer se utiliza un array dinámico con elementos de tipo double. De hecho, se trata de un conjunto de niveles de precios sobre los que se trazará la curva del indicador:
//--- indicator buffers double UpBuffer[]; double DownBuffer[];
Ejemplo 20. Variable para el búfer de dibujado
Y luego viene la descripción de las dos funciones: OnInit y OnCalculate.
OnInit es exactamente la misma función que en los scripts. También se inicia justo después de que se inicie el indicador -antes de que se ejecuten todas las demás funciones- y se ejecuta exactamente una vez. En este caso, el asistente ha escrito en él dos llamadas de la función estándar SetIndexBuffer, diseñadas para vincular nuestro array al búfer de dibujado del indicador. Además, se han asignado iconos a ambas flechas mediante la función PlotIndexSetInteger. La función OnInit no toma ningún parámetro y retorna el estado de éxito de la inicialización (en este caso, siempre "exitoso").
OnCalculate: como hemos escrito antes, esta es una función que se llama en cada tick o cada vez que otros programas tratan de utilizar nuestro indicador. Ahora está vacía, y es en ella donde escribiremos las acciones principales de nuestro indicador.
La función adopta un conjunto de variables como parámetros:
- número total de barras del gráfico (rates_total);
- número de barras calculadas previamente por el indicador (prev_calculated). En la primera ejecución el valor prev_calculated es igual a cero, pero la implementación de la función creada por el asistente devuelve el número total de barras en este tick al final del funcionamiento, y al principio de la siguiente ejecución el terminal transmitirá este valor a la función como prev_calculated. Por cierto, uno de los algoritmos que conozco para determinar una nueva vela se basa en esto: si rates_total==prev_calculated, significará que la vela es la misma y no hay que calcular nada, pero si no son iguales, iniciaremos otros pasos;
- multitud de matrices: precios, tiempo, volúmenes, spreads... De hecho, esto es todo lo que un tráder puede necesitar si escribe su propio indicador y no utiliza los de otros. Tenga en cuenta que los arrays se transmiten por referencia (ya que los arrays no se pueden transmitir por valor), pero tienen un modificador const para indicar al compilador que los datos de estos arrays son inmutables.
Y ahora podemos empezar a programar nuestro indicador.
Programación del indicador
Lo primero que quiero hacer es ajustar un poco los parámetros del indicador. Solo podemos tener dos tipos de búsqueda: una barra interior o una barra exterior. Implementaremos esto como una enumeración global justo después de las directivas del preprocesador:
enum BarsTypeSelector { Inside = 0, // Inside bar Outside = 1 // Outside bar };
Ejemplo 21. Enumeración del tipo de cálculo
Y cambiamos el tipo y el valor por defecto de nuestro parámetro de entrada:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside;
Ejemplo 22. Modificación del tipo y el valor por defecto de un parámetro de entrada
Ahora ampliaremos un poco las posibilidades de configuración de nuestro indicador. Añadiremos la posibilidad de cambiar la apariencia del icono y la separación visible del icono de la barra. Para hacer esto manualmente (para que recuerde que añadir parámetros es muy fácil), añadiremos dos variables más a la sección de parámetros de entrada:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside; input int inp_arrowCode=159; input int inp_arrowShift=5;
Ejemplo 23. Variables adicionales para la configuración de la apariencia
Permítame recordarle que los parámetros de entrada en sí no afectan al funcionamiento del indicador de ninguna forma, deben utilizarse en alguna parte. Para que nuestros cambios funcionen ahora, modificaremos la función OnInit:
int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(1,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, inp_arrowShift); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -inp_arrowShift); //--- return(INIT_SUCCEEDED); }
Ejemplo 24. Cambios en la función OnInit
Ya hemos terminado con los cambios "cosméticos", ahora pasaremos a programar las acciones principales de la función OnCalculate. Para hacer la entrada de código un poco más compacta, omitiremos el encabezado de esta función y describiremos inmediatamente el código que hace el trabajo útil.
/******************************************************************************************** * * * Attention! All arrays in the code are NOT series, so we have larger numbers on the right. * * * ********************************************************************************************/ //--- Description of variables int i, // Loop counter start; // Initial bar for historical data const int barsInPattern = 2; // For proper preparation we need to know, // how many bars will be involved in one check // I've added a constant to avoid the presence of // "magic numbers" that come from nowhere, // we could use the #define directive instead of the constant //--- Check the boundary conditions and set the origin if(rates_total < barsInPattern) // If there are not enough bars to work return(0); // Do nothing if(prev_calculated < barsInPattern+1)// If nothing has been calculated yet { start = barsInPattern; // Set the minimum possible number of bars to start searching } else { start = rates_total — barsInPattern; // If the indicator has been running for some time, // Just count the last two bars } //--- To avoid strange artifacts on the last candlestick, initialize the last elements of the arrays // with EMPTY_VALUE UpBuffer[rates_total-1] = EMPTY_VALUE; DownBuffer[rates_total-1] = EMPTY_VALUE; //--- for(i = start; i<rates_total-1; i++) // Start counting from the starting position // and continue until there are no more closed barsя // (If we needed to include the last - unclosed - bar, // we would set the condition i<=rates_total-1) { // First, let's clear both indicator buffers (initialize to an empty value) UpBuffer[i] = EMPTY_VALUE; DownBuffer[i] = EMPTY_VALUE; if(inp_barsTypeSelector==Inside) // If the user wants to display inside bars { // Check if the current bar is inside if(high[i] <= high[i-1] && low[i] >= low[i-1]) { // And if yes, we mark the previous (larger) candlestick UpBuffer[i-1] = high[i-1]; DownBuffer[i-1] = low[i-1]; } } else // If outside bars are needed { // Check if the current bar is outside if(high[i] >= high[i-1] && low[i] <= low[i-1]) { // Mark the current candlestick if necessary UpBuffer[i] = high[i]; DownBuffer[i] = low[i]; } } } //--- return value of prev_calculated for the next call return(rates_total);
Ejemplo 25. Cuerpo de la función OnCalculate
Me parece que ya hemos comentado el código lo suficiente, así que no tiene sentido "repasarlo". Si no estoy en lo cierto, puede formular las preguntas necesarias en los comentarios. Permítame recordarle una vez más que deberá insertar este código en la función OnCalculate, entre las llaves de apertura y cierre, borrando todo lo que había antes (he incluido el operador return en el ejemplo, en la última línea). El código de funcionamiento completo del indicador se adjunta al artículo.
La figura 9 muestra el funcionamiento de este indicador. A la izquierda se marcan en rosa las barras predecesoras de las barras internas, y a la derecha, las barras de las externas.
Figura 9. Funcionamiento del indicador InsideOutsideBar. A la izquierda, las barras interiores; a la derecha, las exteriores.
Conclusión
Una vez que domine los operadores descritos en este artículo, podrá entender la mayoría de los programas escritos en MQL5, y escribir sus propios algoritmos de cualquier complejidad. De hecho, cualquier tipo de programación puede reducirse a las operaciones más sencillas, como la aritmética o la asignación, la selección entre varias opciones (if o switch) y la repetición del fragmento necesario el número de veces necesario (ciclos). Las funciones son una forma de organizar cómodamente las operaciones; los objetos son una forma de organizar cómodamente las funciones y sus datos externos.
Desde luego, usted no es un novato. Pero de ahí a ser un profesional en MQL5 todavía queda un trecho. Por ejemplo, tiene que averiguar cómo utilizar indicadores ya escritos en su trabajo, y cómo crear asesores expertos que negocien en su lugar. También existen muchos matices a considerar en la plataforma MQL5, cuyo estudio vale la pena para poder hacer todo lo que desee: interfaces gráficas para sus asesores expertos, indicadores inusuales como "Kagi" o "Renko", cómodos servicios y estrategias comerciales automatizadas rentables... Así que este ciclo continuará. En el próximo artículo, veremos cómo escribir EAs con todo el detalle que pueda mostrar. Luego vendrá mi visión de la OOP en el contexto de MQL5. Bueno, entonces la parte "general" estará terminada, y después de eso probablemente profundizaremos en algunas sutilezas de la plataforma y la biblioteca de código estándar suministrada con el terminal en la carpeta Include.
Lista de artículos anteriores de la serie:
- Aprendiendo MQL5 de principiante a profesional (Parte I): Comenzamos a programar
- Aprendiendo MQL5 de principiante a profesional (Parte II): Tipos de datos básicos y uso de variables
- Aprendiendo MQL5 de principiante a profesional (Parte III): Tipos de datos complejos y archivos de inclusión
- Aprendiendo MQL5 de principiante a profesional (Parte IV): Sobre arrays, funciones y variables globales del terminal
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/15499





- 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