Solicitar validación: OrderCheck

Para realizar cualquier operación de trading, el programa MQL debe rellenar primero la estructura MqlTradeRequest con los datos necesarios. Antes de enviarla al servidor mediante funciones de trading, tiene sentido comprobar su corrección formal y evaluar las consecuencias de la solicitud; en concreto, la cantidad de margen que se requerirá y los fondos libres restantes. Esta comprobación la realiza la función OrderCheck.

bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result)

Si no hay fondos suficientes o si los parámetros se rellenan incorrectamente, la función devuelve false. Además, la función también reacciona con un rechazo cuando se desactiva el trading, tanto en el terminal en su conjunto como para un programa específico. Para el código de error, compruebe el campo retcode de la estructura result.

La comprobación satisfactoria de la estructura request y el entorno de trading finaliza con el estado true; sin embargo, esto no garantiza que la operación solicitada vaya a tener éxito con toda seguridad si se repite utilizando las funciones OrderSend o OrderSendAsync. Las condiciones de trading pueden cambiar entre llamadas o el bróker del servidor puede tener ajustes aplicados para un sistema de trading externo específico que no puedan satisfacerse en el algoritmo de verificación formal que realiza OrderCheck.

Para obtener una descripción del resultado financiero previsto, debe analizar los campos de la estructura result.

A diferencia de la función OrderCalcMargin, que calcula el margen estimado necesario para una única posición u orden propuesta, OrderCheck tiene en cuenta, aunque de modo simplificado, el estado general de la cuenta de trading. Así, rellena el campo margin de la estructura MqlTradeCheckResult y otros campos relacionados (margin_free, margin_level) con variables acumulativas que se formarán tras la ejecución de la orden. Por ejemplo, si una posición ya está abierta para cualquier instrumento en el momento de la llamada a OrderCheck y la solicitud que se está comprobando aumenta la posición, el campo margin reflejará el importe del depósito, incluidas las obligaciones de margen anteriores. Si la nueva orden contiene una operación en sentido opuesto, el margen no aumentará (en realidad, debería disminuir, porque una posición debería cerrarse completamente en una cuenta de compensación y el margen de cobertura debería aplicarse para posiciones opuestas en una cuenta de cobertura; sin embargo, la función no realiza cálculos tan precisos).

En primer lugar, OrderCheck es útil para los programadores que se encuentran en la fase inicial de familiarización con la API de trading, a fin de experimentar con las solicitudes sin enviarlas al servidor.

Vamos a probar el rendimiento de la función OrderCheck utilizando un simple Asesor Experto no de trading CustomOrderCheck.mq5. Lo hemos convertido en un Asesor Experto y no en un script para facilitar su uso: de esta manera permanecerá en el gráfico después de ser lanzado con la configuración actual, que se puede editar fácilmente cambiando los parámetros de entrada individuales. Con un script, tendríamos que empezar de nuevo configurando los campos cada vez a partir de los valores por defecto.

Para ejecutar la comprobación, pongamos un temporizador en OnInit.

void OnInit()
{
   // initiate pending execution
   EventSetTimer(1);
}

En cuanto al manejador del temporizador, el algoritmo principal se implementará allí. Al principio cancelamos el temporizador ya que necesitamos que el código se ejecute una vez, y luego esperamos a que el usuario cambie los parámetros.

void OnTimer()
{
   // execute the code once and wait for new user settings
   EventKillTimer();
   ...
}

Los parámetros de entrada del Asesor Experto repiten completamente el conjunto de campos de la estructura de solicitud de operación.

input ENUM_TRADE_REQUEST_ACTIONS Action = TRADE_ACTION_DEAL;
input ulong Magic;
input ulong Order;
input string Symbol;    // Symbol (empty = current _Symbol)
input double Volume;    // Volume (0 = minimal lot)
input double Price;     // Price (0 = current Ask)
input double StopLimit;
input double SL;
input double TP;
input ulong Deviation;
input ENUM_ORDER_TYPE Type;
input ENUM_ORDER_TYPE_FILLING Filling;
input ENUM_ORDER_TYPE_TIME ExpirationType;
input datetime ExpirationTime;
input string Comment;
input ulong Position;
input ulong PositionBy;

Muchos de ellos no afectan a la comprobación ni a los resultados financieros, pero se dejan para que pueda estar seguro de ello.

Por defecto, el estado de las variables corresponde a la solicitud de abrir una posición con el lote mínimo del instrumento actual. En concreto, el parámetro Type sin inicialización explícita obtendrá el valor 0, que es igual al miembro ORDER_TYPE_BUY de la estructura ENUM_ORDER_TYPE. En el parámetro Action, especificamos una inicialización explícita porque 0 no corresponde a ningún elemento de la enumeración ENUM_TRADE_REQUEST_ACTIONS (el primer elemento de TRADE_ACTION_DEAL es 1).

