
Aprendiendo MQL5 de principiante a profesional (Parte IV): Sobre arrays, funciones y variables globales del terminal
Introducción
Este artículo es la continuación de la serie para principiantes. En artículos anteriores, analizamos con gran detalle los métodos para describir los datos almacenados dentro de nuestro programa. A estas alturas el lector debería saber:
- que los datos pueden almacenarse en variables o constantes;
- que el lenguaje MQL5 es un lenguaje fuertemente tipado, lo que significa que cada fragmento de datos en un programa tiene su propio tipo, el cual es necesario para que el compilador asigne la memoria correctamente y evite algunos errores lógicos;
- que los tipos de datos pueden ser simples (básicos) y complejos (definidos por el usuario);
- que para usar datos en un programa MQL5, es necesario declarar al menos una función;
- que cualquier bloque de código se puede trasladar a archivos separados, y luego estos archivos se pueden incluir en el proyecto usando la directiva de preprocesador #include.
En este artículo abordaremos tres temas bastante globales:
- Los arrays de datos, que finalizan la historia principal sobre los datos dentro de un programa.
- Las variables globales del terminal, que permiten intercambiar datos simples entre diferentes programas MQL5.
- Además, discutiremos algunas sutilezas sobre el funcionamiento de las funciones y cómo interactúan con las variables.
Información básica sobre arrays
Un array es una variable que contiene una secuencia de datos del mismo tipo.
Para describir un array, deberemos describir su tipo y nombre de variable y luego escribir corchetes. El número de elementos en una secuencia dada se puede especificar entre corchetes.
int myArray[2]; // Describes an array of integers containing two elements
Ejemplo 1. Descripción de un array estático.
Las secuencias en los programas MQL5 deben describirse con bastante frecuencia. Esto incluye todos los precios de la historia, la hora de inicio de cada vela y los volúmenes... En general, siempre que haya conjuntos de datos, los arrays pueden ser una buena opción.
La numeración de elementos dentro de un array en MQL5 siempre comienza desde 0. Por consiguiente, el número del último elemento del array siempre será igual al número de sus elementos menos uno (lastElement = size - 1).
Para acceder a cualquier elemento del array, simplemente especificaremos el número de ese elemento entre corchetes:
// Fill the array with values: myArray[0] = 3; myArray[1] = 315; // Output the last element of this array to the log: Print(myArray[1]); // 315
Ejemplo 2. Uso de los elementos del array.
Y, por supuesto, cualquier array se puede inicializar cuando se declara, al igual que una estructura, usando las llaves:
double anotherArray[2] = {4.0, 5.2}; // A two-element array is initialized with two values Print( DoubleToString(anotherArray[0],2) ); // Output 4.00
Ejemplo 3. Inicialización de un array durante la descripción.
Arrays multidimensionales
Un array puede almacenar otros arrays dentro de sí mismo. Estos arrays anidados se denominan "multidimensionales".
Un ejemplo visual simple de arrays multidimensionales podrían ser las páginas de un libro. Los caracteres se agrupan en una línea, la primera dimensión; las líneas se agrupan en párrafos, la segunda dimensión; y una página sería un conjunto de párrafos, la tercera dimensión.
Figura 1. Los caracteres se agrupan en una línea: un array unidimensional.
Figura 2. Las líneas se agrupan en párrafos: un array bidimensional.
Figura 3. Los párrafos se agrupan en páginas: un array tridimensional.
Para describir dichos arrays en MQL5, simplemente añadiremos corchetes para cada nueva dimensión. Los paréntesis para los contenedores “exteriores” se colocan a la izquierda de los “interiores”. Por ejemplo, los arrays que se muestran en las figuras 1 a 3 podrían describirse y usarse de la siguiente manera:
char stringArray[21]; // One-dimensional array char paragraphArray[2][22]; // Two-dimensional array char pageArray[3][2][22]; // Three-dimensional array // Filling a two-dimensional array paragraphArray[0][0]='T'; paragraphArray[0][1]='h'; // … paragraphArray[1][20]='n'; paragraphArray[1][21]='.'; // Access to an arbitrary element of a two-dimensional array Print(CharToString(paragraphArray[1][3])); // Will print "a" (why?)
Ejemplo 4. Descripción de arrays multidimensionales para las figuras 1-3.
El número total de dimensiones del array no deberá superar 4. El número máximo de elementos en cualquier dimensión será 2147483647.
La inicialización de arrays multidimensionales durante la descripción será tan simple como la de los unidimensionales. Las llaves simplemente enumerarán los elementos de cada array:
int arrayToInitialize [2][5] = { {1,2,3,4,5}, {6,7,8,9,10} }
Ejemplo 5. Inicialización de arrays multidimensionales.
Arrays dinámicos
No todos los arrays permiten saber inmediatamente cuántos elementos contendrán. Por ejemplo, los arrays que contienen la historia del terminal o las listas de transacciones cambiarán con el tiempo. Por ello, además de los arrays estáticos descritos en los apartados anteriores, MQL5 permite crear arrays dinámicos, es decir, aquellos que pueden cambiar el número de sus elementos durante el funcionamiento del programa. Estos arrays se describen exactamente de la misma manera que los estáticos, solo que el número de elementos no se indicará entre corchetes:
int dinamicArray [];
Ejemplo 6. Descripción de los arrays dinámicos
Un nuevo array así declarado no contendrá elementos, su longitud será 0 y, por lo tanto, no será posible acceder a sus elementos. Si el programa intenta hacer esto, se producirá un error crítico y el programa finalizará. Por consiguiente, antes de trabajar con dicho array, deberemos establecer su tamaño utilizando la función especial incorporada ArrayResize :
ArrayResize(dinamicArray, 1); // The first parameter is the array and the second is the new size ArrayResize(dinamicArray,1, 100); // The third parameter is the reserved (excess) size
Ejemplo 7. Cambiando el tamaño de un array dinámico
En la ayuda verá que la función puede adoptar hasta tres parámetros; sin embargo el tercer parámetro tiene un valor por defecto, por lo que podrá omitirlo, como hemos hecho en la primera línea de nuestro ejemplo.
El primer parámetro de esta función será necesariamente el array que estamos modificando. El segundo será el nuevo tamaño del array. No creo que haya ningún problema con esto. El tercer parámetro será el "tamaño reservado".
El "tamaño reservado" se utilizará si conocemos el tamaño máximo de nuestro array. Por ejemplo, según las condiciones de nuestro problema, no podrá haber más de 100 valores en el array, pero se desconoce cuántos exactamente. Luego podremos usar el parámetro reserve_size en esta función y establecerlo en 100, como hemos hecho en el ejemplo 7 en la segunda línea. En este caso, al llamarse, la función reservará memoria sobrante para 100 elementos, aunque el tamaño real del array seguirá siendo el indicado en el segundo parámetro (1 elemento).
¿Y por qué tantas complicaciones? ¿Por qué no añadir simplemente un elemento cada vez cuando sea necesario?
La respuesta simple es: para acelerar nuestro programa.
Una respuesta más detallada requiere explicaciones más laboriosas. Pero, en resumen, el asunto es que cada vez que usamos la función ArrayResize sin el tercer parámetro, nuestro programa solicita al sistema operativo memoria adicional. Esta asignación de memoria supone una operación bastante larga (desde el punto de vista del procesador) y no importa si la memoria se necesita solo para un elemento o para muchos a la vez. Cuanto menor sea la frecuencia con la que tenga que hacer esto nuestro programa, mejor. Es decir, será mejor reservar mucho espacio de golpe y luego rellenarlo, que asignar poco espacio y luego ampliarlo. No obstante, aquí también deberemos considerar que la RAM es un recurso limitado y, por lo tanto, siempre habrá que encontrar un equilibrio entre la velocidad de funcionamiento y el tamaño de los datos.
Si conoce las limitaciones de sus arrays, será mejor indicarlo explícitamente al programa declarando arrays estáticos o reservando memoria usando el tercer parámetro de la función ArrayResize. Si lo desconoce, entonces el array será definitivamente dinámico y no deberá especificar necesariamente el tercer parámetro de la función ArrayResize, aunque puede hacerse, ya que si el tamaño real del array es mayor que el reservado, MQL5 simplemente asignará la memoria real necesaria.
Una vez que se haya redimensionado el array, como en el ejemplo 7, podremos cambiar los datos dentro de él:
dinamicArray[0] = 3; // Now our array contains exactly one element (see example 7), its index is 0
Ejemplo 8. Usando un array modificado
Cuando trabajamos con arrays dinámicos, la tarea más frecuente consiste en agregar datos al final de este array y no cambiar algo en el medio (aunque esto también sucede). Como en cualquier momento el programa no sabe cuántos elementos hay en el array en ese momento particular, necesitaremos una función especial para averiguarlo. Esta se llama ArraySize. La función tomará un parámetro (un array) y retornará un valor entero (el número de elementos de este array). Y una vez sepamos el tamaño exacto del array dinámico (que nos dará esta función), añadir un elemento se volverá bastante simple:
int size, // Number of elements in the array lastElemet; // Index of the last element char stringArray[]; // Our example dynamic array. // Immediately description its size is 0 (array cannot contain elements) // add an element to the end. size = ArraySize(stringArray); // Find the current size of the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `H`; // Write the value // Now add one more element. The sequence of actions is absolutely the same. size = ArraySize(stringArray); // Find the current size if the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `i`; // Write the value // Note that when adding the second element in this way, only on line changes: // the one that writes the actual value to a specific cell. // // This means that the solution can be written in a shorter form. For example, by creating a separate custom function for it.
Ejemplo 9. Añadiendo un elemento al final de un array dinámico.
Las funciones ArraySize y ArrayResize se usan constantemente al trabajar con arrays dinámicos, generalmente en combinación, como en el ejemplo 9. Le recomiendo que se familiarice con otras funciones menos utilizadas pero no por ello menos útiles.
Y, para finalizar esta sección, me gustaría señalar que también es posible crear un array dinámico multidimensional en el lenguaje MQL5, pero solo el primer índice puede ser indefinido.
int a [][12]; // It's ok // int b [][]; // Compilation error: only the first index can be dynamic
Ejemplo 10. Array dinámico multidimensional.
Si realmente necesita hacer que varios índices sean dinámicos, podrá crear una estructura cuyo único campo sea un array dinámico y luego hacer un array de dichas estructuras.
struct DinArray // Structure containing a dynamic array { int a []; }; DinArray dinamicTwoDimensions []; // Dynamic array of structures ArrayResize( dinamicTwoDimensions, 1 ); // Set size of the outer dimension ArrayResize( dinamicTwoDimensions[0].a, 1 ); // Set size of the internal dimension dinamicTwoDimensions[0].a[0] = 12; // Use cell to write data
Ejemplo 11. Array con dos índices dinámicos.
En principio, existen otros métodos para solucionar este problema. Por ejemplo, podemos crear nuestra propia clase o utilizar una que ya exista en la biblioteca estándar. Sin embargo, dejaremos el tema del trabajo con clases para futuros artículos.
Arrays de series
Los precios de apertura, cierre, máximos y mínimos, los volúmenes reales y de ticks, los spreads, la hora de inicio de la vela y los valores del indicador en cada vela en MQL5 se denominan series o series temporales.
Los programadores de MQL5 no tienen acceso directo a estas secuencias, pero el lenguaje ofrece la capacidad de copiar estos datos a cualquier variable dentro de nuestro programa utilizando un conjunto de funciones especiales predefinidas (enumeradas en la Tabla 1 ).
Si necesitamos, digamos, los precios de cierre, primero deberemos crear nuestro propio array en el que se almacenarán estos precios; luego llamaremos a la función CopyClose y le transmitiremos el array creado como último parámetro. La función copiará la serie estándar en nuestra variable, y luego estos datos se podrán usar de la forma habitual: utilizando índices entre corchetes.
Pero trabajar con series temporales es algo diferente a trabajar con todos los demás arrays. Así ha ocurrido históricamente.
En la memoria, las series se almacenan del mismo modo que todas las demás: de la más antigua a la más nueva, pero las funciones para trabajar con las series de la tabla 1 numeran los elementos de la serie temporal en orden inverso, de derecha a izquierda. Para todas estas funciones la vela cero será la que se encuentra más a la derecha, la actual, la que aún no se ha completado. Pero los arrays "ordinarios" no saben esto, y por lo tanto para ellos esta vela será la última. Y esto supone una gran confusión...
Vamos a intentar comprender esto con la ayuda de imágenes.
Figura 4. Dirección de la numeración en los arrays ordinarios (flecha verde) y en las series (flecha azul).
Figura 5. Copiado de series en arrays ordinarios.
La figura 4 muestra la diferencia en la dirección de numeración de las series y los arrays ordinarios.
La figura 5 muestra esquemáticamente el copiado de series en arrays ordinarios, por ejemplo, utilizando la función CopyRates o algo similar (ver tabla 1). El orden físico de los elementos en la memoria es el mismo para los arrays ordinarios y para las series, pero la numeración cambia y el primer elemento de la serie después del copiado se convierte en el último del array ordinario.
A veces puede resultar incómodo programar considerando siempre estos matices. Hay dos formas de combatir estas dificultades:
- La función incorporada ArraySetAsSeries permite cambiar la dirección de numeración de cualquier array dinámico. Toma dos parámetros: el array en sí y el indicador de su "serialidad" (true/false). Si su algoritmo implica copiar datos que siempre comienzan con la última vela, con frecuencia puede asignar el array de destino como una serie y luego encontrar que las funciones estándar y su algoritmo usan los mismos índices para trabajar.
- Si su algoritmo implica copiar pequeños fragmentos de datos de un lugar arbitrario en el gráfico, especialmente si conocemos con precisión su número en cada paso del algoritmo (por ejemplo, tomamos los precios de cierre de tres barras: la primera en cerrar -es decir, con índice de serie 1- y las dos siguientes, con índices de serie 2 y 3), entonces será mejor aceptar que la numeración estará en direcciones diferentes y simplemente programar con más cuidado. Una posible solución sería crear una función aparte para verificar los valores requeridos e intentar usarla en cualquier expresión.
En el siguiente ejemplo intentaremos ilustrar todo lo anterior usando código.
datetime lastBarTime; // We'll try to write the last candle's time into this variable datetime lastTimeValues[]; // The array will store the time of the last two candles. // It's dynamic so that it can be made into a time series to test indices // Get the start time of the current candlestick using the iTime function lastBarTime = iTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0 // Current candlestick ); Print("Start time of the 0 bar is ", lastBarTime); // Get the start time of the last two candlesticks using the CopyTime function CopyTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0, // Start with position 0 2, // Take two values lastTimeValues // Write them to array lastTimeValues ("regular") array ); Print("No series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array to log. The separator between elements is a semicolon ArraySetAsSeries(lastTimeValues,true); // Convert the array into a time series Print("Series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array again. Note the order of the data /* Script output: 2024.08.01 09:43:27.000 PrintArraySeries (EURUSD,H4) Start time of the 0 bar is 2024.08.01 08:00:00 2024.08.01 09:43:27.051 PrintArraySeries (EURUSD,H4) No series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 04:00:00; 2024.08.01 08:00:00 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) Series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 08:00:00; 2024.08.01 04:00:00 */
Ejemplo 12. Funciones de prueba para trabajar con series temporales.
Tabla 1. Lista de funciones para acceder a series temporales. Para todas las funciones mencionadas, la numeración de elementos comenzará por la derecha, desde la última vela (sin finalizar).
Función | Acción |
---|---|
CopyBuffer | Obtiene en un array los datos del búfer indicado desde el indicador especificado |
CopyRates | Obtiene en un array de estructuras MqlRates los datos históricos para el símbolo y el periodo especificados |
CopySeries | Obtiene a la vez varias series temporales sincronizadas para el símbolo/periodo especificado en la cantidad especificada. La lista de todos los arrays que se deben rellenar se transmitirá al final, mientras que su orden deberá corresponderse con los campos de la estructura MqlRates. |
CopyTime | Obtiene datos históricos sobre la hora de apertura de la barra para el símbolo y periodo correspondiente en un array |
CopyOpen | Obtiene en un array los datos históricos sobre el precio de apertura de las barras para el símbolo y periodo correspondiente |
CopyHigh | Obtiene en un array los datos históricos sobre el precio máximo de las barras para el símbolo y el periodo correspondientes. |
CopyLow | Obtiene en un array los datos históricos sobre el precio mínimo de las barras para el símbolo y el periodo correspondientes. |
CopyClose | Obtiene en un array los datos históricos sobre el precio de cierre de las barras para el símbolo y periodo correspondiente |
CopyTickVolume | Obtiene en un array los datos históricos sobre los volúmenes de ticks para el símbolo y periodo correspondiente |
CopyRealVolume | Obtiene en un array los datos históricos sobre los volúmenes comerciales para el símbolo y el periodo correspondientes |
CopySpread | Obtiene en un array los datos históricos de spreads para el símbolo y periodo correspondientes |
CopyTicks | Obtiene en un array los ticks en formato MqlTick |
CopyTicksRange | Obtiene en un array los ticks en el rango de fechas especificado |
iBarShift | Retorna el índice de la barra de la serie en la que cae la hora especificada |
iClose | Retorna el precio de cierre de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iHigh | Retorna el valor del precio máximo de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iHighest | Retorna el índice del mayor valor encontrado (desplazamiento relativo a la barra actual) del gráfico correspondiente |
iLow | Retorna el valor del precio mínimo de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iLowest | Retorna el índice del menor valor encontrado (desplazamiento relativo a la barra actual) del gráfico correspondiente |
iOpen | Retorna el valor del precio de apertura de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iTime | Retorna el valor de hora de apertura de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iTickVolume | Retorna el volumen de ticks de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iRealVolume | Retorna el valor del volumen real de la barra (especificado por el parámetro shift) del gráfico correspondiente |
iSpread | Retorna el valor de spread para la barra especificada por el parámetro shift en el gráfico correspondiente. |
Creación (detallada) de funciones
Cualquier función en un programa MQL5 se creará utilizando una plantilla de la que hablamos un poco en el primer artículo de la serie:
ResultType Function_Name(TypeOfParameter1 nameOfParameter1, TypeOfParameter2 nameOfParameter2 …) { // Description of the result variable and other local variables ResultType result; // … //--- // Main actions are performed here //--- return resut; }
Ejemplo 13. Plantilla de descripción de funciones.
ResultType (y ParameterTypes) será cualquier tipo de datos válido. Esto podría ser un tipo int, un double, un nombre de clase o enumeración, o cualquier otra cosa que conozca.
Es posible que no haya parámetros ni resultados explícitos de la función. Luego, en lugar del tipo de resultado o dentro de los paréntesis, en lugar de la lista de parámetros, se insertará la palabra void. Traducido de forma literal significa "espacio vacío, agujero".
Obviamente, si ResultType es nulo, entonces no será necesario retornar datos y, en consecuencia, no será necesario especificar la última línea dentro de las llaves (resultado del retorno) y tampoco será necesario describir la variable de resultado.
Bueno, aquí hay algunas reglas más simples:
- Los nombres de función y de parámetros deben seguir las convenciones de los identificadores.
- La declaración return devuelve solo un valor, y no más. Pero existen soluciones alternativas que discutiremos un poco más adelante.
- Una función no se puede describir dentro de otra función, solo desde fuera, fuera de todas las funciones.
- Es posible declarar varias funciones con el mismo nombre, pero con un número diferente (o distintos tipos) de parámetros y/o diferentes tipos de resultados de retorno. Lo principal es poder entender exactamente cuándo se debe usar cada función. Si podemos distinguir y explicar estas diferencias a alguien que no esté familiarizado con nuestro código, entonces el compilador también podrá hacerlo.
A continuación le mostramos algunos ejemplos que ilustran cómo se pueden describir las funciones:
//+------------------------------------------------------------------+ //| Example 1 | //| Comments are often used to describe what a function does, | //| what date it needs and why. For example, like this: | | //| | //| | //| The function returns difference between two integers. | //| | //| Parameters: | //| int a is a minuend | //| int b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ int diff(int a, int b) { // The action is very simple, we do not create a variable for the result. return (a-b); } //+------------------------------------------------------------------+ //| Example 1a | //| The function returns the difference between two real numbers. | //| | //| Function name is as in the previous example, but parameter type | //| differs | //| | //| Parameters: | //| double a is a minuendе | //| double b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ double diff(double a, double b) { return (a-b); } //+------------------------------------------------------------------+ //| Example 2 | //| Illustrates the use of "void". | //| Calls (uses) the diff function | //+------------------------------------------------------------------+ void test() { // You can do whatever you want. // For example, use the function from Example 1. Print(diff(3,4)); // the result is -1 // Since when calling the diff function, integer parameters were // passed in parentheses, the result is also int. // Now let's try to call the same function with double precision parameters Print(diff(3.0,4.0)); // the result is -1.0 // Since the function is declared as "void", the "return" statement is not needed } //+------------------------------------------------------------------+ //| Example 3 | //| The function has no parameters to process. We could use | //| empty parentheses as in the previous example or explicitly use | //| the word "void" | //| Return value: | //| string nameForReturn is some name , always the same | | //+------------------------------------------------------------------+ string name(void) { string nameForReturn="Needed Name"; return nameForReturn; }
Ejemplo 14. Ejemplos de descripciones de funciones según una plantilla.
Debemos entender que el trabajo con una función consta de dos etapas: la descripción y el uso. Al describir una función, esta función todavía no hace nada. Esto es simplemente un algoritmo formal, un esquema de acciones. Por ejemplo, la función diff del ejemplo 14 podría describirse en palabras de la siguiente manera:
- Tomamos dos números enteros (los que sean, no los conocemos de antemano).
- Dentro del algoritmo, asignamos a uno de ellos el nombre a, y al otro el nombre b.
- Restamos el número b del número a.
- El resultado de los cálculos (cualquiera, desconocido de antemano) se entrega a quien lo haya solicitado (retorno al punto de llamada).
Cuando hablamos de "cualquier valor", esta expresión puede sustituirse por las palabras "parámetro formal". Las funciones se crean para efectuar formalmente determinadas acciones con cualquier dato.
Los parámetros que se usan al describir funciones se denominan "formales".
En el ejemplo 14, la función diff contiene dos parámetros formales, las demás no tienen ninguno. En general, una función puede poseer muchos parámetros formales (hasta 63).
Pero para obtener un resultado específico, la función deberá ser llamada (es decir, utilizada), como, por ejemplo, la función test en el ejemplo 14, que ha llamado a las funciones Print y diff. Aquí se usan valores muy específicos, actuales en el momento de la llamada: el contenido de variables o constantes, literales (como en mi ejemplo), los resultados de otras funciones...
Los parámetros que transmitimos a una función en el momento de la llamada se denominan "actuales".
Para llamar a cualquier función, debemos especificar su nombre y enumerar los parámetros reales entre paréntesis. Los parámetros reales deben corresponderse con los parámetros formales en cuanto al tipo y la cantidad.
Así, en el ejemplo 14, la función de prueba usa exactamente dos números enteros o exactamente dos dobles para llamar a la función diff. De cometerse un error e intentar escribir uno o tres parámetros, se obtiene un error de compilación.
Alcance variable
Al declarar variables, debemos considerar exactamente dónde se declaran.
-
Si declaramos una variable dentro de una función (incluidos los parámetros formales de esa función), otras funciones no podrán "ver" (y por lo tanto usar) esa variable. Normalmente, dicha variable "nace" en el momento en que se llama a la función y "muere" cuando la función finaliza su trabajo. Una variable de este tipo se llama local.
En general, podemos decir que el alcance de una variable está determinado por la "entidad" completa en nuestro código. Por ejemplo, si una variable se declara entre llaves, solo será visible dentro de las llaves que forman el bloque, pero no fuera de ese bloque. Los parámetros formales de una función pertenecen a la "entidad" de la función, por lo que serán visibles solo dentro de esa función. Y así sucesivamente. La vida útil de dicha variable es igual a la vida útil de la "entidad" a la que pertenece. Por ejemplo, una variable declarada dentro de una función se crea cuando se llama esa función y se destruye cuando la función finaliza.
void OnStart() { //--- Local variable inside a function is visible to all blocks of that function, but not beyond it int myString = "This is local string"; // Curly braces describe a block inside a function { int k=4; // Block local variable - visible only inside curly braces Print(k); // It's ok Print (myString); } // Print(k); // Compilation error. The variable k does not exist outside the curly brace block. }
Ejemplo 15. Variable local dentro del bloque de llaves.
- Si una variable se declara desde fuera, fuera de la declaración de cualquier función, podrá ser utilizada por todas las funciones de nuestra aplicación. En este caso, la vida útil de dicha variable será igual a la vida útil del programa. Una variable de este tipo se denominará global.
int globalVariable = 345; void OnStart() { //--- Print (globalVariable); // It's ok }
Ejemplo 16. Las variables globales serán visibles para todas las funciones de nuestro programa.
- Una variable local puede tener el mismo nombre que una variable global, pero en este caso la variable local ocultará la variable global para la función dada.
int globalVariable=5; void OnStart() { int globalVariable=10; // The variable is described according to all the rules, including the type. // If the type were not declared, this expression would change the global variable //--- Print(globalVariable); // The result is 10 - that is, the value of the local variable Print(::globalVariable); // The result is 5. To print the value of a global variable, not the local one, // we use two colons before the name }
Ejemplo 17. Nombres "superpuestos" de variables locales y globales. La variable local oculta la global.
Variables estáticas
Existe un caso especial al describir variables locales.
Como hemos mencionado antes, las variables locales pierden sus valores tras finalizarse la función. Normalmente este es exactamente el comportamiento esperado. Sin embargo, hay situaciones en las que debemos guardar el valor de una variable local incluso después de que la función haya completado su ejecución.
Por ejemplo, organizar un contador de llamadas a una función. O bien (una tarea más común para un tráder) la organización de una función para verificar el inicio de una nueva vela, para lo cual será necesario obtener el valor de la hora actual en cada tick y compararlo con el valor conocido previamente. Obviamente, podemos crear una variable global para cada uno de estos contadores, pero cada variable global aumentará la probabilidad de que aparezca un error, ya que estos contadores solo son necesarios para una función y no resulta deseable que otros los cambien, o incluso los "vean".
En tales casos, cuando una variable local debe vivir tanto como una global, se usan variables estáticas. Se describen con la misma precisión que las normales, solo que se agrega la palabra static antes de la descripción, como se hace dentro de la función HowManyCalls en el siguiente ejemplo:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- HowManyCalls(); HowManyCalls(); HowManyCalls(); } //+------------------------------------------------------------------+ //| The function counts the number of requests | //+------------------------------------------------------------------+ void HowManyCalls() { //--- Variable description. The variable is local, but its lifetime is long. static int counter=0; // Since 'static' keyword is used, the variable is initialized only // before he first function call // (more precisely, before the OnInit function call) //--- Main actions counter++; // During program execution, the value will be stored till the end //--- Operation result Print( IntegerToString(counter)+" calls"); } // Script output: // 1 calls // 2 calls // 3 calls
Ejemplo 18. Usando una variable estática.
El ejemplo contiene dos funciones: HowManyCalls, que usa una variable estática para contar la cantidad de llamadas a ella y registra el resultado del recuento, y OnStart, que llama a HowManyCalls tres veces seguidas.
Transmisión de los parámetros de la función por valor y por referencia
Por defecto, la función solo utiliza copias de los datos que se le transmiten como parámetros (los programadores dicen que en este caso los datos se transmiten "por valor"). Por consiguiente, incluso si escribimos algo en un parámetro de variable dentro de la función, no pasará nada con los datos originales.
Si queremos que los datos iniciales cambien desde dentro de la función, el parámetro formal modificado deberá designarse con un símbolo especial "&" (ampersand). Este método de descripción de parámetros se llama transmisión "por referencia".
Para ilustrar cómo una función puede modificar datos externos, crearemos un nuevo archivo de script que contenga el siguiente código:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart(void) { //--- Declare and initialize two local variables int first = 3; int second = 77; //--- Print their values BEFORE all changes Print("Before swap: first = " + first + " second = " + second); //--- Use the Swap function, which takes data by reference Swap(first,second); //--- See what happened Print("After swap: first = " + first + " second = " + second); //--- //--- Apply the CheckLocal function to the received data //--- This function takes parameters by value CheckLocal(first,second); //--- Print the result again Print("After CheckLocal: first = " + first + " second = " + second); } //+------------------------------------------------------------------+ //| Swaps the values of two integer variables | //| Data is passed by reference, so the originals will be modified | //+------------------------------------------------------------------+ void Swap(int &a, int& b) // It can be done in any way, both positions are correct { int temp; //--- temp = a; a = b; b = temp; } //+------------------------------------------------------------------+ //| Takes parameters by value, that is why changes happen | //| only locally | //+------------------------------------------------------------------+ void CheckLocal(int a, int b) { a = 5; b = 10; } // Script output: // Before swap: first = 3 second = 77 // After swap: first = 77 second = 3 // After CheckLocal: first = 77 second = 3
Ejemplo 19. Transmitiendo parámetros por referencia.
Este código describirá tres funciones cortas: OnStart, Swap y CheckLocal.
CheckLocal toma los datos por valor y por lo tanto trabaja con copias, Swap toma dos parámetros por referencia y por lo tanto trabaja con originales. La función OnStart declara dos variables locales, luego imprime el valor de esas variables, llama a las funciones Swap y CheckLocal, y muestra los resultados de la interacción imprimiendo el valor de sus variables locales en el diario de registro después de cada interacción. Una vez más, llamaré su atención sobre el hecho de que la función Swap ha cambiado los datos que se le han transmitido, pero CheckLocal no ha podido hacerlo.
Debemos considerar que todas las variables de tipos complejos (como enumeraciones, estructuras, objetos, etc., así como cualquier array) siempre deberán transmitirse por referencia.
Al intentar transmitir dichas variables por valor, el compilador generará un error.
Y una vez más enumeraremos brevemente las reglas básicas de interacción de variables y funciones:
- Las variables globales en el lenguaje MQL5 se pueden usar directamente desde cualquier función, incluyendo el cambio de sus valores.
- Las variables locales solo resultan accesibles dentro del bloque donde se declaran.
- Si un parámetro formal describe la transmisión de datos "por valor", la función no podrá cambiar los datos de origen, incluso si cambiamos internamente el valor de la variable del parámetro. Los datos transferidos "por referencia", sin embargo, pueden cambiar en su ubicación original.
- Si una variable global y una local tienen el mismo nombre, la variable local tendrá prioridad (en otras palabras, la variable local redefinirá la variable global).
- La vida útil de las variables globales es igual a la vida útil del programa, y las variables locales serán iguales a la vida útil del bloque donde se describen.
Valores por defecto para los parámetros de funciones formales
A los parámetros formales se les puede asignar un valor por defecto.
Por ejemplo, si creamos una función de registro, es posible que necesitemos distinguir los mensajes de la función de todos los demás mensajes del terminal. La forma más fácil de hacerlo es añadiendo un prefijo al comienzo del mensaje original y un sufijo al final. La cadena en sí siempre debe especificarse, de lo contrario se perderá el significado de la función. Pero los "añadidos" pueden ser estándar, aunque puedan cambiar.
A continuación, ofrecemos el código más simple para ilustrar esta idea:
//+------------------------------------------------------------------+ //| Add a prefix and suffix to a sting | //+------------------------------------------------------------------+ string MakeMessage( string mainString, string prefix="=== ", string suffix=" ===" ) { return (prefix + mainString + suffix); }
Ejemplo 20. Descripción de una función con parámetros formales por defecto
Al llamar a esta función, podemos omitir uno o ambos parámetros que tengan valores por defecto. Si estos parámetros no se especifican explícitamente, la función usará los valores especificados en la descripción. Por ejemplo:
Print ( MakeMessage("My first string") ); // Default prefix and suffix Print ( MakeMessage("My second string", "~~ ") ); // Prefix changed, suffix remains unchanged Print ( MakeMessage("My third string", "~~ ", " ~~") ); // Both actual parameter have been changed // Script output: // === My first string === // ~~ My first string === // ~~ My first string ~~
Ejemplo 21. Los parámetros reales que tienen valores por defecto se pueden omitir.
Los parámetros con valores por defecto solo pueden aparecer en una fila y deben describirse después de todos los demás parámetros que no contengan dichos valores.
Cómo hacer que una función retorne múltiples resultados
Ya hemos dicho antes que el operador de retorno solo puede devolver un resultado. Además, la función no puede retornar arrays. ¿Pero qué pasa si realmente lo necesitamos? ¿Qué sucede si, por ejemplo, necesitamos utilizar una función para calcular tanto la hora como el precio de cierre de una vela u obtener una lista de instrumentos disponibles? Primero, intente responder por su cuenta y luego compare su respuesta con lo que verá a continuación.
Para solucionar el problema planteado en el título, puede utilizar uno de los siguientes métodos:
- Crear un tipo de datos complejo (como una estructura) y retorne una variable de ese tipo.
- Utilizar la transmisión de parámetros por referencia. Obviamente, esto no le ayudará a retornar estos datos con la ayuda del operador return, pero le permitirá escribir cualquier valor y luego usarlo.
- Utilizar variables globales (no recomendado). Este método resulta similar al anterior, pero es potencialmente más peligroso para el código. Será mejor utilizar lo mínimo las variables globales y solo cuando sea absolutamente imposible prescindir de ellas. Pero si realmente quiere, puede intentarlo...
Modificadores de variables globales: input y extern
También existen "casos especiales" cuando se usan variables globales. Incluyo entre ellos:
- la descripción de los parámetros de entrada de nuestro programa utilizando el modificador input;
- el uso del modificador extern.
Parámetros de entrada (variables de entrada)
Cada parámetro de entrada de un programa escrito en MQL5 se describe como una variable global (fuera de todas las funciones) y se designa usando la palabra clave input, que se coloca al comienzo de la descripción.
input string smart = "The smartest decision"; // The window will contain this description
Ejemplo 22. Descripción de los parámetros de entrada.
Normalmente, la columna izquierda de la ventana de propiedades muestra el nombre de la variable. No obstante, si hay un comentario en la misma línea donde se describe esta variable, como en el ejemplo 22, se mostrará en lugar del nombre de la variable.
En los programas escritos en MQL5, las variables designadas como input solo se pueden leer; no se puede escribir nada en ellas. Los valores de estas variables solo se pueden configurar en la descripción (en el código) o desde la ventana de diálogo de las propiedades del programa.
Si creamos un asesor experto o un indicador, los valores de dichas variables normalmente se pueden optimizar utilizando el simulador de estrategias. Sin embargo, si desea excluir algunos parámetros de la optimización, deberá agregar la letra s o el modificador estático al comienzo de la palabra input:
input double price =1.0456; // optimize sinput int points =15; // NOT optimize static input int unoptimizedVariable =100; // NOT optimize
Ejemplo 23. Uso del modificador sinput para excluir una variable de la optimización en el simulador.
Si queremos que el usuario seleccione valores de una lista en algún campo de entrada, deberemos crear una enumeración para cada uno de dichos campos. Los comentarios de línea para los elementos de las enumeraciones también funcionan, por lo que en lugar de nombres como POINT_PRICE_CLOSE podemos mostrar "Precio de cierre del punto" en cualquier idioma humano. Desafortunadamente, no existe una forma fácil de seleccionar el idioma del texto para el nombre del campo (comentarios). Para cada idioma que utilicemos, tendremos que compilar un archivo aparte, por eso la mayoría de los programadores experimentados prefieren utilizar el idioma universal (inglés).
Los parámetros se pueden agrupar visualmente para que resulte más fácil usarlos. Para especificar un nombre de grupo, se usará una descripción especial:
input group "Group Name"
Ejemplo 24. Encabezado de un grupo de parámetros.
A continuación, le mostramos un ejemplo completo que ilustra todas estas posibilidades:
#property script_show_inputs // Enumeration. Allows you to create a list box. enum ENUM_DIRECTION { // All inline comments next to lines describing parameters, // will be displayed instead of the names in the parameters window DIRECTION_UP = 1, // Up DIRECTION_DN = -1, // Down DIRECTION_FL = 0 // Unknown }; input group "Will be optimized" input int onlyExampleName = 10; input ENUM_DIRECTION direction = DIRECTION_FL; // Possible directions list input group "Will not be optimized" sinput string something = "Something good"; static input double doNotOptimizedMagickVariable = 1.618; // Some magic //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- } //+------------------------------------------------------------------+
Ejemplo 25 . Distintas opciones para describir los parámetros de entrada.
Figura 6. Ventana de diálogo de parámetros. Los grupos (grupo de entrada) se resaltan en color. Las flechas verdes indican los valores de los comentarios sustituidos en lugar de los nombres de las variables.
La figura 6 muestra la ventana de diálogo de opciones generado a partir del código del ejemplo 25. Puede que se vea ligeramente distinto en distintas computadoras, pero en cualquier caso, los encabezados de grupo estarán resaltados (los hemos resaltado en azul en la imagen). También puede ver que los parámetros que no tienen comentarios de línea usarán nombres de variables. Si hay comentarios, el compilador los usará en lugar de los nombres de las variables, como en nuestras celdas indicadas por las flechas verdes. De todas formas, le propongo comparar el código del ejemplo 25 con la imagen: espero que lo entienda todo.
Y una cosa más: no todos los principiantes notan los íconos en el lado izquierdo del tipo de datos de cada parámetro. Por ejemplo, el parámetro llamado "Possible directions list" en la figura tiene el tipo de datos enumeración y su icono ( ) insinúa la lista. Los datos de este campo solo se pueden seleccionar de una lista limitada de opciones, que es lo que necesitábamos. El resto de iconos también resultan "elocuentes", creo que no será difícil descifrarlos.
El nombre de cualquier parámetro no deberá tener más de 63 caracteres (esto es mucho, en realidad los nombres suelen ser mucho más cortos).
La longitud del valor de un parámetro de línea no puede exceder los 254 caracteres, y cuanto más largo sea el nombre de este parámetro, más corto podrá ser el contenido, ya que se almacenan en la memoria como una línea continua.
Debemos recordar esta limitación, especialmente si estamos especificando la dirección de alguna página web que sea importante para el programa. A veces las direcciones resultan muy largas, y si este es su caso, intente transmitir la dirección de alguna otra manera, por ejemplo, codifíquela como una variable global, pero no como un parámetro. Obviamente, existen mejores soluciones, como usar archivos o "pegar" una dirección de varios fragmentos, pero lo principal es recordar el número 254 para los valores de los parámetros...
El segundo caso especial son las variables "externas".
Cuando los desarrolladores escriben un programa grande dividido en varios archivos, hay casos en que se declara una variable global en un archivo y el programa necesita acceder a ella desde otros. Al mismo tiempo, por alguna razón, no quiero incluir archivos con la directiva #include… El MetaEditor percibe cada archivo por separado y, por consiguiente, en este caso, no puede servir de ayuda.
La mayoría de las veces, esta situación surge cuando se usan parámetros de entrada (los descritos en la subsección anterior).
Aquí es donde la palabra clave extern acude al rescate.
extern bool testComplete;
Ejemplo 26. Descripción de variables externas.
Estas variables no se pueden inicializar en el archivo dado y, durante la compilación, la dirección de la memoria de esta variable probablemente será reemplazada por una variable global "real" con el mismo nombre, si el compilador puede encontrar una. Sin embargo, las funciones pueden acceder libremente a estos datos "formales", e incluso modificarlos, y el IDE no tendrá ninguna dificultad con la sustitución automática.
Variables globales del terminal
Las variables locales y globales descritas en las secciones anteriores resultan accesibles únicamente para el programa actual. Todos los demás programas no pueden usar estos datos. Pero hay situaciones en las que los programas necesitan intercambiar datos entre sí, o es necesario garantizar que los valores de las variables se guarden incluso después de apagar el terminal.
Un ejemplo de una tarea para el intercambio puede ser un indicador muy simple en el que sea necesario mostrar la cantidad de fondos en la divisa de depósito necesaria para abrir una posición. Parece que todo es sencillo. Después de navegar un poco por la tabla de contenidos de ayuda, descubrimos que MQL5 tiene una función especial OrderCalcMargin, que calcula la cantidad requerida. Intentamos aplicarla y obtenemos… un resultado decepcionante. El problema es que no se pueden usar funciones comerciales en los indicadores. Está prohibido físicamente, a nivel del compilador. Y OrderCalcMargin es precisamente una herramienta comercial…
Por ello, tendremos que tomar caminos indirectos. Una opción sería escribir un script o servicio que calcule las cantidades requeridas y luego escriba estas cantidades en variables terminales. Y luego nuestro indicador leerá estos datos, no los calculará. Este truco resulta posible porque, a diferencia de los indicadores, los scripts y servicios pueden negociar (vea la tabla en el primer artículo de la serie).
Veamos cómo se podría implementar semejante intercambio. Primero, crearemos un archivo de script usando el wizard. Llamaremos al archivo "CalculateMargin.mq5".
Para acceder a las variables terminales, existe un conjunto de funciones predefinidas cuyos nombres comienzan con el prefijo GlobalVariable.
Usando estas y la función OrderCalcMargin para hacer que los datos que necesitamos estén disponibles para los indicadores, crearemos un nuevo script:
//+------------------------------------------------------------------+ //| CalculateMargin.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 script_show_inputs //--- Script input parameters input double requiredAmount = 1; // Number of lots //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Description of local variables string symbolName = Symbol(); // Name of the current symbol string terminalVariableName; // Terminal variable name double marginBuy, marginSell; // Margin values (buy and sell) double currentPrice = iClose(symbolName,PERIOD_CURRENT,0); // Current price to calculate margin bool okCalcBuy, okCalcSell; // Indication of success when calculating margin up or down //--- Main operations // Calculate Buy margin okCalcBuy = OrderCalcMargin( ORDER_TYPE_BUY, // Order type symbolName, // Symbol name requiredAmount, // Required volume in lots currentPrice, // Order open price marginBuy // Result (by reference) ); // Calculate Sell margin okCalcSell = OrderCalcMargin( ORDER_TYPE_SELL, // Sometimes different amounts are needed for opening up and down symbolName, requiredAmount, currentPrice, marginSell ); //--- Operation result // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Write the data. If the global terminal variable does not exist, it will be created. GlobalVariableSet ( terminalVariableName, // Where to write marginBuy // What to write ); // Now we create another name - for the Sell details terminalVariableName = symbolName + "SellAmount"; // Write data for Sell. If there was no variable with the name stored in terminalVariableName, // create one GlobalVariableSet(terminalVariableName,marginSell); } //+------------------------------------------------------------------+
Ejemplo 31. Script para calcular los fondos en la divisa de depósito necesarios para comprar o vender 1 lote y guardar estos datos en las variables globales del terminal.
Aquí usaremos la función estándar GlobalVariableSet para escribir los datos en variables del terminal. Creo que en el ejemplo dado el uso de estas funciones resulta obvio. La única aclaración: la longitud del nombre de una variable global del terminal no deberá superar los 63 caracteres.
Si aplicamos este script a cualquier gráfico, no veremos ningún resultado obvio de inmediato. Sin embargo, podremos ver lo que hemos obtenido presionando la tecla <F3> o seleccionando "Herramientas -> Variables globales" en el menú de la terminal.
Figura 7. Menú de variables del terminal.
Después de seleccionar este elemento de menú, aparecerá una ventana con una lista de todas las variables del terminal:
Figura 8. Ventana con lista de variables globales del terminal
En la figura 8 podemos ver que solo hemos ejecutado el script en el par EURUSD, por lo que solo resultan visibles dos variables: las cantidades a comprar y vender, que en este caso son las mismas.
Ahora crearemos un indicador que utilizará estos datos y, al mismo tiempo, veremos cómo las funciones estándar utilizan los principios de trabajo con variables discutidos anteriormente.
El archivo indicador se llamará "GlobalVars.mq5". El trabajo principal de este indicador se realizará dentro de la función OnInit, que se ejecuta inmediatamente después de que el programa se inicie. También hemos agregado la función OnDeinit, que elimina los comentarios cuando borramos el indicador del gráfico. La función OnCalculate, que es obligatoria para cada indicador y se ejecuta en cada tick, también estará presente en este indicador, por supuesto, pero no se utilizará.
//+------------------------------------------------------------------+ //| GlobalVars.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 //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Description of local variables string symbolName = Symbol(); // Symbol name string terminalVariableName; // Name of global terminal value double buyMarginValue, sellMarginValue; // Buy and Sell value bool okCalcBuy; // Indication that everything is OK when calling one of the variants of the GlobalVariableGet function //--- Main operations // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Use the first method to get the value of a global variable. // To get the result, the parameter is passed by reference okCalcBuy = GlobalVariableGet(terminalVariableName, buyMarginValue); // Change the name of the terminal variable - for Sell details terminalVariableName = symbolName + "SellAmount"; // Second way to get the result: return value sellMarginValue = GlobalVariableGet(terminalVariableName); //--- Output the result as a comment on the chart Comment( "Buy margin is " + DoubleToString(buyMarginValue) // Buy margin value, the second parameter // of the DoubleToString function is omitted +"\n" // Line break +"Sell margin is " + DoubleToString(sellMarginValue,2) // Margin value for sale, indicated the number of // decimal places ); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| The function will be called when the program terminates. | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Очищаем комментарии Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function (not used here) | //+------------------------------------------------------------------+ 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 32. Usando las variables globales del terminal en el indicador.
En este indicador de demostración necesitaremos leer las variables del terminal una vez y finalizar el trabajo. Por lo tanto, el código principal se colocará en la función OnInit. El ejemplo puede parecer grande y aterrador, pero en realidad es muy fácil de leer, sobre todo porque la mayor parte está compuesta por comentarios. Permítame describir nuevamente con palabras lo que sucede en la función OnInit:
- En el primer bloque de esta función declararemos todas las variables que necesitaremos más adelante.
- A continuación, crearemos un nombre para la variable global del terminal.
- Y leeremos el valor de la variable global del terminal que necesitaremos en la variable local correspondiente.
- Luego generaremos el nombre de la segunda variable y también leeremos su valor en la siguiente línea.
- Y la última acción consistirá en mostrar un mensaje al usuario como comentario en la esquina superior izquierda (ver figura 9).
Tenga en cuenta que la función GlobalVariableGet tiene dos opciones para su llamada: usando el valor de retorno o un parámetro transmitido por referencia, mientras que la función DoubleToString tiene un parámetro con un valor por defecto. Si escribimos el texto de ejemplo en el editor para comprobar la funcionalidad del código, en lugar de copiarlo a través del portapapeles, MetaEditor le solicitará estos matices.
Figura 9. Resultado del funcionamiento del indicador.
Debido a que hemos utilizado distintas formas de llamar a la función DoubleToString para generar la salida, los comentarios en las líneas superior e inferior se ven ligeramente distintos. Al formatear el mensaje para la línea superior, hemos omitido el segundo parámetro de la función DoubleToString. Este parámetro deberá especificar el número de caracteres después del punto decimal y, por defecto, será 8. Para la línea inferior, hemos especificado este valor explícitamente y le hemos pedido al programa que genere dos caracteres.
Tenga en cuenta que el indicador debe colocarse en el gráfico donde se ha aplicado el script, y solo después del script, para garantizar que las variables globales de la terminal existan cuando se esté ejecutando. De lo contrario, se producirá un error al ejecutarse el indicador y los comentarios no aparecerán.
Por lo tanto, la función GlobalVariableSet se usará para escribir las variables del terminal, mientras que GlobalVariableGet se utilizará para leerlas. Estas funciones son las más usadas por los programadores, pero las demás también son útiles, por lo que recomiendo leer al menos su lista en la ayuda (link al inicio de la sección).
Conclusión
Repasemos nuevamente la lista de temas tratados hoy. Si algún punto de la lista no le queda claro, vuelva al lugar correspondiente en el artículo y léalo de nuevo, ya que este material supone la base para todo el resto del trabajo (bueno, tal vez con la excepción de las variables globales del terminal, a menudo podemos prescindir de ellas, pero comprenderlas de forma general no le causará dificultades). Entonces, este artículo ha abarcado:
- Los arrays:
- los hay estáticos y dinámicos;
- unidimensionales y multidimensionales;
- los arrays estáticos se pueden inicializar utilizando literales (entre llaves);
- Para trabajar con arrays dinámicos, debemos utilizar funciones estándar para cambiar los tamaños y descubrir el tamaño actual.
- Las variables en relación con las funciones son
- locales (de corta duración, excepto las estáticas); podemos agregarles un modificador estático;
- y globales (de larga vida); podemos agregarles modificadores externos y de entrada.
- Los parámetros de función se pueden transmitir
- por enlace;
- por valor.
- Existen variables globales del terminal. A diferencia de las variables de programa simplemente globales, podemos utilizar estas para intercambiar datos entre distintos programas. Hay un conjunto especial de funciones para su uso.
Si recuerda todo esto y ninguno de los elementos de la lista le provoca confusión, entonces ya no podrá considerarse un novato: ya tiene una buena base sobre la que trabajar. Todo lo que le queda es descubrir cómo usar los operadores básicos, así como comprender la peculiaridades del lenguaje MQL5 al escribir indicadores y expertos en relación con otros lenguajes, y ya podrá comenzar a escribir programas útiles. Es cierto que para alcanzar el nivel "profesional" necesitará comprender una docena de temas más, incluida la programación orientada a objetos, pero todos estos temas seguirán, de una forma u otra, dependiendo de la base, que después de leer este artículo ya está medio lista.
Que la guía de ayuda le acompañe...
Anteriores artículos 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
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/15357





- 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