English Русский Deutsch 日本語 Português
preview
Equilibrio de riesgos en la negociación simultánea de varios instrumentos comerciales

Equilibrio de riesgos en la negociación simultánea de varios instrumentos comerciales

MetaTrader 5Ejemplos | 26 julio 2024, 14:38
26 0
Aleksandr Seredin
Aleksandr Seredin

Este artículo abordará el tema del equilibrio de riesgos al negociar con varios instrumentos comerciales intradía al mismo tiempo. El propósito de este artículo es permitir al usuario escribir código para herramientas de equilibrado desde cero, e introducir a los usuarios experimentados a otras implementaciones (posiblemente no usadas previamente) de viejas ideas. Para ello, estudiaremos la definición de riesgo, elegiremos criterios de optimización, tocaremos aspectos técnicos sobre la implementación de nuestra solución, analizaremos el conjunto de capacidades estándar de los terminales para dicha implementación, y también veremos otras posibles formas de integrar este algoritmo en nuestra infraestructura programática.


Criterios para equilibrar los instrumentos comerciales según el riesgo

Al negociar simultáneamente con varios instrumentos financieros, consideraremos dos factores principales como criterios de equilibrio del riesgo.

  • El valor del tick en el instrumento
  • La volatilidad media diaria del instrumento

El valor del tick será el valor en la divisa de la variación mínima del precio de un instrumento para un lote estándar de dicho instrumento. Tendremos en cuenta este criterio porque el valor de tick puede variar significativamente en distintos instrumentos. Por ejemplo, de 1,27042 para el EURGBP a 0,61374 para el par AUDNZD.

La volatilidad media diaria será la variación característica del precio de un instrumento durante un día. Este valor en los distintos instrumentos será menos constante que el anterior criterio seleccionado y podrá variar con el tiempo en función de la fase en que se encuentre el mercado. Por ejemplo, el par EURGBP suele promediar unos 336 pips, mientras que el par CHFJPY puede promediar 1271 pips el mismo día, es decir, casi cuatro veces más. Los datos aquí presentados caracterizarán los valores "habituales", "más probables", de la volatilidad de los precios, sin considerar la volatilidad "paranormalmente" alta de los instrumentos en determinados momentos del mercado. Es decir, cuando el precio empieza a moverse con mucha fuerza en una dirección, como, por ejemplo, en la siguiente figura para el instrumento USDJPY.

Figura 1: Aumento de la volatilidad del instrumento en el gráfico diario

Figura 1. Aumento de la volatilidad del instrumento en el gráfico diario

Este comportamiento de los instrumentos puede provocar riesgos muy graves para el depósito, que se describen detalladamente en el artículo "Cómo reducir los riesgos del tráder". En este artículo propondremos la tesis de que es imposible protegerse contra ese riesgo usando instrumentos de equilibrio, se trata de una categoría de riesgo completamente diferente. Con el equilibrio podemos proteger nuestro depósito contra el riesgo de que el mercado vaya en contra de nuestra posición dentro de la volatilidad media. Si queremos proteger nuestros fondos contra movimientos paranormales, también llamados "cisnes negros", resultará aconsejable utilizar los siguientes principios: 

  • no asumir un gran porcentaje de riesgo en la posición de un solo instrumento, 
  • no esforzarse por estar en una posición en todo momento, 
  • no depositar todos los fondos gestionados en una misma cuenta con un mismo agente bursátil 
  • y al mismo tiempo no negociar las mismas entradas en diferentes cuentas y brókeres al mismo tiempo. 

Siguiendo estos principios, podremos minimizar nuestras pérdidas si sucede que el instrumento "vuela" contra nosotros cuando estamos en una posición abierta. Bien, volvamos específicamente a la consideración de los riesgos dentro de la volatilidad estándar.

La consideración simultánea de estos dos factores permitirá equilibrar los riesgos y normalizar los beneficios esperados para cada par de divisas en la negociación simultánea, sin sobreponderar los riesgos en ninguno de los instrumentos. Este enfoque ofrecerá estadísticas comerciales más homogéneas para su posterior análisis en la historia de operaciones y reducirá el error cuando el optimizador de estrategias trabaje con estos datos, reduciendo en consecuencia la desviación estándar de la muestra con respecto a los datos medios. Para saber más sobre cómo afecta el valor de la desviación típica a la calidad del análisis de conjuntos, puede leer el artículo "Matemáticas de Trading. Cómo evaluar los resultados de las transacciones comerciales". Nosotros vamos a pasar a seleccionar un contenedor para almacenar los datos.


