Cálculo del margen para una orden futura: OrderCalcMargin

Antes de enviar una solicitud de operación al servidor, un programa MQL puede calcular el margen necesario para una operación planificada utilizando la función OrderCalcMargin. Se recomienda hacerlo siempre así para evitar una carga excesiva de depósitos.

bool OrderCalcMargin(ENUM_ORDER_TYPE action, const string symbol,
double volume, double price, double &margin)

La función calcula el margen necesario para el tipo de orden action especificado y el instrumento financiero symbol con lotes volume. Esto se ajusta a la configuración de la cuenta corriente, pero no tiene en cuenta las órdenes pendientes existentes ni las posiciones abiertas. La enumeración ENUM_ORDER_TYPE se introdujo en la sección Tipos de órdenes.

El valor del margen (en la divisa de la cuenta) se escribe en el parámetro margin pasado por referencia.

Cabe destacar que se trata de una estimación del margen para una única posición u orden nueva, y no del valor total de la fianza, en el que se convertirá tras la ejecución. Además, la evaluación se realiza como si no hubiera otras órdenes pendientes ni posiciones abiertas en la cuenta corriente. En realidad, el valor del margen depende de muchos factores, incluidas otras órdenes y posiciones, y puede variar a medida que cambia el entorno del mercado (como el apalancamiento).

La función devuelve un indicador de éxito (true) o de error (false). El código de error puede obtenerse de la forma habitual a partir de la variable _LastError.

La función OrderCalcMargin sólo puede utilizarse en Asesores Expertos y scripts. Para calcular el margen en los indicadores es necesario implementar un método alternativo; por ejemplo, lanzar un Asesor Experto auxiliar en un objeto gráfico, pasarle parámetros y obtener el resultado a través del mecanismo de eventos, o describir de forma independiente los cálculos en MQL5 utilizando fórmulas según los tipos de instrumentos. En la sección siguiente ofrecemos un ejemplo de una implementación de este tipo, junto con una estimación de los posibles beneficios/pérdidas.

Podríamos escribir un script sencillo que llame a OrderCalcMargin para los símbolos de Observación de Mercado, y comparar los valores de los márgenes para ellos. En lugar de ello, vamos a complicar ligeramente la tarea y a considerar el archivo de encabezado LotMarginExposure.mqh, que permite evaluar la carga del depósito y el nivel de margen después de abrir una posición con un nivel de riesgo predeterminado. Un poco más adelante hablaremos de la función OrderCheck que es capaz de proporcionar información similar. No obstante, nuestro algoritmo podrá resolver además el problema inverso de elegir el tamaño del lote en función de los niveles de carga o riesgo dados.

El uso de las nuevas funciones se demuestra en un Asesor Experto no de trading LotMarginExposureTable.mq5.

En teoría, el hecho de que un programa MQL se implemente como un Asesor Experto no significa que las operaciones de trading deban realizarse en él. Muy a menudo, como en nuestro caso, se crean varias utilidades en forma de Asesor Experto. Su ventaja sobre los scripts es que permanecen en el gráfico y pueden realizar sus funciones indefinidamente en respuesta a determinados eventos.

En el nuevo Asesor Experto utilizamos las habilidades de creación de una interfaz gráfica interactiva utilizando objetos. Para decirlo de una manera más sencilla, para una lista dada de símbolos, el Asesor Experto mostrará una tabla con varias columnas de indicadores de margen en el gráfico, y la tabla se puede ordenar por cada una de las columnas. Proporcionaremos la lista de columnas un poco más adelante.

Dado que el análisis de lotes, margen y carga de depósito es una tarea común, separaremos la implementación en un archivo de encabezado independiente LotMarginExposure.mqh.

Todas las funciones de archivo se agrupan en un espacio de nombres para evitar conflictos y en aras de la claridad (indicar el contexto antes de llamar a una función interna informa sobre el origen y la ubicación de esta función).

namespace LEMLR
{
   ...
};

La abreviatura LEMLR significa «Lot, Exposure, Margin Level, Risk» (lote, exposición, nivel de margen, riesgo).