void OnTimer()
{
   ...
   // initialize structures with zeros
   MqlTradeRequest request = {};
   MqlTradeCheckResult result = {};
   
   // default values
   const bool kindOfBuy = (Type & 1) == 0;
   const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
   const double volume = Volume == 0 ?
      SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : Volume;
   const double price = Price == 0 ?
      SymbolInfoDouble(symbolkindOfBuy ? SYMBOL_ASK : SYMBOL_BID) : Price;
   ...

Completemos la estructura. Los robots reales normalmente sólo necesitan asignar unos pocos campos, pero como esta prueba es genérica, debemos asegurarnos de que se pasen todos los parámetros que introduzca el usuario.

   request.action = Action;
   request.magic = Magic;
   request.order = Order;
   request.symbol = symbol;
   request.volume = volume;
   request.price = price;
   request.stoplimit = StopLimit;
   request.sl = SL;
   request.tp = TP;
   request.deviation = Deviation;
   request.type = Type;
   request.type_filling = Filling;
   request.type_time = ExpirationType;
   request.expiration = ExpirationTime;
   request.comment = Comment;
   request.position = Position;
   request.position_by = PositionBy;
   ...

Tenga en cuenta que aquí aún no normalizamos precios ni lotes, aunque ello es necesario en el programa real. Así, esta prueba permite introducir valores «desiguales» y asegurarse de que conducen a un error. En los siguientes ejemplos se activará la normalización.

A continuación, llamamos a OrderCheck y registramos las estructuras request y result. Sólo nos interesa el campo retcode de este último, por lo que se imprime adicionalmente con «descifrado» como texto, macro TRCSTR (TradeRetcode.mqh). También puede analizar un campo de cadena comment, pero su formato puede cambiar para que sea más adecuado para mostrarlo al usuario.

   ResetLastError();
   PRTF(OrderCheck(requestresult));
   StructPrint(requestARRAYPRINT_HEADER);
   Print(TRCSTR(result.retcode));
   StructPrint(resultARRAYPRINT_HEADER2);
   ...

La salida de estructuras la proporciona una función auxiliar StructPrint basada en ArrayPrint. Por ello, seguiremos obteniendo una visualización «en bruto» de los datos. En concreto, los elementos de las enumeraciones se representan mediante números «tal cual». Más adelante desarrollaremos una función para que la salida de la estructura de MqlTradeRequest sea más transparente (fácil de usar) (véase TradeUtils.mqh).

Para facilitar el análisis de los resultados, al principio de la función OnTimer mostraremos el estado actual de la cuenta, y al final, para comparar, calcularemos el margen para una operación de trading determinada utilizando la función OrderCalcMargin.

void OnTimer()
{
   PRTF(AccountInfoDouble(ACCOUNT_EQUITY));
   PRTF(AccountInfoDouble(ACCOUNT_PROFIT));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_FREE));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
   ...
   // filling in the structure MqlTradeRequest
   // calling OrderCheck and printing results
   ...
   double margin = 0;
   ResetLastError();
   PRTF(OrderCalcMargin(Typesymbolvolumepricemargin));
   PRTF(margin);
}

A continuación se muestra un ejemplo de registros para XAUUSD con la configuración predeterminada:

AccountInfoDouble(ACCOUNT_EQUITY)=15565.22 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=15565.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=0.0 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order] [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

1 0 0 "XAUUSD" 0.01 1899.97 0.00 0.00 0.00 0 0 »

» [type_filling] [type_time] [expiration] [comment] [position] [position_by] [reserved]

» 0 0 1970.01.01 00:00:00 "" 0 0 0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

0 15565.22 15565.22 0.00 19.00 15546.22 81922.21 "Done" 0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=19.0 / ok

En el siguiente ejemplo se muestra una estimación del aumento previsto del margen en la cuenta, en la que ya existe una posición abierta que vamos a doblar.

AccountInfoDouble(ACCOUNT_EQUITY)=9999.540000000001 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=-0.83 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=79.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=9920.32 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=12622.49431961626 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order] [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

1 0 0 "PLZL.MM" 1.0 12642.0 0.0 0.0 0.0 0 0 »

» [type_filling] [type_time] [expiration] [comment] [position] [position_by] [reserved]

» 0 0 1970.01.01 00:00:00 "" 0 0 0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

0 10000.87 9999.54 -0.83 158.26 9841.28 6318.43 "Done" 0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=79.04000000000001 / ok

Pruebe a cambiar cualquier parámetro de la solicitud y compruebe si la solicitud tiene éxito. Las combinaciones incorrectas de parámetros provocarán códigos de error de la lista estándar, pero como hay muchas más opciones no válidas que reservadas (los errores más comunes), la función puede devolver a menudo el código genérico TRADE_RETCODE_INVALID (10013). A este respecto, se recomienda que emplee sus propias comprobaciones de estructura con un mayor grado de diagnóstico.

Al enviar solicitudes reales al servidor se utiliza el mismo código TRADE_RETCODE_INVALID en diversas circunstancias imprevistas; por ejemplo, al intentar reeditar una orden cuya operación de modificación ya se ha iniciado (pero aún no ha finalizado) en el sistema de trading externo.