Selección de contenedores para almacenar datos 

A la hora de seleccionar los contenedores de almacenamiento para nuestro proyecto, consideraremos los siguientes factores:

  • el rendimiento de los contenedores
  • los requisitos de memoria para su inicialización
  • las funciones integradas para el análisis de datos
  • en nuestro caso, también la facilidad de inicialización a través de la interfaz de usuario

Los criterios más habituales para seleccionar los contenedores de almacenamiento son la velocidad de los mismos y los requisitos de memoria de la computadora para almacenar los datos. Los distintos tipos de almacenamiento suelen ofrecer un mejor rendimiento a la hora de procesar los datos, o una cierta ganancia en la cantidad de memoria ocupada.

Para comprobarlo, declararemos un array simple y un contenedor especial de tipo vector, inicializándolos previamente con el mismo tipo de datos double y valores idénticos.

   double arr[] = {1.5, 2.3};
   vector<double> vect = {1.5, 2.3};
Utilizaremos la operación sizeof de la memoria que corresponde a los tipos anteriores en tiempo de compilación.
Print(sizeof(arr));
Print(sizeof(vect));

El resultado será un valor de 16 bytes y 128 bytes. Esta diferencia en los requisitos de memoria para el tipo de datos vector viene determinada por su funcionalidad incorporada, incluida la asignación adicional de memoria redundante para ofrecer un mejor rendimiento.

Basándonos en esto, dados nuestros objetivos para los contenedores que solo requerirán el almacenamiento de datos previamente seleccionados, utilizaremos un tipo de almacenamiento simple como un array. Para los tipos de datos homogéneos que procesaremos posteriormente durante nuestro algoritmo, será conveniente utilizar un tipo de datos vector especial. El uso de este tipo también nos ahorrará tiempo de desarrollo en cuanto a la escritura de funciones personalizadas para operaciones típicas que ya están implementadas "listas para usar" en vector.

Como resultado, el almacenamiento de datos necesario para el cálculo tendrá el siguiente aspecto.
   string symbols[];       // symbols used for balancing

   double tick_val[],      // symbol tick price
          atr[],           // symbol volatility
          volume[],        // calculated position volume for symbols taking into account balancing 
          point[];         // value of one price change point 

   vector<double> risk_contract; // risk amount for a standard contract

Ahora consideraremos las diferentes opciones de implantación de las soluciones para introducir datos para los instrumentos de compensación.


Seleccionamos el modo en que el usuario introduce los símbolos de los instrumentos

Existen muchas soluciones a la hora de elegir cómo introducir los datos en el terminal MetaTrader 5. De manera global, se dividen en soluciones orientadas a la interacción directa con el usuario a través de una ventana de diálogo estándar del terminal, o a la interacción con otras aplicaciones mediante archivos guardados en el disco, o interfaces de interacción remota.

Considerando la necesidad de introducir muchos símbolos de instrumentos que se almacenan en el terminal en formato de string, podemos distinguir las siguientes variantes posibles de implementación de la introducción de datos en nuestro script:

  1. lectura de datos desde un archivo de tabla de tipo *.csv
  2. lectura de datos desde un archivo binario de tipo *.bin
  3. lectura de datos desde un archivo de base de datos de tipo .sqlite
  4. utilización de métodos de interacción de terceros entre el terminal y bases de datos remotas
  5. uso de soluciones web api
  6. uso estándar del terminal de una variable(s) del tipo apropiado con un modificador de clase de memoria del tipo input

Antes de elegir el método de introducción de datos en nuestro proyecto, consideremos brevemente las ventajas y desventajas de cada una de las opciones para llevar a cabo esta tarea. Si elegimos el método descrito en el punto 1, la lectura de datos desde un archivo de tabla de tipo *.csv podrá realizarse mediante las funciones estándar del terminal para trabajar con archivos. En términos generales, el código puede tener el siguiente aspecto.

   string file_name = "Inputs.csv";                         // file name

   int handle=FileOpen(file_name,FILE_CSV|FILE_READ,";");   // attempt to find and open the file

   if(handle!=INVALID_HANDLE)                               // if the file is found, then
     {
      while(FileIsEnding(handle)==false)                    // start reading the file
        {
         string str_follow = FileReadString(handle);        // reading
        
        // here we implement filling the container depending on its type
        }
     }

