English Русский Português
preview
Aprendiendo MQL5 de principiante a profesional (Parte V): Operadores básicos para redirigir el flujo de comandos

Aprendiendo MQL5 de principiante a profesional (Parte V): Operadores básicos para redirigir el flujo de comandos

MetaTrader 5Ejemplos | 23 abril 2025, 13:22
26 0
Oleh Fedorov
Oleh Fedorov

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.
Podemos considerar todos los casos de interacciones de variables utilizando estos operadores mediante tablas:
Tabla 1. Negación lógica (!)
Tabla 2. Multiplicación lógica (&&)
Tabla 3. Suma lógica (||)
not
and
or

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:

  1. Todos los operadores aritméticos se ejecutarán en primer lugar
    • *, /, %
    • +, -
  2. A continuación, se ejecutarán los operadores de comparación (==, !=, >, <, >=, <=).
  3. 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 (||);
  4. 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í:

Operador de ramificación

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í:

Ilustración de la ramificación

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:

Esquema del ciclo while

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:

Esquema del ciclo do... while

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.

Añadimos los parámetros de entrada

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.

Selección de la forma del método OnCalculate

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).

Comparación de las ventanas de inicio de los indicadores con diferentes formas de OnCalculate

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).

Parámetros de dibujado del indicador

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.

Funcionamiento del indicador InsideOutsideBar

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:

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/15499

Archivos adjuntos |
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Introducción a Connexus (Parte 1): ¿Cómo utilizar la función WebRequest? Introducción a Connexus (Parte 1): ¿Cómo utilizar la función WebRequest?
Este artículo es el comienzo de una serie de desarrollos para una biblioteca llamada “Connexus” para facilitar las solicitudes HTTP con MQL5. El objetivo de este proyecto es brindarle al usuario final esta oportunidad y mostrarle cómo utilizar esta biblioteca auxiliar. Mi intención era hacerlo lo más sencillo posible para facilitar el estudio y ofrecer la posibilidad de desarrollos futuros.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Redes neuronales en el trading: Transformer para nubes de puntos (Pointformer) Redes neuronales en el trading: Transformer para nubes de puntos (Pointformer)
En este artículo analizaremos los algoritmos necesarios para utilizar métodos de atención en la resolución de problemas de detección de objetos en nubes de puntos. La detección de objetos en nubes de puntos es bastante importante para muchas aplicaciones del mundo real.