Los principales cálculos se realizan en la función Estimate. Considerando un prototipo de la función integrada OrderCalcMargin, en los parámetros de la función Estimate necesitamos pasar el nombre del símbolo, el tipo de orden, el volumen y el precio. Pero eso no es todo lo que necesitamos.

   bool Estimate(const ENUM_ORDER_TYPE typeconst string symbolconst double lot,
      const double price,...)

Pretendemos evaluar varios indicadores de una operación de trading, que están interconectados y pueden calcularse en distintas direcciones, dependiendo de lo que el usuario haya introducido como datos iniciales y de lo que desee calcular. Por ejemplo, utilizando los parámetros anteriores, es fácil encontrar el nuevo nivel de margen y la carga de la cuenta. Sus fórmulas son exactamente lo contrario:

Ml = money / margin * 100
Ex = margin / money * 100

Aquí la variable margin indica la cantidad de margen, para lo cual basta con llamar a OrderCalcMargin.

No obstante, los operadores a menudo prefieren partir de un nivel de carga o margen predeterminado y calcular el volumen para ello. Además, existe un método de cálculo de lotes basado en el riesgo que también goza de gran popularidad. Por riesgo se entiende el importe de la pérdida potencial del trading en caso de un movimiento desfavorable de los precios, como consecuencia del cual disminuirá el contenido de otra variable de las fórmulas anteriores, es decir, money.

Para calcular la pérdida, es importante conocer la volatilidad del instrumento financiero durante el periodo de trading (la duración de la estrategia) o la distancia del Stop Loss asumida por el usuario.

Por lo tanto, la lista de parámetros de la función Estimate se amplía.

   bool Estimate(const ENUM_ORDER_TYPE typeconst string symbolconst double lot,
      const double price,
      const double exposureconst double riskLevelconst int riskPoints,
      const ENUM_TIMEFRAMES riskPerioddouble money,...)

En el parámetro exposure especificamos la carga de depósito deseada en porcentaje, y en el parámetro riskLevel, indicamos la parte del depósito (también en porcentaje) que estamos dispuestos a arriesgar. Para los cálculos basados en el riesgo, puede pasar el tamaño del Stop Loss en puntos en el parámetro riskPoints. Cuando es igual a 0, entra en juego el parámetro riskPeriod: especifica el periodo para el que el algoritmo calculará automáticamente el rango de cotizaciones de los símbolos en puntos. Por último, en el parámetro money, podemos especificar una cantidad arbitraria de margen libre para la evaluación de lotes. Algunos operadores dividen condicionalmente el depósito entre varios robots. Cuando money es 0, la función ejecutará esta variable con la propiedad AccountInfoDouble(ACCOUNT_MARGIN_FREE).

Ahora tenemos que decidir cómo devolver los resultados de la función. Dado que es capaz de evaluar muchos indicadores de trading y varias opciones de volumen, tiene sentido definir la estructura SymbolLotExposureRisk.

   struct SymbolLotExposureRisk
   {
      double lot;                      // requested volume (or minimum)
      int atrPointsNormalized;         // price range normalized by tick size
      double atrValue;                 // range as the amount of profit/loss for 1 lot
      double lotFromExposureRaw;       // not normalized ( can be less than the minimum lot)
      double lotFromExposure;          // normalized lot from deposit loading
      double lotFromRiskOfStopLossRaw// not normalized (can be less than the minimum lot)
      double lotFromRiskOfStopLoss;    // normalized lot from risk
      double exposureFromLot;          // loading based on the volume of 'lot
      double marginLevelFromLot;       // margin level from 'lot' volume
      int lotDigits;                   // number of digits in normalized lots
   };

El campo lot de la estructura contiene el lote pasado a la función Exposure si el lote no es igual a 0. Si el lote pasado es cero, se sustituye por la propiedad de símbolo SYMBOL_VOLUME_MIN.

Se asignan dos campos para los valores calculados de volúmenes basados en la carga del depósito y el riesgo: con el sufijo Raw (lotFromExposureRaw, lotFromRiskOfStopLossRaw) y sin él (lotFromExposure, lotFromRiskOfStopLoss). Raw contienen un resultado «aritmético puro», que puede no coincidir con la especificación del símbolo. En los campos sin sufijo, los lotes se normalizan considerando el mínimo, el máximo y el paso. Esta duplicación es útil, en particular, para los casos en que el cálculo da valores inferiores al lote mínimo (por ejemplo, lotFromExposureRaw es igual a 0.023721 con un mínimo de 0.1, debido a lo cual lotFromExposure se reduce a cero): entonces, a partir del contenido de los campos Raw, se puede evaluar cuánto dinero añadir o cuánto aumentar el riesgo para llegar al lote mínimo.