En la documentación del terminal se describen con detalle otras opciones para implementar la funcionalidad de trabajo con ficheros. En esta opción, el usuario solo tiene que preparar el archivo de parámetros de entrada utilizando una aplicación de hoja de cálculo de terceros, como MS Excel u OpenOffice. Para los usuarios "sin pretensiones", en este caso, incluso el Bloc de notas estándar de Windows puede ser adecuado.

En el segundo punto, la función estándar del terminal FileLoad() resulta adecuada para utilizar ficheros de tipo *.bin. Para utilizarla, necesitaremos saber de antemano qué estructura de datos ha usado la aplicación al guardar este archivo para poder leer los datos desde un archivo con esta extensión. La implementación de esta idea podría ser la siguiente.

   struct InputsData                   // take the structure, according to which the binary file was created 
     {
      int                  symbol_id;  // id of a balanced symbol
      ENUM_POSITION_TYPE   type;       // position type
     };

   InputsData inputsData[];            // storage of inputs

   string  filename="Inputs.bin";      // file name

   ArrayFree(inputsData);              // array released

   long count=FileLoad(filename,inputsData,FILE_COMMON); // load file

El principal inconveniente de este enfoque es que la función FileLoad() no funciona con estructuras de datos que contengan tipos de datos de objeto y, en consecuencia, no funcionará con una estructura si contiene el tipo de datos string. En este caso tendremos que utilizar adicionalmente diccionarios personalizados de contenedores para convertir el id de los símbolos de un valor entero como int al tipo de datos correspondiente string. O bien realizar una consulta adicional a la base de datos necesaria. En general, este método no resultará el más acertado para nuestra aplicación debido a la excesiva complejidad que supone realizar operaciones bastante sencillas.

El tercer punto sugiere usar la funcionalidad incorporada del terminal para trabajar con la base de datos de archivos .sqlite. Supone una variante incorporada de trabajo con bases de datos relacionales construida sobre la interacción con un archivo guardado en el disco duro de la computadora.

   string filename="Inputs.sqlite"; // file name with inputs prepared in advance

   int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE |
                       DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); // open the database

   if(db!=INVALID_HANDLE)                                            // if opened
     {
      // implement queries to the database using the DatabaseExecute() function
      // the query structure will depend on the structure of the database tables
     }

En la aplicación de este enfoque, resultará esencial la construcción inicial de la estructura de tablas de la base de datos de la que dependerá la estructura de las consultas para recuperar los datos necesarios. La principal ventaja de este enfoque será la posibilidad de almacenar datos de forma relacional; así, las tablas almacenarán el id del instrumento en formato entero, en lugar de en formato de cadena, lo cual puede suponer una ganancia muy significativa a la hora de optimizar el espacio en el disco de la computadora. También cabe señalar que, en determinadas condiciones, esta versión de la base de datos puede resultar muy productiva. Encontrará más detalles en el artículo "SQLite: trabajo nativo con bases de datos en SQL en MQL5".

El cuarto punto describe la opción de utilizar bases de datos remotas, que requerirá el uso de bibliotecas adicionales de terceros para su implementación. Esta opción resultará más laboriosa que el método descrito en el punto anterior, ya que no puede implementarse completamente a través de la funcionalidad estándar del terminal. Existen muchas publicaciones sobre este tema, incluyendo una buena variante de realización de la interacción entre el terminal y la base de datos MySQL descrita por el autor en el artículo "Trabajo con el SGBD MySQL desde MQL5 (MQL4)".

La aplicación de consultas web api para obtener parámetros de entrada, descrita en el quinto punto, puede considerarse probablemente la solución más universal y multiplataforma para nuestra tarea. Esta funcionalidad está integrada en el terminal con la ayuda de la función predefinida WebRequest() y resultará ideal si ya tiene implementada una infraestructura para aplicaciones de back-end y front-end debido a su versatilidad. De lo contrario, el desarrollo de estas aplicaciones desde cero podría consumir bastante tiempo y recursos, a pesar de que dichas soluciones pueden escribirse en muchos lenguajes de programación e intérpretes modernos.

En esta implementación, utilizaremos variables con el modificador de clase de memoria del tipo input del tipo de datos string del sexto punto, simplemente porque esta opción es capaz de proporcionar la funcionalidad requerida en su totalidad sin necesidad de desarrollar programas de usuario adicionalmente. No declararemos un conjunto de variables para cada símbolo, porque no podemos saber de antemano cuántos símbolos vamos a equilibrar, y existe una solución más elegante y flexible para ello. Trabajaremos con una única cadena que contiene todos los valores, con una mayor separación de los datos en ella. Así, declaramos una variable a nivel global de la forma siguiente:

input string input_symbols = "EURCHFz USDJPYz";

Asegúrese de inicializarlo con el valor por defecto para que los usuarios que no sean desarrolladores entiendan cómo introducir los símbolos del instrumento para el correcto funcionamiento de la aplicación. En este caso usaremos un espacio normal como separador de cadenas para mayor comodidad del usuario.

Obtendremos los datos de esta variable y rellenaremos nuestro array symbols[] utilizando la función predefinida del terminal para trabajar con cadenas StringSplit() de la siguiente forma:

   string symbols[];                         // storage of user-entered symbols
   
   StringSplit(input_symbols,' ',symbols);   // split the string into the symbols we need

   int size = ArraySize(symbols);            // remember the size of the resulting array right away

Al ejecutar la función StringSplit(), el array symbols[] que se le transmitirá por referencia se rellenará con los datos seleccionados de la cadena utilizando un separador de espacio (' ').

Ahora que tenemos un array lleno con los valores de los nombres de los símbolos de los instrumentos para el equilibrio de riesgos, procederemos a solicitar al terminal los datos necesarios sobre los instrumentos seleccionados para los cálculos posteriores.


Obtención de los datos necesarios del instrumento usando funciones predefinidas del terminal

Para los cálculos necesitaremos saber para cada símbolo introducido el valor del cambio mínimo en el precio del instrumento y cuánto nos costará este cambio en la divisa de depósito. Puede realizarse mediante la función predefinida del terminal SymbolInfoDouble() usando la enumeración ENUM_SYMBOL_INFO_DOUBLE que necesitemos como uno de los parámetros de la función. La implementación de la enumeración de datos se organizará a través del popular ciclo for de la siguiente manera:

for(int i=0; i<size; i++)  // loop through previously entered symbols
     {
      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);                	// requested the minimum price change size (tick)
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE_LOSS);  // request tick price in currency
     }

Cabe señalar aquí que la enumeración ENUM_SYMBOL_INFO_DOUBLE contiene no solo el valor SYMBOL_TRADE_TICK_VALUE_LOSS para solicitar el precio del tick en la divisa, sino también el valor SYMBOL_TRADE_TICK_VALUE y el valor SYMBOL_TRADE_TICK_VALUE_PROFIT igual a él. En principio, la solicitud de cualquiera de los valores anteriores puede aplicarse en nuestro cálculo, ya que la diferencia de estos valores no es muy significativa. Por ejemplo, los valores de estos argumentos para el tipo cruzado AUDNZD se presentan en la tabla siguiente:

Parámetro de función Valor del precio del tick retornado por la función
SYMBOL_TRADE_TICK_VALUE 0.6062700000000001
SYMBOL_TRADE_TICK_VALUE_LOSS 0.6066200000000002
SYMBOL_TRADE_TICK_VALUE_PROFIT 0.6062700000000001

Tabla 1. Diferencia de los valores retornados del precio del tick en diferentes parámetros de la función SymbolInfoDouble() según el símbolo AUDNZD

Aunque en nuestro caso lo más correcto sería utilizar el parámetro SYMBOL_TRADE_TICK_VALUE_LOSS, en mi opinión podemos utilizar cualquiera de las opciones aquí sugeridas. Como dijo en 1962 Richard Hamming

"The purpose of computing is insight, not numbers" ("El objetivo de la informática es la comprensión, no los números").