Vamos a describir el último parámetro de salida de la función Estimate como una referencia a esta estructura. Iremos ejecutando poco a poco todos los campos del cuerpo de la función. En primer lugar, obtenemos el margen de un lote llamando a OrderCalcMargin y lo guardamos en una variable local lot1margin.

   bool Estimate(const ENUM_ORDER_TYPE typeconst string symbolconst double lot,
      const double priceconst double exposure,
      const double riskLevelconst int riskPointsconst ENUM_TIMEFRAMES riskPeriod,
      double moneySymbolLotExposureRisk &r)
   {
      double lot1margin;
      if(!OrderCalcMargin(typesymbol1.0,
         price == 0 ? GetCurrentPrice(symboltype) : price,
         lot1margin))
      {
         Print("OrderCalcMargin "symbol" failed: "_LastError);
         return false;
      }
      if(lot1margin == 0)
      {
         Print("Margin "symbol" is zero, "_LastError);
         return false;
      }
      ...

Si no se especifica el precio de entrada, es decir, price es igual a 0, la función de ayuda GetCurrentPrice devuelve un precio adecuado en función del tipo de orden: para las compras, se tomará la propiedad de símbolo SYMBOL_ASK, y para las ventas será SYMBOL_BID. Esta y otras funciones de ayuda se omiten aquí, su contenido se puede encontrar en el código fuente adjunto.

Si el cálculo del margen falla, o se recibe un valor cero, la función Estimate devolverá false.

Tenga en cuenta que el margen cero puede ser la norma, pero también puede ser un error, dependiendo del instrumento y del tipo de orden. Por lo tanto, para los tickers de bolsa, las órdenes pendientes están sujetas a depósito, pero no para los tickers OTC (es decir, el depósito 0 es correcto). Este punto debe tenerse en cuenta en el código de llamada: debe solicitar margen sólo para aquellas combinaciones de símbolos y tipos de operaciones para las que tenga sentido y se suponga que es distinto de cero.

Teniendo un depósito para un lote, podemos calcular el número de lotes para asegurar una carga determinada del depósito.

      double usedMargin = 0;
      if(money == 0)
      {
         money = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
         usedMargin = AccountInfoDouble(ACCOUNT_MARGIN);
      }
   
      r.lotFromExposureRaw = money * exposure / 100.0 / lot1margin;
      r.lotFromExposure = NormalizeLot(symbolr.lotFromExposureRaw);
      ...

A continuación se muestra la función de ayuda NormalizeLot.

Con el fin de obtener un lote dependiendo del riesgo y la volatilidad, hay que calcular un poco más.

      const double tickValue = SymbolInfoDouble(symbolSYMBOL_TRADE_TICK_VALUE);
      const int pointsInTick = (int)(SymbolInfoDouble(symbolSYMBOL_TRADE_TICK_SIZE)
         / SymbolInfoDouble(symbolSYMBOL_POINT));
      const double pointValue = tickValue / pointsInTick;
      const int atrPoints = (riskPoints > 0) ? (int)riskPoints :
         (int)(((MathMax(iHigh(symbolriskPeriod1), iHigh(symbolriskPeriod0))
         -  MathMin(iLow(symbolriskPeriod1), iLow(symbolriskPeriod0)))
         / SymbolInfoDouble(symbolSYMBOL_POINT)));
      // rounding by tick size 
      r.atrPointsNormalized = atrPoints / pointsInTick * pointsInTick;
      r.atrValue = r.atrPointsNormalized * pointValue;
      
      r.lotFromRiskOfStopLossRaw = money * riskLevel / 100.0
         / (pointValue * r.atrPointsNormalized);
      r.lotFromRiskOfStopLoss = NormalizeLot(symbolr.lotFromRiskOfStopLossRaw);
      ...

Aquí encontramos el coste de un pip del instrumento y el rango de sus cambios para el periodo especificado, tras el cual ya calculamos el lote.

Por último, obtenemos la carga de la cuenta y el nivel de margen para el lote dado.

      r.lot = lot <= 0 ? SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : lot;
      double margin = r.lot * lot1margin;
     
      r.exposureFromLot = (margin + usedMargin) / money * 100.0;
      r.marginLevelFromLot = margin > 0 ? money / (margin + usedMargin) * 100.0 : 0;
      r.lotDigits = (int)MathLog10(1.0 / SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN));
      
      return true;
   }