Nosotros ahora procederemos a solicitar los datos de volatilidad necesarios.


Obtención de los datos de volatilidad necesarios usando la clase de usuario estándar CiATR

En los capítulos anteriores ya hemos mencionado el concepto de volatilidad como indicador que caracteriza la variación típica del precio de un instrumento durante un determinado periodo de tiempo, en nuestro caso durante un día comercial. Aunque este indicador resulta obvio, los métodos de su cálculo pueden variar enormemente, debido principalmente a los siguientes aspectos:

  • si tenemos en cuenta los gaps no cerrados en los gráficos diarios
  • si aplicamos la promediación y, en caso afirmativo, durante qué periodo
  • si excluimos del cálculo las barras con una volatilidad "paranormal" (extremadamente rara)
  • si realizamos los cálculos según el máximo/mínimo de la barra diaria o tomamos solo la apertura y el cierre de la barra
  • o negociamos con barras Renko y el tiempo de promediación no es tan importante para nosotros.

La contabilización de los gaps en el cálculo de la volatilidad se usa muy a menudo al trabajar en las bolsas, a diferencia del mercado de divisas, simplemente porque los gaps no cerrados durante el día son muy raros en el mercado de divisas y el cálculo final de la volatilidad media diaria no cambia por ello. El cálculo aquí solo se distinguirá en que tomaremos el valor máximo de los dos valores. El primero será la diferencia entre el máximo y el mínimo de cada barra, mientras que el segundo será la diferencia entre el máximo y el mínimo de la barra actual y el cierre de la barra anterior. Para ello, podemos utilizar la función incorporada del terminal MathMax().

Al aplicar la media de los valores obtenidos, deberemos considerar que cuanto más largo sea el periodo de media, más lento se volverá este indicador en el cambio de la volatilidad del mercado. Por regla general, se usa un periodo de 3 a 5 días para promediar la volatilidad diaria en el mercado de divisas. También resulta conveniente excluir los movimientos paranormales del mercado al calcular el indicador de volatilidad. Esto puede hacerse automáticamente usando el valor mediano de la muestra. Para ello, podemos utilizar el método incorporado llamado Median() en un ejemplar de clase de tipo vector.

Muchos prefieren hacer el promedio usando la contabilización de la volatilidad solo a través de los precios de apertura y cierre sin tener en cuenta las colas de las velas. Este método no resulta recomendable, ya que puede producir un valor muy inferior a la volatilidad que puede haber en el mercado. Al aplicar las barras Renko, la lógica cambia mucho y aquí la adición de la volatilidad no se basará en el periodo comercial, sino que el periodo comercial vendrá determinado por la volatilidad. Por lo tanto, este enfoque no nos servirá en nuestro trabajo.

En nuestra implementación, usaremos una consulta de volatilidad a través del indicador del terminal ATR utilizando la clase personalizada estándar CiATR, que tiene código abierto y se almacena en la biblioteca del terminal. Usaremos un valor rápido de 3 para promediar el índice. En general, el código de la consulta de volatilidad tendrá el aspecto siguiente. A nivel global, declararemos el nombre de la variable de clase con una llamada al constructor por defecto de la siguiente forma.

CiATR indAtr[];

Cabe señalar aquí que utilizaremos exactamente un array de indicadores para almacenar datos simultáneamente, y no para sobrecargar un indicador existente, principalmente por comodidad y la posibilidad de ampliar aún más la funcionalidad del código. A continuación, añadiremos el siguiente código a nuestro ciclo de iteración por los símbolos donde ya solicitamos los datos sobre los instrumentos, para solicitar los valores de los indicadores.

indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);   // create symbol and period
   
indAtr[i].Refresh();          // be sure to update data

atr[i] = indAtr[i].Main(1);   // request data on closed bars on D1

Una vez obtenidos todos los datos de entrada necesarios para el cálculo, pasaremos directamente a los cálculos.


Dos variantes de la lógica de cálculo del riesgo para distintas formas de salida de una posición