En caso de que el cálculo se realice correctamente, la función devolverá true.

Aquí está la vista abreviada de la función NormalizeLot (todas las comprobaciones de 0 se omiten para simplificar). Encontrará información detallada sobre las propiedades correspondientes en la sección Volúmenes permitidos de operaciones de trading.

   double NormalizeLot(const string symbolconst double lot)
   {
      const double stepLot = SymbolInfoDouble(symbolSYMBOL_VOLUME_STEP);
      const double newLotsRounded = MathFloor(lot / stepLot) * stepLot;
      const double minLot = SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN);
      if(newLotsRounded < minLotreturn 0;
      const double maxLot = SymbolInfoDouble(symbolSYMBOL_VOLUME_MAX);
      if(newLotsRounded > maxLotreturn maxLot;
      return newLotsRounded;
   }

La implementación anterior de Estimate no tiene en cuenta los ajustes por solapamiento de posiciones. Por regla general, conducen a una disminución del depósito, por lo que la estimación actual de la carga de la cuenta y el nivel de margen puede ser más pesimista de lo que resulta en la realidad, pero esto proporciona una protección adicional. Los interesados pueden añadir un código para analizar la composición de los fondos de la cuenta ya congelados (su importe total figura en la propiedad de la cuenta ACCOUNT_MARGIN) desglosados por posiciones y órdenes: entonces será posible tener en cuenta el efecto potencial de una nueva orden sobre el margen (por ejemplo, sólo se tendrá en cuenta la posición más grande de las opuestas o se aplicará una tasa de margen de cobertura reducida, véanse los detalles en la sección Requisitos de margen).

Ahora es el momento de poner en práctica la estimación de márgenes y lotes en LotMarginExposureTable.mq5. Teniendo en cuenta que los campos Raw sólo se mostrarán en aquellos casos en los que la normalización de los lotes haya llevado a su puesta a cero, el número total de columnas de la tabla de indicadores resultante es de 8.

#include <MQL5Book/LotMarginExposure.mqh>
#define TBL_COLUMNS 8

En los parámetros de entrada ofreceremos la posibilidad de especificar el tipo de orden, la lista de símbolos que se van a analizar (una lista separada por comas), los fondos disponibles, así como el lote, la carga del depósito objetivo, el nivel de margen y el riesgo.

input ENUM_ORDER_TYPE Action = ORDER_TYPE_BUY;
input string WorkList = "";                   // Symbols (comma,separated,list)
input double Money = 0;                       // Money (0 = free margin)
input double Lot = 0;                         // Lot (0 = min lot)
input double Exposure = 5.0;                  // Exposure (%)
input double RiskLevel = 5.0;                 // RiskLevel (%)
input int RiskPoints = 0;                     // RiskPoints/SL (0 = auto-range of RiskPeriod)
input ENUM_TIMEFRAMES RiskPeriod = PERIOD_W1;

Para los tipos de órdenes pendientes es necesario seleccionar símbolos bursátiles, ya que para otros símbolos se obtendrá un margen cero, lo que provocará un error en la función Estimate. Si la lista de símbolos se deja vacía, el Asesor Experto procesará sólo el símbolo del gráfico actual. Los valores cero por defecto de los parámetros Money y Lot significan, respectivamente, la cantidad actual de fondos libres en la cuenta y el lote mínimo para cada símbolo.

El valor 0 en el parámetro RiskPoints significa obtener un rango de precios durante RiskPeriod (por defecto es una semana).

El parámetro de entrada UpdateFrequency establece la frecuencia de recálculo en segundos. Si lo deja igual a cero, el recálculo se realiza en cada nueva barra.

input int UpdateFrequency = 0// UpdateFrequency (sec, 0 - once per bar)

En el contexto global se describen un array de símbolos (rellenada posteriormente analizando el parámetro de entrada WorkList) y la marca de tiempo del último cálculo realizado con éxito.

string symbols[];
datetime lastTime;

Al arrancar, encendemos el segundo temporizador.

void OnInit()
{
   Comment("Starting...");
   lastTime = 0;
   EventSetTimer(1);
}

En el manejador del temporizador proporcionamos la primera llamada al cálculo principal en OnTick, si aún no se ha llamado a OnTick al llegar un tick. Esta situación puede darse, por ejemplo, los fines de semana o durante un mercado en calma. Además, OnTimer es el punto de entrada para los recálculos a una frecuencia determinada.

void OnTimer()
{
   if(lastTime == 0// calculation for the first time (if OnTick did have time to trigger)
   {
      OnTick();
      Comment("Started");
   }
   else if(lastTime != -1)
   {
      if(UpdateFrequency <= 0// if there is no frequency, we work on new bars in OnTick
      {
         EventKillTimer();     // and the timer is no longer needed
      }
      else if(TimeCurrent() - lastTime >= UpdateFrequency)
      {
         lastTime = LONG_MAX// prevent re-entering this 'if' branch 
         OnTick();
         if(lastTime != -1)   // completed without error
         {
 lastTime = TimeCurrent();// update timestamp
         }
      }
      Comment("");
   }
}

En el manejador OnTick, primero comprobamos los parámetros de entrada y convertimos la lista de símbolos en un array de cadenas. Si se encuentran problemas, el signo del error se escribe en lastTime: el valor -1, y el procesamiento de los ticks posteriores se interrumpe al principio.

void OnTick()
{
   if(lastTime == -1return// already had an error, exit 
  
   if(UpdateFrequency <= 0)   // if the update rate is not set
   {
      if(lastTime == iTime(NULL00)) return// waiting for a new bar
   }
   else if(TimeCurrent() - lastTime < UpdateFrequency)
   {
      return;
   }
      
   const int ns = StringSplit((WorkList == "" ? _Symbol : WorkList), ',', symbols);
   if(ns <= 0)
   {
      Print("Empty symbols");
      lastTime = -1;
      return;
   }
   
   if(Exposure > 100 || Exposure <= 0)
   {
      Print("Percent of Exposure is incorrect: "Exposure);
      lastTime = -1;
      return;
   }
   
   if(RiskLevel > 100 || RiskLevel <= 0)
   {
      Print("Percent of RiskLevel is incorrect: "RiskLevel);
      lastTime = -1;
      return;
   }
   ...

En concreto, se considera un error si los valores de entrada Exposure y Risk Level están fuera del rango de 0 a 100, como debería ser para los porcentajes. En caso de datos de entrada normales, actualizamos la marca de tiempo, describimos la estructura LEMLR::SymbolLotExposureRisk para recibir los indicadores calculados de la función LEMLR::Estimate (un símbolo cada uno), así como un array bidimensional LME (de «Lot Margin Exposure») para recopilar los indicadores de todos los símbolos.

   lastTime = UpdateFrequency > 0 ? TimeCurrent() : iTime(NULL00);
   
   LEMLR::SymbolLotExposureRisk r = {};
   
   double LME[][13];
   ArrayResize(LMEns);
   ArrayInitialize(LME0);
   ...

En un bucle a través de símbolos, llamamos a la función LEMLR::Estimate y ejecutamos el array LME.

   for(int i = 0i < nsi++)
   {
      if(!LEMLR::Estimate(Actionsymbols[i], Lot0,
         ExposureRiskLevelRiskPointsRiskPeriodMoneyr))
      {
        Print("Calc failed (will try on the next bar, or refresh manually)");
        return;
      }
      
      LME[i][eLot] = r.lot;
      LME[i][eAtrPointsNormalized] = r.atrPointsNormalized;
      LME[i][eAtrValue] = r.atrValue;
      LME[i][eLotFromExposureRaw] = r.lotFromExposureRaw;
      LME[i][eLotFromExposure] = r.lotFromExposure;
      LME[i][eLotFromRiskOfStopLossRaw] = r.lotFromRiskOfStopLossRaw;
      LME[i][eLotFromRiskOfStopLoss] = r.lotFromRiskOfStopLoss;
      LME[i][eExposureFromLot] = r.exposureFromLot;
      LME[i][eMarginLevelFromLot] = r.marginLevelFromLot;
      LME[i][eLotDig] = r.lotDigits;
      LME[i][eMinLot] = SymbolInfoDouble(symbols[i], SYMBOL_VOLUME_MIN);
      LME[i][eContract] = SymbolInfoDouble(symbols[i], SYMBOL_TRADE_CONTRACT_SIZE);
      LME[i][eSymbol] = pack2double(symbols[i]);
   }
   ...

Los elementos de la enumeración especial LME_FIELDS se utilizan como índices de array, que proporcionan simultáneamente nombres y números para los indicadores de la estructura.

enum LME_FIELDS // 10 fields + 3 additional symbol properties
{
   eLot,
   eAtrPointsNormalized,
   eAtrValue,
   eLotFromExposureRaw,
   eLotFromExposure,
   eLotFromRiskOfStopLossRaw,
   eLotFromRiskOfStopLoss,
   eExposureFromLot,
   eMarginLevelFromLot,
   eLotDig,
   eMinLot,
   eContract,
   eSymbol
};

Se añaden como referencia las propiedades SYMBOL_VOLUME_MIN y SYMBOL_TRADE_CONTRACT_SIZE. El nombre del símbolo se «empaqueta» en un valor aproximado del tipo double utilizando la función pack2double, para posteriormente implementar una ordenación unificada por cualquiera de los campos, incluidos los nombres.

double pack2double(const string s)
{
   double r = 0;
   for(int i = 0i < StringLen(s); i++)
   {
      r = (r * 255) + (StringGetCharacter(si) % 255);
   }
   return r;
}

En esta etapa podríamos ejecutar ya el Asesor Experto e imprimir los resultados en un registro, algo como lo siguiente:

ArrayPrint(LME);

No obstante, mirar un registro todo el tiempo no es conveniente. Además, el formateo unificado de valores de distintas columnas, y más aún la presentación de filas «empaquetadas» en double, no puede calificarse de fácil de usar. Por lo tanto, se ha desarrollado la clase scoreboard (Tableau.mqh) para mostrar una tabla arbitraria en el gráfico. Además de que al preparar una tabla podemos controlar nosotros mismos el formato de cada campo (en el futuro, resaltarlo en un color diferente), esta clase permite ordenar interactivamente la tabla por cualquier columna: el primer clic del ratón ordena en una dirección, el segundo clic ordena en la dirección opuesta, y el tercero cancela la ordenación.

Aquí no describiremos la clase en detalle, pero puede estudiar su código fuente. Sólo es importante señalar que la interfaz se basa en objetos gráficos. De hecho, las celdas de la tabla están formadas por objetos del tipo OBJ_LABEL, y todas sus propiedades son ya familiares para el lector. Sin embargo, algunas de las técnicas utilizadas en el código fuente del scoreboard, en concreto, el trabajo con recursos gráficos y la medición del texto de visualización se presentará más adelante, en la séptima parte.

El constructor de la clase tableau toma varios parámetros:

  • prefix - prefijo para los nombres de los objetos gráficos creados
  • rows - número de filas
  • cols - número de columnas
  • height - altura de la línea en píxeles (-1 significa el doble del tamaño de la fuente)
  • width - anchura de la celda en píxeles
  • c - un ángulo del gráfico para anclar objetos
  • g - el espacio en píxeles entre celdas
  • f - tamaño de letra
  • font - nombre de la fuente para las celdas regulares
  • bold - el nombre de la fuente en negrita para los títulos
  • bgc - color de fondo
  • bgt - transparencia de fondo

class Tableau
{
public:
   Tableau(const string prefixconst int rowsconst int cols,
      const int height = 16const int width = 100,
      const ENUM_BASE_CORNER c = CORNER_RIGHT_LOWERconst int g = 8,
      const int f = 8const string font = "Consolas"const string bold = "Arial Black",
      const int mask = TBL_FLAG_COL_0_HEADER,
      const color bgc = 0x808080const uchar bgt = 0xC0)
      ...
};

El usuario puede configurar la mayoría de estos parámetros en las variables de entrada del Asesor Experto de LotMarginExposureTable.mq5.

input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER;
input int Gap = 16;
input int FontSize = 8;
input string DefaultFontName = "Consolas";
input string TitleFontName = "Arial Black";
input string MotoTypeFontsHint = "Consolas/Courier/Courier New/Lucida Console/Lucida Sans Typewriter";
input color BackgroundColor = 0x808080;
input uchar BackgroundTransparency = 0xC0// BackgroundTransparency (255 - opaque, 0 - glassy)

El número de columnas de la tabla está predeterminado, el número de líneas es igual al número de símbolos, más la línea superior con encabezados.

Es importante tener en cuenta que las fuentes para la tabla deben seleccionarse sin letra proporcional, por lo que en la variable MotoTypeFontsHint se proporciona una información sobre herramientas con un conjunto de fuentes monoespaciado estándar de Windows.

Los objetos gráficos creados se rellenan utilizando el método fill de la clase Tableau.

   bool fill(const string &data[], const string &hint[]) const;

Nuestro Asesor Experto pasa el array data de cadenas que se obtienen del array LME a través de una serie de transformaciones a través de StringFormat, así como el array hint con información sobre herramientas para los títulos.

En la siguiente imagen se muestra una parte del gráfico con el Asesor Experto en ejecución con la configuración predeterminada, pero con una lista especificada de símbolos «EURUSD,USDRUB,USDCNH,XAUUSD,XPDUSD».

Niveles de carga de márgenes y depósito con un lote mínimo para cada símbolo
Niveles de carga de márgenes y depósito con un lote mínimo para cada símbolo

Los nombres de los símbolos aparecen en la columna de la izquierda. Como encabezamiento de la primera columna se muestra el importe de los fondos (en este caso, libres en la cuenta en el momento actual, ya que en el parámetro de entrada Money se deja a 0). Al pasar el ratón por encima del nombre de la columna, aparecerá una información sobre herramientas con una explicación.

En las siguientes columnas:

  • L(E) lote calculado para cargar el nivel E del depósito del 5 % tras la transacción
  • L(R) lote calculado a riesgo R para el 5 % del depósito después de una operación sin éxito (rango en puntos e importe del riesgo - en la última columna)
  • E % carga de depósito después de la entrada con un lote mínimo
  • M % nivel de margen tras la entrada con el lote mínimo
  • MinL lote mínimo para cada símbolo
  • Contrato tamaño del contrato (1 lote) para cada símbolo
  • Riesgo beneficio/pérdida en dinero al negociar 1 lote y el mismo rango en puntos

En las columnas E % y M %, en este caso, se utilizan los lotes mínimos, ya que el parámetro de entrada Lot es 0 (por defecto).

Al cargar un 5 % del depósito, es posible operar con todos los símbolos seleccionados excepto «XPDUSD». Para este último, el volumen resultó ser de 0.03272, inferior al lote mínimo de 0.1, por lo que el resultado figura entre paréntesis. Si permitimos una carga del 20 % (introduzca 20 en el parámetro Exposure), obtendremos el lote mínimo para «XPDUSD» 0.1.

Si introducimos el valor 1 en el parámetro Lot, veremos actualizados los valores de las columnas E % y M % de la tabla (aumentará la carga y disminuirá el nivel de margen).

Niveles de carga de márgenes y depósito con un solo lote para cada símbolo
Niveles de carga de márgenes y depósito con un solo lote para cada símbolo

En la última captura de pantalla que ilustra el trabajo del Asesor Experto se muestra un gran conjunto de blue chips de la bolsa rusa MOEX ordenados por volumen calculado para una carga de depósito del 5 % (2ª columna). Entre los ajustes no estándar, cabe señalar que Lot=10, y el periodo de cálculo de la horquilla de precios y el riesgo es igual a MN1. El fondo se hace blanco translúcido, el anclaje se hace a la esquina superior izquierda del gráfico.

Lotes, carga de depósito y nivel de margen para instrumentos MOEX
Lotes, carga de depósito y nivel de margen para instrumentos MOEX