Al redactar la lógica del equilibrio de riesgos en la negociación simultánea de varios instrumentos, deberemos tener en cuenta los siguientes puntos importantes. Cómo prevé nuestro sistema comercial las salidas de las posiciones equilibradas y cómo tenemos en cuenta la correlación de los instrumentos que vamos a equilibrar. Incluso a primera vista, los criterios comprensibles de correlación cruzada en los mercados de divisas no siempre ofrecen un nivel garantizado de correlación en el periodo temporal considerado y, a menudo, pueden considerarse instrumentos separados. Por ejemplo, si al comienzo de la jornada comercial hemos decidido el conjunto de instrumentos con los que negociaremos hoy y la dirección de las entradas, deberíamos saber de antemano lo siguiente. Si cerraremos todas las posiciones al final del día comercial o no. Si consideraremos la salida de las posiciones durante el día por separado para cada posición, o simplemente el tiempo de todas las posiciones. Aquí no puede haber una receta universal para todos, simplemente porque cada tráder toma decisiones sobre la entrada y la salida partiendo de un conjunto bastante amplio de criterios basados en sus propios conocimientos y experiencia.

En esta implementación, realizaremos un cálculo universal que nos otorgará flexibilidad a la hora de determinar el volumen de entrada de la posición en función de lo que se esté negociando, simplemente cambiando los parámetros de entrada del riesgo por instrumento e incluyendo/excluyendo los instrumentos que el tráder crea que están correlacionados en ese momento. Para ello, declararemos el parámetro de riesgo de entrada por instrumento de la siguiente manera.

input double risk_per_instr = 50;

Además, para un uso universal, deberemos prever la salida simultánea de los resultados del equilibrado al efectuar la regresión del riesgo introducido por parte del usuario sobre el instrumento comercial tomado por separado y teniendo en cuenta la correlación de estos instrumentos. Esto permitirá al tráder obtener cierta gama de variaciones de volúmenes para las posiciones y, lo que es más importante, las proporciones de estos instrumentos para la negociación simultánea. Para ello, primero tendremos que añadir la siguiente entrada a nuestro ciclo de cálculo principal.

risk_contract[i] = tick_val[i]*atr[i]/point[i]; // calculate the risk for a standard contract taking into account volatility and point price

Además, fuera del ciclo especificado, encontraremos el instrumento de nuestro conjunto con el valor de riesgo máximo para construir a partir de él una proporción que equilibre los volúmenes comerciales de todo el conjunto. La funcionalidad incorporada de nuestro contenedor resulta muy útil para ello.

double max_risk = risk_contract.Max();          // call the built-in container method to find the maximum value

Ahora que conocemos el riesgo máximo de nuestra red, organizaremos otro ciclo para calcular el volumen equilibrado de cada instrumento de nuestra muestra, suponiendo que no existe correlación entre ellos, como se indica a continuación, y enviaremos inmediatamente los resultados al registro.

for(int i=0; i<size; i++)	// loop through the size of our symbol array
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits); // calculate the balanced volume
     }

Print("Separate");		// display the header in the journal preliminarily

for(int i=0; i<size; i++)	// loop through the array again
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));	// display the resulting volume values
     }

Podemos decir que se trata del volumen máximo de negociación intradía de nuestras posiciones, equilibrado según la volatilidad media diaria más probable y esperada, considerando el riesgo que asumimos por instrumento.

A continuación, calcularemos el volumen de nuestras posiciones basándonos en la premisa de que los instrumentos pueden empezar a correlacionarse en un día comercial, lo que de hecho puede aumentar significativamente el riesgo previsto por instrumento en relación con lo que hemos introducido en los parámetros de entrada. Para ello, añadiremos el siguiente código en el que simplemente dividiremos el valor obtenido por el número de instrumentos negociados.

Print("Complex");		// display the header in the journal preliminarily
   
for(int i=0; i<size; i++)	// loop through the array
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));	// calculate the minimum volume for entry
     }

Ahora hemos obtenido los límites máximo y mínimo de los volúmenes de las posiciones equilibradas según el riesgo. Basándonos en ellos, determinaremos de forma independiente el volumen de las entradas dentro de este rango. Lo principal es respetar las proporciones indicadas en el cálculo. También debemos señalar que la muestra de los resultados en el registro es la más sencilla, pero no la única. También podemos utilizar otras funciones estándar del terminal para mostrar información al usuario. En este caso, el terminal ofrece una funcionalidad muy amplia, que incluye el uso de funciones como MessageBox(), Alert(), SendNotification(), SendMail() y muchas otras. Pasemos ahora al código del asesor completo, tal y como debería verse en el archivo compilado.


Implementación final de la solución en un script 

Como resultado, nuestro código de implementación tendrá el aspecto siguiente.

#property strict		

#include <Indicators\Oscilators.mqh>

//---
input string input_symbols = "EURCHFz USDJPYz";
input double risk_per_instr = 50;
input int atr_period = 3;
input int calc_digits = 3;
CiATR indAtr[];


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  { 
   string symbols[];
   StringSplit(input_symbols,' ',symbols);   

   int size = ArraySize(symbols);
   double tick_val[], atr[], volume[], point[];
   vector<double> risk_contract;

   ArrayResize(tick_val,size);
   ArrayResize(atr,size);
   ArrayResize(volume,size);
   ArrayResize(point,size);
   ArrayResize(indAtr,size);
   risk_contract.Resize(size);

   for(int i=0; i<size; i++)
     {
      indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);
      indAtr[i].Refresh();

      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE);

      atr[i] = indAtr[i].Main(1);
      risk_contract[i] = tick_val[i]*atr[i]/point[i];
     }

   double max_risk = risk_contract.Max();
   Print("Max risk in set\t"+symbols[risk_contract.ArgMax()]+"\t"+DoubleToString(max_risk));

   for(int i=0; i<size; i++)
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits);
     }

   Print("Separate");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));
     }

   Print("Complex");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));
     }
  }
//+------------------------------------------------------------------+

Tras la compilación, se abrirá la ventana de parámetros de entrada. Por ejemplo, queremos equilibrar tres instrumentos y un riesgo máximo de 500 dólares.


Figura 2: Parámetros de entrada

Figura 2. Parámetros de entrada

La ejecución del script dará como resultado los siguientes datos sobre el volumen de cada instrumento considerando el balance de riesgos.

Figura 3. Datos de salida

Figura 3. Datos de salida

Aquí tenemos el código completo para calcular los volúmenes de negociación simultánea de varios instrumentos equilibrados según el riesgo utilizando la funcionalidad más sencilla, pero mínima necesaria, que ofrece el terminal. Si lo deseamos, podremos ampliar este algoritmo utilizando las características adicionales mencionadas en este artículo, así como nuestros propios desarrollos.


Conclusión

Finalmente, disponemos de un script totalmente funcional que permitirá a los tráders que no comercian con negociación algorítmica ajustar con rapidez y precisión los volúmenes de las posiciones con el trading intradía. Espero que incluso aquellos que se dedican al trading algorítmico encuentren aquí nuevas ideas para mejorar su infraestructura de software y tal vez mejorar los resultados comerciales actuales. Gracias por su atención y los comentarios realizados.

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

Archivos adjuntos |
RiskBallance.mq5 (2.27 KB)
Redes neuronales: así de sencillo (Parte 73): AutoBots para predecir la evolución de los precios Redes neuronales: así de sencillo (Parte 73): AutoBots para predecir la evolución de los precios
Seguimos hablando de algoritmos para entrenar modelos de predicción de trayectorias. En este artículo nos familiarizaremos con un método llamado "AutoBots".
Usamos algoritmos de optimización para ajustar los parámetros del asesor sobre la marcha Usamos algoritmos de optimización para ajustar los parámetros del asesor sobre la marcha
El artículo analizará diversos aspectos prácticos relacionados con el uso de algoritmos de optimización para encontrar los mejores parámetros de un asesor sobre la marcha, y también virtualizar las operaciones comerciales y la lógica del asesor. El lector puede usar este artículo a modo de instrucciones para implementar algoritmos de optimización en un asesor comercial.
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.
Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX
El proyecto consiste en utilizar Python para realizar previsiones basadas en el aprendizaje profundo en los mercados financieros. Exploraremos los entresijos de la comprobación del rendimiento del modelo utilizando métricas clave como el error medio absoluto (MAE, Mean Absolute Error), el error medio cuadrático (MSE, Mean Squared Error) y R-cuadrado (R2), y aprenderemos a envolverlo todo en un ejecutable. También haremos un fichero modelo ONNX con su EA.