
Kit de herramientas de negociación MQL5 (Parte 1): Desarrollo de una biblioteca EX5 de gestión de posiciones
Introducción
Como desarrollador de software, a menudo me resulta cómodo y eficaz crear mis propias bibliotecas de código o conjuntos de herramientas. Esto me ahorra tiempo porque no tengo que reescribir repetidamente el código para las tareas comunes requeridas en mis diversos proyectos de desarrollo MQL5. En esta serie de artículos, crearemos una biblioteca de operaciones MQL5 responsable de realizar tareas repetitivas en proyectos de desarrollo MQL5 comunes.
En este primer artículo hablaremos de qué son las bibliotecas de desarrolladores, por qué son importantes y los diferentes tipos de bibliotecas de código que se pueden crear con MQL5. Luego, procederemos a crear una librería de funciones MQL5 para manejar varias operaciones de posición como un ejemplo práctico para ayudar a solidificar tu entendimiento de cómo puedes usar una librería de código para un proyecto del mundo real.
¿Qué son las bibliotecas de código en MQL5?
Las bibliotecas de código MQL5 son funciones de código preescritas (ex5) o bibliotecas enlazadas dinámicamente (DLLs) que podemos utilizar para acelerar eficientemente el proceso de desarrollo de Asesores Expertos, indicadores personalizados, scripts o servicios para la plataforma MetaTrader 5.
Imagine una biblioteca de códigos como la caja de herramientas de un mecánico. Al igual que la caja de herramientas del mecánico contiene diversas herramientas (llaves, destornilladores, etc.) para tareas específicas, una biblioteca de código contiene funciones preescritas que cumplen funciones similares. Cada función se ocupa de una tarea concreta dentro de su programa, como abrir, cerrar o modificar posiciones, enviar notificaciones push, gestionar bases de datos, etc., de forma parecida a como una llave inglesa aprieta tornillos o un destornillador los gira.
Tipos de bibliotecas de código en MQL5
Como desarrollador MQL5, tienes varias opciones para construir tu biblioteca de código o conjunto de herramientas:
- Bibliotecas de funciones (*.ex5): Estas bibliotecas implementan un estilo de codificación procedimental, ofreciendo una colección de funciones preescritas para tareas específicas. También ofrecen la ventaja añadida de la seguridad que proporciona la encapsulación del código. Piense en ellas como herramientas individuales, cada una diseñada para un trabajo concreto.
- DLLs C++ de terceros: Puede integrar bibliotecas C++ preescritas como DLLs (Dynamic Linked Libraries). Esto amplía las capacidades de MQL5 al permitirle aprovechar funcionalidades externas.
MetaTrader 5 también ofrece formas adicionales de ampliar su conjunto de herramientas:
- Bibliotecas .NET: El MetaEditor proporciona una integración perfecta con las bibliotecas .NET a través de la importación «inteligente» de funciones, eliminando la necesidad de envoltorios personalizados.
- Módulo de lenguaje Python: El nuevo módulo de lenguaje Python le permite aprovechar las funcionalidades de Python en sus proyectos MQL5.
Si te sientes cómodo con C++, puedes crear DLLs personalizadas que se integren fácilmente en tus proyectos MQL5. Puedes utilizar herramientas como Microsoft Visual Studio para desarrollar archivos de código fuente C++ (CPP y H), compilarlos en DLLs, y luego importarlos a MetaEditor para utilizarlos con tu código MQL5.
Otros recursos de código similares a las bibliotecas MQL5
- Class/Include (*.mqh): Los archivos include de MQL5 utilizan programación orientada a objetos, ofreciendo clases preconstruidas que encapsulan datos y funcionalidades. Imagínatelas como herramientas más complejas que combinan funciones/métodos y estructuras de datos. En esencia, una clase o estructura no puede ser exportada para crear una libreria MQL5 (ex5) pero puedes usar punteros y referencias a clases o estructuras en las funciones de la libreria MQL5.
¿Por qué es necesario crear o utilizar una biblioteca MQL5 (ex5)?
Crear tus propias bibliotecas de código para proyectos MQL5 puede hacer que tu proceso de desarrollo sea mucho más eficiente. Estas bibliotecas, guardadas como archivos *.ex5, actúan como una caja de herramientas personal repleta de funciones que has optimizado para tareas específicas.
Fácil reutilización y diseño modular
Ahorre tiempo al no tener que reescribir funciones comunes cada vez que inicie un nuevo proyecto. Con las bibliotecas .ex5, se escribe el código una vez, se optimiza para obtener el mejor rendimiento, se exportan las funciones y luego se importan fácilmente a cualquier proyecto. Este enfoque modular mantiene el código limpio y organizado al separar las funcionalidades básicas de la lógica específica del proyecto. Cada parte de la biblioteca se convierte en un bloque de construcción para crear potentes sistemas de negociación.
Funciones seguras con encapsulación
La creación de bibliotecas MQL5 le ayuda a compartir sus funciones manteniendo oculto el código fuente. La encapsulación garantiza que los detalles de su código sean seguros y no visibles para los usuarios, al tiempo que proporciona una interfaz clara para la funcionalidad. Sólo tiene que compartir el archivo de biblioteca .ex5 junto con una documentación clara de las definiciones de las funciones exportadas con otros desarrolladores para que puedan importar las funciones de la biblioteca en sus proyectos. El archivo de biblioteca .ex5 oculta eficazmente el código fuente y cómo se codifican las funciones exportadas, manteniendo un espacio de trabajo seguro y encapsulado en el código principal de su proyecto.
Actualizaciones sencillas y ventajas a largo plazo
Cuando aparecen nuevas funciones del lenguaje o las antiguas quedan obsoletas, actualizar el código es fácil con las bibliotecas .ex5. Basta con actualizar el código de la biblioteca, volver a desplegarla y recompilar todos los proyectos que la utilicen para obtener automáticamente los cambios. Esto le ahorrará mucho tiempo y esfuerzo, sobre todo en proyectos de gran envergadura. La biblioteca actúa como un sistema central para su código base y una actualización o cambio afectará a todos los proyectos relacionados, por lo que su trabajo será más eficiente a largo plazo.
¿Cómo crear una biblioteca ex5 en MQL5?
Todas las bibliotecas .ex5 comienzan como archivos de código fuente .mq5 con la directiva de biblioteca #property añadida al principio del archivo y una o más funciones que se designan como exportables utilizando la palabra clave especial export. El archivo de código fuente de la librería .mq5 se transforma en un archivo de librería .ex5 una vez compilado, encapsulando u ocultando de forma segura el código fuente y dejándolo listo para importarlo y utilizarlo en otros proyectos MQL5.
Crear una nueva biblioteca MQL5 es fácil con el MetaEditor IDE. Siga los pasos que se indican a continuación para crear un nuevo archivo de código fuente de biblioteca (.mq5) que contendrá las funciones de gestión de posiciones y que posteriormente se compilará en una biblioteca .ex5.
Paso 1: Abra el MetaEditor IDE e inicie el Asistente MQL5 utilizando el botón Nuevo del menú.
Paso 2: Selecciona la opción Biblioteca y haz clic en Siguiente.
Paso 3: En la sección 'Propiedades generales del archivo de biblioteca', rellene la carpeta y el nombre para su nueva biblioteca «Libraries\Toolkit\PositionsManager» y proceda haciendo clic en 'Finalizar' para generar nuestra nueva biblioteca.
Una biblioteca MQL5 se almacena normalmente en un archivo con la extensión «.mq5». Este archivo contiene el código fuente de diferentes funciones escritas para diversas tareas específicas. Las bibliotecas de código se almacenan en la carpeta MQL5\Libraries por defecto dentro del directorio de instalación de MetaTrader 5. Una forma rápida de acceder a la carpeta Bibliotecas es utilizando el panel Navegador del MetaEditor.
Ahora tenemos un archivo/biblioteca MQL5 llamado PositionsManager.mq5 en blanco recién creado que contiene las directivas de propiedad de la cabeza y una función comentada. Recuerde guardar el nuevo archivo antes de continuar. Así es como se ve nuestro archivo de biblioteca recién generado:
//+------------------------------------------------------------------+ //| PositionsManager.mq5 | //| Copyright 2024, Wanateki Solutions Ltd. | //| https://www.wanateki.com | //+------------------------------------------------------------------+ #property library #property copyright "Copyright 2024, Wanateki Solutions Ltd." #property link "https://www.wanateki.com" #property version "1.00" //+------------------------------------------------------------------+ //| My function | //+------------------------------------------------------------------+ // int MyCalculator(int value,int value2) export // { // return(value+value2); // } //+------------------------------------------------------------------+
Componentes de un archivo de código fuente de una biblioteca MQL5
Un archivo de código fuente de la biblioteca MQL5 (.mq5) consta de dos componentes principales:
1. La directiva de biblioteca #property: Esta directiva de propiedades debe añadirse en la parte superior del archivo de código fuente de la biblioteca .mq5. La propiedad library permite al compilador saber que el archivo dado es una biblioteca y una indicación de ello se almacena en la cabecera de la biblioteca compilada en el archivo .ex5 que resulta de la compilación de la biblioteca .mq5.
#property library
2. Funciones Exportadas: En el corazón de las librerías MQL5 están las funciones exportadas. Estos son los componentes principales de una biblioteca, ya que son los encargados de realizar todo el trabajo pesado de las tareas que debe ejecutar la biblioteca. Una función MQL5 exportada es similar a una función MQL5 ordinaria excepto que se declara con el postmodificador de exportación lo que le permite ser importada y utilizada en otros programas MQL5 después de la compilación. El modificador export indica al compilador que añada la función especificada a la tabla de funciones ex5 exportadas por este archivo de biblioteca. Sólo las funciones que se declaran con el modificador export son accesibles y detectables desde otros programas MQL5 donde se pueden llamar después de la importación con la directiva especial #import.
//+------------------------------------------------------------------+ //| Example of an exported function with the export postmodifier | //+------------------------------------------------------------------+ int ExportedFunction(int a, int b) export { return(a + b); } //+------------------------------------------------------------------+
No se espera ni se requiere que las bibliotecas MQL5 ejecuten directamente ningún manejo de eventos estándar como asesores expertos, indicadores personalizados o scripts. Esto significa que no tienen ninguna de las funciones estándar como OnInit(), OnDeinit(), o OnTick().
Biblioteca de funciones MQL5 para la gestión de posiciones
La gestión de posiciones es una tarea fundamental para todo Asesor Experto en desarrollo. Estas operaciones esenciales forman el núcleo de cualquier sistema de comercio algorítmico. Para evitar la codificación repetitiva, los desarrolladores de MQL5 deben usar bibliotecas para administrar las posiciones de manera eficiente. Esto garantiza que los desarrolladores no reescriban el mismo código de gestión de posiciones para cada Asesor Experto.
En esta sección, añadiremos algo de código a nuestro recién creado archivo PositionsManager.mq5 para crear una librería de gestión de posiciones usando MQL5. Esta biblioteca manejará todas las operaciones relacionadas con la posición. Al importarlo a su código de Asesor Experto, puede ejecutar y administrar posiciones de manera efectiva, manteniendo su base de código limpia y organizada.
Variables globales
Las operaciones de solicitud de comercio en MQL5 se representan mediante una estructura especial predefinida conocida como MqlTradeRequest. Esta estructura incluye todos los campos necesarios para ejecutar operaciones comerciales y garantiza que todos los datos de solicitud de orden necesarios estén empaquetados dentro de una única estructura de datos. Cuando se procesa una solicitud de operación, el resultado se guarda en otra estructura predefinida llamada MqlTradeResult. El MqlTradeResult se encarga de darnos información detallada sobre el resultado de la solicitud de operación, incluyendo si la solicitud ha tenido éxito y cualquier dato relevante sobre la ejecución de la operación.
Dado que utilizaremos estas dos estructuras de datos especiales en la mayoría de nuestras funciones, empecemos por declararlas como variables globales para que estén disponibles en toda la biblioteca.
//---Global variables //------------------------------- //-- Trade operations request and result data structures MqlTradeRequest tradeRequest; MqlTradeResult tradeResult; //-------------------------------
Función de gestión de errores de posición
La primera función de nuestra biblioteca será una función de gestión de errores. Cuando abrimos, modificamos y cerramos posiciones con MQL5, a menudo obtenemos diferentes tipos de errores que nos obligan a abortar la operación o a reenviar la petición de gestión de posición al servidor de operaciones en otro momento. Crear una función dedicada a supervisar y recomendar la acción correcta es una necesidad para tener una biblioteca correctamente codificada y optimizada.
Antes de que podamos crear la función de gestión de errores, tenemos que entender los diversos códigos de error de gestión de posición MQL5 que podemos encontrar. En la siguiente tabla se destacan algunos de los códigos de error devueltos por el servidor de operaciones y en tiempo de ejecución que debemos detectar y superar al gestionar diferentes operaciones de posición. Puede encontrar una lista completa de todos los códigos de errores y advertencias en la documentación de MQL5.
Código | Constante de código | Descripción | Medidas adoptadas | Tipo de código |
---|---|---|---|---|
10004 | TRADE_RETCODE_REQUOTE | Recotización. | Vuelva a enviar la solicitud de pedido. | Código de retorno del servidor de comercio (RETCODE) |
10008 | TRADE_RETCODE_PLACED | Pedido realizado. | No hay medidas que tomar. Operaciones con éxito. | Código de retorno del servidor de comercio (RETCODE) |
10009 | TRADE_RETCODE_DONE | Solicitud completada. | No hay medidas que tomar. Operaciones completadas. | Código de retorno del servidor de comercio (RETCODE) |
10013 | TRADE_RETCODE_INVALID | Solicitud no válida | Deje de reenviar la solicitud de inicialización de la orden y actualice los detalles de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10014 | TRADE_RETCODE_INVALID_VOLUME | Volumen no válido en la solicitud. | Deje de reenviar la solicitud de inicialización de la orden y actualice los detalles de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10016 | TRADE_RETCODE_INVALID_STOPS | Stops no válidos en la solicitud. | Deje de reenviar la solicitud de inicialización de la orden y actualice los detalles de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10017 | TRADE_RETCODE_TRADE_DISABLED | El comercio está desactivado. | Finaliza todas las operaciones comerciales y deja de reenviar la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10018 | TRADE_RETCODE_MARKET_CLOSED | El mercado está cerrado. | Detener el reenvío de la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10019 | TRADE_RETCODE_NO_MONEY | No hay dinero suficiente para completar la solicitud. | Deje de reenviar la solicitud de inicialización de la orden y actualice los detalles de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10026 | TRADE_RETCODE_SERVER_DISABLES_AT | Autotrading desactivado por el servidor. | El servidor no permite el comercio. Detener el reenvío de la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10027 | TRADE_RETCODE_CLIENT_DISABLES_AT | Autotrading desactivado por el terminal del cliente. | El terminal del cliente ha desactivado el comercio del EA. Detener el reenvío de la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10034 | TRADE_RETCODE_LIMIT_VOLUME | El volumen de órdenes y posiciones para el símbolo ha alcanzado el límite. | Detener el reenvío de la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10011 | TRADE_RETCODE_ERROR | Error de procesamiento de la solicitud. | Sigue reenviando la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10012 | TRADE_RETCODE_TIMEOUT | Solicitud cancelada por tiempo de espera. | Pausa la ejecución durante unos milisegundos y luego sigue reenviando la petición de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10015 | TRADE_RETCODE_INVALID_PRICE | Precio no válido en la solicitud. | Actualiza el precio de entrada de la orden y reenvía la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10020 | TRADE_RETCODE_PRICE_CHANGED | Los precios han cambiado. | Actualiza el precio de entrada de la orden y reenvía la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10021 | TRADE_RETCODE_PRICE_OFF | No hay cotizaciones para tramitar la solicitud. | Pausa la ejecución durante unos milisegundos y, a continuación, vuelve a enviar la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10024 | TRADE_RETCODE_TOO_MANY_REQUESTS | Solicitudes demasiado frecuentes. | Ponga en pausa la ejecución durante unos segundos y, a continuación, vuelva a enviar la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
10031 | TRADE_RETCODE_CONNECTION | No hay conexión con el servidor de comercio. | Pausa la ejecución durante unos milisegundos y, a continuación, vuelve a enviar la solicitud de inicialización de la orden. | Código de retorno del servidor de comercio (RETCODE) |
0 | ERR_SUCCESS | La operación se ha completado con éxito. | Deja de reenviar la solicitud. Pedido enviado correctamente. | Código de error de ejecución |
4752 | ERR_TRADE_DISABLED | Prohibido operar con Asesores Expertos. | Detener el reenvío de la solicitud de inicialización de la orden. | Código de error de ejecución |
4753 | ERR_TRADE_POSITION_NOT_FOUND | Posición no encontrada. | Detener el reenvío de la solicitud de operación comercial. | Código de error de ejecución |
4754 | ERR_TRADE_ORDER_NOT_FOUND | Pedido no encontrado. | Deje de reenviar la solicitud de pedido. | Código de error de ejecución |
4755 | ERR_TRADE_DEAL_NOT_FOUND | Oferta no encontrada. | Deje de reenviar la solicitud de pedido. | Código de error de ejecución |
Vamos a crear nuestra primera función en la biblioteca para procesar los errores mencionados anteriormente. La función de procesamiento de errores, denominada ErrorAdvisor(), será de tipo booleano, lo que significa que devolverá True o False dependiendo del tipo de error encontrado. Tomará dos argumentos para ayudar en el procesamiento de datos:
- callingFunc (string): Este parámetro almacena el nombre o identificador de la función que llama a ErrorAdvisor().
- symbol (string): Este parámetro almacena el nombre del símbolo del activo sobre el que se está trabajando.
- tradeServerErrorCode (integer): Este parámetro almacena el tipo de error encontrado.
Si el error es recuperable y no crítico, la función ErrorAdvisor() devolverá True. Esto indica a la función de llamada que la orden aún no se ha ejecutado y que debe reenviar la solicitud de orden. Si ErrorAdvisor() devuelve False, significa que la función llamante debe dejar de enviar más solicitudes de órdenes, puesto que la orden ya se ha ejecutado correctamente o se ha producido un error crítico irrecuperable.
Recuerde colocar el posmodificador export antes de la llave de apertura de la función para indicar que la función pertenece a una biblioteca y está destinada a ser utilizada en otros programas MQL5.
//------------------------------------------------------------------+ // ErrorAdvisor(): Error analysis and processing function. | // Returns true if order opening failed and order can be re-sent | // Returns false if the error is critical and can not be executed | //------------------------------------------------------------------+ bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode) export { //-- place the function body here }
Comencemos declarando e inicializando una variable entera para almacenar el error de ejecución actual. Nombra el entero runtimeErrorCode y llama a la función GetLastError() para almacenar el error en tiempo de ejecución más reciente. Utilizaremos esta variable para procesar cualquier error en tiempo de ejecución que podamos encontrar en el segundo operador switch anidado.
//-- save the current runtime error code int runtimeErrorCode = GetLastError();
Escanearemos y procesaremos los errores de retorno del servidor de comercio (retcode) y los errores de ejecución utilizando operadores switch anidados. Esto es conveniente porque nos permite identificar rápidamente el tipo de error, imprimir una descripción para el usuario en el registro del Asesor Experto, e instruir a la función de llamada sobre cómo proceder. Observará que he agrupado los errores en dos categorías:
- Errores que indican la finalización o no ejecución de la orden: Estos errores devolverán False, indicando a la función de llamada que deje de enviar solicitudes de orden.
- Errores que indican pedidos incompletos: Estos errores devolverán True, indicando a la función de llamada que reenvíe la solicitud de pedido.
El primer operador de conmutación se ocupará de los códigos de retorno del servidor de comercio y el segundo operador de conmutación anidado se ocupará de los códigos de error en tiempo de ejecución. Este enfoque minimiza el código de la función al evitar comprobaciones secuenciales para cada código de error o advertencia.
Ahora, vamos a codificar la primera sentencia switch para examinar el tradeServerErrorCode encontrado y ver qué tipo de error reportó el servidor de trading.
switch(tradeServerErrorCode)//-- check for trade server errors { //--- Cases to scan different retcodes/server return codes }
Dentro de la sentencia switch, añadiremos casos para diferentes códigos de error devueltos por el servidor de negociación. He aquí algunas de ellas:
Requote (code 10004): Esto significa que el precio ha cambiado desde que el usuario intentó abrir la orden. En este caso, querremos imprimir un mensaje en el registro, esperar unos milisegundos utilizando la función Sleep() y, a continuación, indicar a la función de llamada que vuelva a intentar abrir la orden.
case 10004: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!"); Sleep(10); return(true); //--- Exit the function and retry opening the order again
Order Placed Successfully (code 10008): Si se devuelve este código, todo ha ido bien y se ha realizado el pedido. Podemos imprimir un mensaje en el registro diciendo que el pedido se ha realizado correctamente y luego decirle a la función de llamada que deje de intentar abrir el pedido.
case 10008: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!"); return(false); //--- success - order placed ok. exit function
Añadiremos casos similares para otros errores del servidor de operaciones (códigos 10009, 10011, 10012, etc.), siguiendo la misma lógica de imprimir un mensaje para el registro del Asesor Experto, esperar un poco si es necesario, e indicar a la función que llama si debe reintentar enviar de nuevo la solicitud de operación.
Si la sentencia switch para los errores del servidor de comercio no encuentra una coincidencia, significa que el error podría ser un error de tiempo de ejecución y sólo se puede encontrar mediante la creación de otra sentencia switch que escanea el error de tiempo de ejecución actual devuelto por la función GetLastError(). Para afrontar este reto, utilizaremos una sentencia switch anidada en la sección default de la sentencia switch anterior para examinar el valor del runtimeErrorCode que guardamos anteriormente.
default: switch(runtimeErrorCode)//-- check for runtime errors //-- Add cases for different runtime errors here } }
Repita el mismo proceso que hicimos con los errores del servidor comercial y añada casos para diferentes códigos de error en tiempo de ejecución:
No Errors (code 0): Esto significa que todo funcionó bien desde la perspectiva de nuestro programa. Podemos imprimir un mensaje en el registro diciendo que no hubo errores y luego decirle a la función de llamada que deje de intentarlo.
case 0: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order
Seguiremos añadiendo casos similares para otros errores de ejecución (códigos 4752, 4753, 4754, etc.), siguiendo la misma lógica de imprimir un mensaje para el registro de errores del Asesor Experto e indicando a la función de llamada que detenga o continúe con la solicitud de operación.
Dado que sólo hemos tenido en cuenta los códigos de error más importantes que pueden afectar al proceso de ejecución de la orden y no hemos escaneado ni procesado todos los posibles códigos de error existentes, es posible que nos encontremos con un error que actualmente no se procesa ni se tiene en cuenta en nuestro código actual. En este caso, imprimiremos un mensaje en el registro indicando que se ha producido un error desconocido (otro), especificaremos el error de código de retorno del servidor y el error de tiempo de ejecución encontrado y, a continuación, indicaremos a la función de llamada que deje de intentar abrir el pedido.
default: //--- All other error codes Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order break;
Aquí está la función completa de gestión de errores ErrorAdvisor() con todos los segmentos de código completados:
bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode) export { //-- save the current runtime error code int runtimeErrorCode = GetLastError(); switch(tradeServerErrorCode)//-- check for trade server errors { case 10004: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!"); Sleep(10); return(true); //--- Exit the function and retry opening the order again case 10008: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!"); return(false); //--- success - order placed ok. exit function case 10009: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request completed!"); return(false); //--- success - order placed ok. exit function case 10011: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request processing error!"); Sleep(10); return(true); //--- Exit the function and retry opening the order again case 10012: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request canceled by timeout!"); Sleep(100); return(true); //--- Exit the function and retry opening the order again case 10015: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Invalid price in the request!"); Sleep(10); return(true); //--- Exit the function and retry opening the order again case 10020: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Prices changed!"); Sleep(10); return(true); //--- Exit the function and retry opening the order again case 10021: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") There are no quotes to process the request!"); Sleep(100); return(true); //--- Exit the function and retry opening the order again case 10024: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Too frequent requests!"); Sleep(1000); return(true); //--- Exit the function and retry opening the order again case 10031: Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") No connection with the trade server!"); Sleep(100); return(true); //--- Exit the function and retry opening the order again default: switch(runtimeErrorCode)//-- check for runtime errors case 0: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order case 4752: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Trading by Expert Advisors prohibited!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order case 4753: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Position not found!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order case 4754: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Order not found!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order case 4755: Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Deal not found!"); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order default: //--- All other error codes Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode); ResetLastError(); //--- reset error cache return(false); //--- Exit the function and stop trying to open order break; } } }
Función de permisos comerciales
Esta función verifica si la negociación está actualmente permitida en el terminal de negociación. Tiene en cuenta la autorización del usuario, del servidor de operaciones y del intermediario. La función se ejecuta antes de que se envíe cualquier operación de posición o solicitud de orden al servidor de negociación para su procesamiento.
Llamaremos a la función TradingIsAllowed() y le daremos un tipo de retorno booleano. Si la negociación está permitida y habilitada, devolverá un valor booleano de True y un valor booleano de False si la negociación automática está deshabilitada o no permitida. No tendrá parámetros ni argumentos y contendrá el siguiente segmento de código:
//+-----------------------------------------------------------------------+ //| TradingIsAllowed() verifies whether auto-trading is currently allowed | | //+-----------------------------------------------------------------------+ bool TradingIsAllowed() export { if( !IsStopped() && MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT) ) { return(true);//-- trading is allowed, exit and return true } return(false);//-- trading is not allowed, exit and return false }
Función de registro e impresión de detalles del pedido
Se trata de una función sencilla para registrar e imprimir las propiedades de las distintas operaciones comerciales o solicitudes de apertura de posiciones. Proporciona una forma sencilla para que el usuario de un Asesor Experto se mantenga actualizado sobre el estado de las operaciones del Asesor Experto a través de la pestaña de registro del EA de MetaTrader 5. Observará que esta función no está exportada y, por tanto, sólo es accesible a través de otras funciones exportadas que la llamen explícitamente o la ejecuten. La llamaremos PrintOrderDetails() y especificaremos que no devolverá ningún dato convirtiéndola en una función de tipo void y que tomará una variable string como parámetro o argumento de entrada.
//+-----------------------------------------------------------------------+ //| PrintOrderDetails() prints the order details for the EA log | //+-----------------------------------------------------------------------+ void PrintOrderDetails(string header) { string orderDescription; //-- Print the order details orderDescription += "_______________________________________________________________________________________\r\n"; orderDescription += "--> " + tradeRequest.symbol + " " + EnumToString(tradeRequest.type) + " " + header + " <--\r\n"; orderDescription += "Order ticket: " + (string)tradeRequest.order + "\r\n"; orderDescription += "Volume: " + StringFormat("%G", tradeRequest.volume) + "\r\n"; orderDescription += "Price: " + StringFormat("%G", tradeRequest.price) + "\r\n"; orderDescription += "Stop Loss: " + StringFormat("%G", tradeRequest.sl) + "\r\n"; orderDescription += "Take Profit: " + StringFormat("%G", tradeRequest.tp) + "\r\n"; orderDescription += "Comment: " + tradeRequest.comment + "\r\n"; orderDescription += "Magic Number: " + StringFormat("%d", tradeRequest.magic) + "\r\n"; orderDescription += "Order filling: " + EnumToString(tradeRequest.type_filling)+ "\r\n"; orderDescription += "Deviation points: " + StringFormat("%G", tradeRequest.deviation) + "\r\n"; orderDescription += "RETCODE: " + (string)(tradeResult.retcode) + "\r\n"; orderDescription += "Runtime Code: " + (string)(GetLastError()) + "\r\n"; orderDescription += "---"; Print(orderDescription); }
Funciones de apertura de posiciones
Agruparemos estas funciones en dos categorías:
- OpenBuyPositions(): Esta función exportable se encargará de abrir nuevas posiciones de compra, como su propio nombre indica.
- OpenSellPositions(): Esta función exportable se encargará únicamente de abrir nuevas posiciones de venta, como su propio nombre indica.
Función OpenBuyPositions()
Esta función es de tipo bool y devuelve un valor true si se consigue abrir una nueva posición de compra según lo indicado y false si no es posible abrir una nueva posición de compra. Recibe seis parámetros o argumentos:
- ulong magicNumber: Se utiliza para guardar el número mágico de los Asesores Expertos para facilitar la modificación o finalización de posiciones con un filtrado sencillo de las mismas.
- string symbol: Guarda el nombre del símbolo o activo para el que se está ejecutando la solicitud.<
- double lotSize: Almacena el volumen o la cantidad de la posición de compra que se va a abrir.
- int sl: Almacena el valor del Stop Loss en puntos/pips de la posición de compra.
- int tp: Almacena el valor del Take Profit en puntos/pips de la posición de compra.
- string positionComment: Sirve para guardar o almacenar el comentario de la posición de compra.
Comenzaremos codificando la definición de la función. Observa que hemos colocado el modificador export antes de la llave de apertura de la función para indicar al compilador que haga esta función exportable para su uso en otros proyectos MQL5 que implementen esta librería.
bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export { //--- Function body }
Antes de intentar abrir una orden, comprobemos si nuestro Asesor Experto tiene permiso para operar. Lo haremos llamando a la función TradingIsAllowed() que creamos anteriormente. La función TradingIsAllowed() buscará varias configuraciones y permisos para asegurar que la negociación automatizada está habilitada.
Si TradingIsAllowed() devuelve false, significa que la negociación está desactivada y nuestro Asesor Experto no puede abrir órdenes. En este caso, devolveremos inmediatamente false desde esta función también y saldremos de ella sin abrir una nueva orden de compra.
//-- first check if the ea is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function }
El siguiente paso consistirá en prepararse para la solicitud de la orden eliminando los datos sobrantes de intentos de operaciones anteriores. Para ello, vamos a utilizar la función ZeroMemory() en las dos estructuras de datos globales de comercio que creamos al principio de nuestro archivo: tradeRequest y tradeResult. Estos almacenarán los detalles de la orden de compra que queremos abrir y los resultados devueltos por el servidor de operaciones, respectivamente.
//-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult);
Ahora, vamos a establecer los parámetros para la apertura de una posición de compra en la variable de estructura de datos tradeRequest :
- tradeRequest.type: Establézcalo como ORDER_TYPE_BUY para indicar una orden de compra.
- tradeRequest.action: Establézcalo como TRADE_ACTION_DEAL para especificar la apertura de una nueva posición.
- tradeRequest.magic: Aquí asignaremos el magicNumber proporcionado como argumento. Esto ayuda a identificar las órdenes abiertas por nuestro Asesor Experto.
- tradeRequest.symbol: Asigna el símbolo proporcionado como argumento aquí, especificando el par de divisas a negociar.
- tradeRequest.tp y tradeRequest.sl: Por ahora los pondremos a 0, ya que más adelante manejaremos los niveles de Take Profit (TP) y Stop Loss (SL).
- tradeRequest.comment: Asigna aquí el positionComment proporcionado como argumento, que puede utilizarse para añadir un comentario de texto a la orden.
- tradeRequest.deviation: Ajústelo para permitir una desviación de hasta el doble del diferencial actual para el símbolo que se está negociando. Esto da a la plataforma cierta flexibilidad a la hora de encontrar un precio de orden coincidente y limita las recotizaciones de órdenes.
//-- initialize the parameters to open a buy position tradeRequest.type = ORDER_TYPE_BUY; tradeRequest.action = TRADE_ACTION_DEAL; tradeRequest.magic = magicNumber; tradeRequest.symbol = symbol; tradeRequest.tp = 0; tradeRequest.sl = 0; tradeRequest.comment = positionComment; tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;
La normalización del volumen de la orden o del tamaño del lote es un paso muy importante. Lo haremos ajustando el argumento o parámetro lotSize para asegurarnos de que entra dentro del rango permitido para el símbolo elegido. Así es como lo ajustaremos:
//-- set and moderate the lot size or volume lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN)); //-- Verify that volume is not less than allowed minimum lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)); //-- Verify that volume is not more than allowed maximum lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step tradeRequest.volume = lotSize;
A continuación, utilizaremos la función ResetLastError() para borrar cualquier código de error de ejecución anterior de la plataforma. Esto asegura que obtendremos un código de error preciso de la función ErrorAdvisor() más tarde.
//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function ResetLastError();
Ahora, introduciremos un bucle que puede intentar abrir la orden hasta 100 veces (con un máximo de 101 iteraciones). Este bucle actúa como mecanismo de seguridad en caso de que la orden no se abra al primer intento debido a fluctuaciones temporales del mercado u otras razones.
for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries) { //--- Place the order request code to open a new buy position }
La primera tarea del bucle for será actualizar el precio de apertura de la orden en cada iteración en caso de recotización del precio de la orden. Utilizaremos SymbolInfoDouble(symbol, SYMBOL_ASK) para obtener el precio de venta actual del símbolo y asignarlo a tradeRequest.price. Esto garantiza que nuestra solicitud de pedido refleje el último precio de mercado.
//--- update order opening price on each iteration tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);
A continuación, actualizaremos los valores de Take Profit y Stop Loss en cada iteración para que coincidan con el precio de entrada actualizado mientras también normalizamos sus valores para asegurarnos de que se ajustan a los requisitos de precisión requeridos y, a continuación, los asignamos a la estructura de datos tradeRequest.tp y tradeRequest.tp.
//-- set the take profit and stop loss on each iteration if(tp > 0) { tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits); } if(sl > 0) { tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits); }
A continuación, enviaremos la orden al servidor de operaciones para su ejecución. Para esta tarea, utilizaremos la función OrderSend(), pasando tradeRequest y tradeResult como argumentos vacíos. Esta función intenta abrir la orden basándose en las especificaciones almacenadas en tradeRequest. Los resultados, incluidos los códigos de éxito o error, se almacenarán en la variable tradeResult después de que complete su ejecución.
La sentencia if que hemos colocado para la función OrderSend() nos permitirá comprobar y confirmar si la solicitud de pedido se ha realizado correctamente o no. Si OrderSend() devuelve true, significa que la solicitud de pedido se envió correctamente y si devuelve false significa que la solicitud falló.
A continuación, llamamos a nuestra función codificada anteriormente PrintOrderDetails() con el mensaje «Sent OK» para registrar esta información en el registro del Asesor Experto.
Además, compruebe el tradeResult.retcode para confirmar que la orden se ha ejecutado correctamente. Devuelve true desde la función OpenBuyPosition() para indicar el éxito, y utiliza break para salir del bucle por completo. Si OrderSend() devuelve false (lo que significa que la solicitud de pedido ha fallado), significa que se ha producido un problema. Llamaremos a PrintOrderDetails() con el mensaje «Sending Failed» para registrar esta información. También imprimiremos un mensaje de error para resaltar los diferentes códigos de error que nos hemos encontrado y devolveremos false desde la función OpenBuyPosition() para indicar el fallo, y utilizaremos break para salir del bucle.
//--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Print the order details PrintOrderDetails("Sent OK"); //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price); PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); Print("_______________________________________________________________________________________"); return(true); //-- exit the function break; //--- success - order placed ok. exit the for loop } } else //-- Order request failed { //-- Print the order details PrintOrderDetails("Sending Failed"); //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped()) { Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume); Print("_______________________________________________________________________________________"); return(false); //-- exit the function break; //-- exit the for loop } }
Aquí está OpenBuyPosition() con todos los segmentos de código y su secuencia correcta:
//-------------------------------------------------------------------+ // OpenBuyPosition(): Function to open a new buy entry order. | //+------------------------------------------------------------------+ bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export { //-- first check if the ea is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function } //-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //-- initialize the parameters to open a buy position tradeRequest.type = ORDER_TYPE_BUY; tradeRequest.action = TRADE_ACTION_DEAL; tradeRequest.magic = magicNumber; tradeRequest.symbol = symbol; tradeRequest.tp = 0; tradeRequest.sl = 0; tradeRequest.comment = positionComment; tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2; //-- set and moderate the lot size or volume lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN)); //-- Verify that volume is not less than allowed minimum lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)); //-- Verify that volume is not more than allowed maximum lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step tradeRequest.volume = lotSize; //--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function ResetLastError(); for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries) { //--- update order opening price on each iteration tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK); //-- set the take profit and stop loss on each iteration if(tp > 0) { tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits); } if(sl > 0) { tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits); } //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Print the order details PrintOrderDetails("Sent OK"); //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price); PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); Print("_______________________________________________________________________________________"); return(true); //-- exit the function break; //--- success - order placed ok. exit the for loop } } else //-- Order request failed { //-- Print the order details PrintOrderDetails("Sending Failed"); //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped()) { Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume); Print("_______________________________________________________________________________________"); return(false); //-- exit the function break; //-- exit the for loop } } } return(false); }
Función OpenSellPositios()
Esta función es muy similar a la función OpenBuyPosition() que terminamos de codificar anteriormente y sigue los mismos procedimientos, con algunas diferencias como el tipo de orden que se procesa. La función OpenSellPosition() está diseñada para abrir una nueva posición de venta. Incluye un bucle for que realiza múltiples intentos en caso de fallo, lo que aumenta significativamente su tasa de éxito siempre que se le proporcionen parámetros válidos de solicitud de operaciones. Aquí está el código de la función OpenSellPosition():
//-------------------------------------------------------------------+ // OpenSellPosition(): Function to open a new sell entry order. | //+------------------------------------------------------------------+ bool OpenSellPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export { //-- first check if the ea is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function } //-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //-- initialize the parameters to open a sell position tradeRequest.type = ORDER_TYPE_SELL; tradeRequest.action = TRADE_ACTION_DEAL; tradeRequest.magic = magicNumber; tradeRequest.symbol = symbol; tradeRequest.tp = 0; tradeRequest.sl = 0; tradeRequest.comment = positionComment; tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2; //-- set and moderate the lot size or volume lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN)); //-- Verify that volume is not less than allowed minimum lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)); //-- Verify that volume is not more than allowed maximum lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step tradeRequest.volume = lotSize; ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(int loop = 0; loop <= 100; loop++) //-- try opening the order (101 max) times untill it is successful { //--- update order opening price on each iteration tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_BID); //-- set the take profit and stop loss on each iteration if(tp > 0) { tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits); } if(sl > 0) { tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits); } //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Print the order details PrintOrderDetails("Sent OK"); //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { Print("CONFIRMED: Successfully openend a ", symbol, " SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price); PrintFormat("retcode=%u deal=%I64u order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order); Print("_______________________________________________________________________________________"); return(true); //-- exit function break; //--- success - order placed ok. exit for loop } } else //-- Order request failed { //-- Print the order details PrintOrderDetails("Sending Failed"); //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped()) { Print(symbol, " ERROR opening a SELL POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume); Print("_______________________________________________________________________________________"); return(false); //-- exit function break; //-- exit for loop } } } return(false); }
Función de modificación de la posición Stop Loss y Take Profit
Nuestra siguiente función en la librería se llamará SetSlTpByTicket() y se encargará de modificar los niveles de Stop Loss (SL) y Take Profit (TP) de una posición abierta existente utilizando el ticket de la posición como mecanismo de filtrado. Toma el número de ticket de la posición, el SL deseado en pips (puntos) y el TP deseado en pips (puntos) como argumentos e intenta actualizar el SL y el TP de la posición en el servidor de negociación. La función devolverá un valor booleano de (true o false). Si los niveles de Stop Loss y Take Profit se modifican con éxito para la posición, devolverá true, y si no es capaz de establecer o modificar con éxito los niveles de Stop Loss o Take Profit devolverá false.
He aquí un desglose de los argumentos o parámetros de la función SetSlTpByTicket():
- ulong positionTicket: Se trata de un identificador único para la posición que vamos a modificar.
- int sl: Este es el nivel de Stop Loss deseado en pips (puntos) desde el precio de apertura.
- int tp: Es el nivel de Take Profit deseado en pips (puntos) desde el precio de apertura.
Recuerde utilizar el modificador export post en la definición de la función para hacerla accesible externamente para nuestra biblioteca.
bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export { //-- Function body }
Al igual que con nuestras otras funciones anteriores, primero comprobaremos si se permite la negociación para nuestro Asesor Experto utilizando la función TradingIsAllowed(). Si la negociación está desactivada, la función sale y devuelve false.
//-- first check if the EA is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function }
Antes de seleccionar la posición especificada mediante el argumento positionTicket, primero restableceremos la variable de sistema de código de error en tiempo de ejecución para obtener una respuesta de error procesable precisa para la función ErrorAdvisor() más adelante. Si la selección tiene éxito, se imprime un mensaje indicando que la posición está seleccionada dándonos luz verde para acceder a las propiedades de la posición. Si la selección falla, se imprime un mensaje de error junto con el código de error recuperado mediante GetLastError(). A continuación, la función sale devolviendo false.
//--- Confirm and select the position using the provided positionTicket ResetLastError(); //--- Reset error cache incase of ticket selection errors if(PositionSelectByTicket(positionTicket)) { //---Position selected Print("\r\n_______________________________________________________________________________________"); Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP."); } else { Print("\r\n_______________________________________________________________________________________"); Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError()); return(false); //-- Exit the function }
Después de seleccionar la posición, tenemos que recopilar y guardar algunos detalles sobre ella. Utilizaremos esta información para los cálculos y como referencia posterior.
//-- create variables to store the calculated tp and sl prices to send to the trade server double tpPrice = 0.0, slPrice = 0.0; double newTpPrice = 0.0, newSlPrice = 0.0; //--- Position ticket selected, save the position properties string positionSymbol = PositionGetString(POSITION_SYMBOL); double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); double volume = PositionGetDouble(POSITION_VOLUME); double currentPositionSlPrice = PositionGetDouble(POSITION_SL); double currentPositionTpPrice = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
También necesitaremos información específica sobre los símbolos:
//-- Get some information about the positions symbol int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL); double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT); double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT); int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);
Ahora, calculemos los nuevos precios de Stop Loss (SL) y Take Profit (TP) basándonos en el precio de apertura de la posición, el SL/TP deseado en pips (puntos) y el tipo de posición (Compra o Venta). Almacenaremos estos cálculos iniciales antes de validarlos:
//--Save the non-validated tp and sl prices if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices { newSlPrice = entryPrice - (sl * symbolPoint); newTpPrice = entryPrice + (tp * symbolPoint); } else //-- SELL POSITION { newSlPrice = entryPrice + (sl * symbolPoint); newTpPrice = entryPrice - (tp * symbolPoint); }
A continuación, imprimimos un resumen de los datos de posición que hemos recopilado anteriormente:
//-- Print position properties before modification string positionProperties = "--> " + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" + " <--\r\n"; positionProperties += "------------------------------------------------------------\r\n"; positionProperties += "Ticket: " + (string)positionTicket + "\r\n"; positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n"; positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n"; positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + " -> New Proposed SL: " + (string)newSlPrice + "\r\n"; positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + " -> New Proposed TP: " + (string)newTpPrice + "\r\n"; positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "---"; Print(positionProperties);
Dado que los valores proporcionados por el usuario de la biblioteca para SL y TP podrían no ser directamente utilizables por la función OrderSend(). Tenemos que hacer una simple validación de sus valores antes de continuar:
//-- validate the sl and tp to a proper double that can be used in the OrderSend() function if(sl == 0) { slPrice = 0.0; } if(tp == 0) { tpPrice = 0.0; }
Ahora, necesitamos realizar una validación más compleja basada en los detalles del símbolo que habíamos guardado anteriormente. Agruparemos la lógica de validación en dos grupos, uno para las posiciones de Compra y otro para las posiciones de Venta. La validación del SL y TP se basará en el precio actual del símbolo, las restricciones del nivel mínimo de stop del símbolo y el spread del símbolo.
Si un precio TP o SL especificado no es válido y se encuentra fuera del rango requerido, se mantendrá el precio TP o SL original y se imprimirá un mensaje explicando por qué falló la modificación. Cuando terminemos de validar los valores SL y TP, imprimiremos otro resumen para registrar los valores confirmados y verificados como referencia:
//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice if(positionType == POSITION_TYPE_BUY) { //-- calculate the new sl and tp prices newTpPrice = 0.0; newSlPrice = 0.0; if(tp > 0) { newTpPrice = entryPrice + (tp * symbolPoint); } if(sl > 0) { newSlPrice = entryPrice - (sl * symbolPoint); } //-- save the new sl and tp prices incase they don't change afte validation below tpPrice = newTpPrice; slPrice = newSlPrice; if( //-- Check if specified TP is valid tp > 0 && ( newTpPrice <= entryPrice + (spread * symbolPoint) || newTpPrice <= positionPriceCurrent || ( newTpPrice - entryPrice < symbolStopLevel * symbolPoint || (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint) ) ) ) { //-- Specified TP price is invalid, don't modify the TP Print( "Specified proposed ", positionSymbol, " TP Price at ", newTpPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!" ); tpPrice = currentPositionTpPrice; } if( //-- Check if specified SL price is valid sl > 0 && ( newSlPrice >= positionPriceCurrent || entryPrice - newSlPrice < symbolStopLevel * symbolPoint || positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint ) ) { //-- Specified SL price is invalid, don't modify the SL Print( "Specified proposed ", positionSymbol, " SL Price at ", newSlPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!" ); slPrice = currentPositionSlPrice; } } if(positionType == POSITION_TYPE_SELL) { //-- calculate the new sl and tp prices newTpPrice = 0.0; newSlPrice = 0.0; if(tp > 0) { newTpPrice = entryPrice - (tp * symbolPoint); } if(sl > 0) { newSlPrice = entryPrice + (sl * symbolPoint); } //-- save the new sl and tp prices incase they don't change afte validation below tpPrice = newTpPrice; slPrice = newSlPrice; if( //-- Check if specified TP price is valid tp > 0 && ( newTpPrice >= entryPrice - (spread * symbolPoint) || newTpPrice >= positionPriceCurrent || ( entryPrice - newTpPrice < symbolStopLevel * symbolPoint || (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint) ) ) ) { //-- Specified TP price is invalid, don't modify the TP Print( "Specified proposed ", positionSymbol, " TP Price at ", newTpPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!" ); tpPrice = currentPositionTpPrice; } if( //-- Check if specified SL price is valid sl > 0 && ( newSlPrice <= positionPriceCurrent || newSlPrice - entryPrice < symbolStopLevel * symbolPoint || newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint ) ) { //-- Specified SL price is invalid, don't modify the SL Print( "Specified proposed ", positionSymbol, " SL Price at ", newSlPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!" ); slPrice = currentPositionSlPrice; } } //-- Print verified position properties before modification positionProperties = "---\r\n"; positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n"; positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n"; positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + " -> New SL: " + (string)slPrice + "\r\n"; positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + " -> New TP: " + (string)tpPrice + "\r\n"; Print(positionProperties);
Ahora que tenemos los valores SL y TP validados, es el momento de enviar una petición al servidor de negociación para modificarlos. Utilizamos la función ZeroMemory() para borrar las estructuras tradeRequest y tradeResult, asegurándonos de que no contienen datos residuales de operaciones anteriores. A continuación, inicializamos la estructura tradeRequest con la siguiente información:
- action: Establecer en TRADE_ACTION_SLTP para indicar la modificación de Stop Loss y Take Profit.
- position: Establecer en positionTicket la posición en la que estamos trabajando.
- symbol: Se establece en positionSymbol para identificar el símbolo de esta posición.
- sl: Establecer en slPrice que contiene el valor de Stop Loss validado.
- tp: Se establece el tpPrice que contiene el valor de Take Profit validado.
A continuación llamamos a la función ResetLastError() para borrar cualquier código de error anterior almacenado internamente. Esto garantiza que obtengamos códigos de error precisos durante el proceso de envío del pedido.
//-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult);
Estamos listos para enviar la orden al servidor de negociación. Sin embargo, la experiencia me ha enseñado que la ejecución de órdenes puede fallar ocasionalmente debido a problemas temporales de la red o a una sobrecarga del servidor. Esto significa que tenemos que encontrar una manera inteligente de manejar el envío de la orden con reintentos. Para resolverlo, utilizaremos un bucle for que itere hasta 101 veces (bucle <= 100). Este mecanismo de reintento ayuda a gestionar posibles errores temporales durante la ejecución de la orden.
Dentro del bucle for, utilizamos OrderSend() para enviar la solicitud de orden contenida en tradeRequest y almacenamos el resultado en tradeResult. Si OrderSend() devuelve verdadero, indica que los precios SL y TP se han modificado correctamente y la solicitud de orden se ha completado sin problemas.
También haremos una confirmación final comprobando el tradeResult.retcode en busca de códigos específicos (10008 o 10009) que indiquen una modificación SL/TP exitosa para esta posición. Si los códigos coinciden, imprimimos un mensaje de confirmación con detalles como el ticket de posición, el símbolo y los códigos de retorno. A continuación, utilizamos return(true) para salir de la función con éxito. La sentencia break sale del bucle sólo para estar completamente seguros de que salimos del bucle for para evitar iteraciones innecesarias. Si OrderSend() devuelve false o el retcode no coincide con códigos de éxito, indica un error.
//-- initialize the parameters to set the sltp tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.sl = slPrice; tradeRequest.tp = tpPrice; ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful { //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________\r\n\r\n"); return(true); //-- exit function break; //--- success - order placed ok. exit for loop } } else //-- Order request failed { //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________\r\n\r\n"); return(false); //-- exit function break; //-- exit for loop } } }
Aquí está la función SetSlTpByTicket() con todos los segmentos de código y su secuencia adecuada. Asegúrese de que su función tiene todos los componentes del código siguiente:
bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export { //-- first check if the EA is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function } //--- Confirm and select the position using the provided positionTicket ResetLastError(); //--- Reset error cache incase of ticket selection errors if(PositionSelectByTicket(positionTicket)) { //---Position selected Print("\r\n_______________________________________________________________________________________"); Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP."); } else { Print("\r\n_______________________________________________________________________________________"); Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError()); return(false); //-- Exit the function } //-- create variables to store the calculated tp and sl prices to send to the trade server double tpPrice = 0.0, slPrice = 0.0; double newTpPrice = 0.0, newSlPrice = 0.0; //--- Position ticket selected, save the position properties string positionSymbol = PositionGetString(POSITION_SYMBOL); double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); double volume = PositionGetDouble(POSITION_VOLUME); double currentPositionSlPrice = PositionGetDouble(POSITION_SL); double currentPositionTpPrice = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //-- Get some information about the positions symbol int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL); double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT); double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT); int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD); //--Save the non-validated tp and sl prices if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices { newSlPrice = entryPrice - (sl * symbolPoint); newTpPrice = entryPrice + (tp * symbolPoint); } else //-- SELL POSITION { newSlPrice = entryPrice + (sl * symbolPoint); newTpPrice = entryPrice - (tp * symbolPoint); } //-- Print position properties before modification string positionProperties = "--> " + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" + " <--\r\n"; positionProperties += "------------------------------------------------------------\r\n"; positionProperties += "Ticket: " + (string)positionTicket + "\r\n"; positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n"; positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n"; positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + " -> New Proposed SL: " + (string)newSlPrice + "\r\n"; positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + " -> New Proposed TP: " + (string)newTpPrice + "\r\n"; positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "---"; Print(positionProperties); //-- validate the sl and tp to a proper double that can be used in the OrderSend() function if(sl == 0) { slPrice = 0.0; } if(tp == 0) { tpPrice = 0.0; } //--- Check if the sl and tp are valid in relation to the current price and set the tpPrice if(positionType == POSITION_TYPE_BUY) { //-- calculate the new sl and tp prices newTpPrice = 0.0; newSlPrice = 0.0; if(tp > 0) { newTpPrice = entryPrice + (tp * symbolPoint); } if(sl > 0) { newSlPrice = entryPrice - (sl * symbolPoint); } //-- save the new sl and tp prices incase they don't change afte validation below tpPrice = newTpPrice; slPrice = newSlPrice; if( //-- Check if specified TP is valid tp > 0 && ( newTpPrice <= entryPrice + (spread * symbolPoint) || newTpPrice <= positionPriceCurrent || ( newTpPrice - entryPrice < symbolStopLevel * symbolPoint || (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint) ) ) ) { //-- Specified TP price is invalid, don't modify the TP Print( "Specified proposed ", positionSymbol, " TP Price at ", newTpPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!" ); tpPrice = currentPositionTpPrice; } if( //-- Check if specified SL price is valid sl > 0 && ( newSlPrice >= positionPriceCurrent || entryPrice - newSlPrice < symbolStopLevel * symbolPoint || positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint ) ) { //-- Specified SL price is invalid, don't modify the SL Print( "Specified proposed ", positionSymbol, " SL Price at ", newSlPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!" ); slPrice = currentPositionSlPrice; } } if(positionType == POSITION_TYPE_SELL) { //-- calculate the new sl and tp prices newTpPrice = 0.0; newSlPrice = 0.0; if(tp > 0) { newTpPrice = entryPrice - (tp * symbolPoint); } if(sl > 0) { newSlPrice = entryPrice + (sl * symbolPoint); } //-- save the new sl and tp prices incase they don't change afte validation below tpPrice = newTpPrice; slPrice = newSlPrice; if( //-- Check if specified TP price is valid tp > 0 && ( newTpPrice >= entryPrice - (spread * symbolPoint) || newTpPrice >= positionPriceCurrent || ( entryPrice - newTpPrice < symbolStopLevel * symbolPoint || (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint) ) ) ) { //-- Specified TP price is invalid, don't modify the TP Print( "Specified proposed ", positionSymbol, " TP Price at ", newTpPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!" ); tpPrice = currentPositionTpPrice; } if( //-- Check if specified SL price is valid sl > 0 && ( newSlPrice <= positionPriceCurrent || newSlPrice - entryPrice < symbolStopLevel * symbolPoint || newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint ) ) { //-- Specified SL price is invalid, don't modify the SL Print( "Specified proposed ", positionSymbol, " SL Price at ", newSlPrice, " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent, "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!" ); slPrice = currentPositionSlPrice; } } //-- Print verified position properties before modification positionProperties = "---\r\n"; positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n"; positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n"; positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + " -> New SL: " + (string)slPrice + "\r\n"; positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + " -> New TP: " + (string)tpPrice + "\r\n"; Print(positionProperties); //-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //-- initialize the parameters to set the sltp tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.sl = slPrice; tradeRequest.tp = tpPrice; ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful { //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________\r\n\r\n"); return(true); //-- exit function break; //--- success - order placed ok. exit for loop } } else //-- Order request failed { //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________\r\n\r\n"); return(false); //-- exit function break; //-- exit for loop } } } return(false); }
Función de cierre de posición
Esta función se llamará ClosePositionByTicket() y utilizará un enfoque estructurado para garantizar que podamos cerrar posiciones de forma efectiva en función de sus números de ticket. Toma como argumento el número de ticket de la posición. Comprobará si la negociación está permitida, seleccionará la posición utilizando el ticket proporcionado, recuperará e imprimirá sus propiedades, preparará una solicitud de negociación e intentará cerrar la posición mientras gestiona cualquier error que se produzca.
En primer lugar, definimos la función y especificamos que devolverá un valor booleano (true o false) y tomará un parámetro como argumento.
bool ClosePositionByTicket(ulong positionTicket) export { //--- Function body }
A continuación comprobaremos si el Asesor Experto tiene permiso para operar.
//-- first check if the EA is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function }
A continuación, restablecemos cualquier error anterior utilizando la función ResetLastError() y luego seleccionamos la posición utilizando el número de ticket proporcionado. Si se selecciona la posición, imprimimos un mensaje confirmando la selección y si falla la selección, imprimimos un mensaje de error y salimos de la función devolviendo false.
//--- Confirm and select the position using the provided positionTicket ResetLastError(); //--- Reset error cache incase of ticket selection errors if(PositionSelectByTicket(positionTicket)) { //---Position selected Print("..........................................................................................."); Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed."); } else { Print("..........................................................................................."); Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError()); return(false); //-- Exit the function }
Una vez seleccionada correctamente la posición, guardamos sus propiedades y las imprimimos.
//--- Position ticket selected, save the position properties string positionSymbol = PositionGetString(POSITION_SYMBOL); double positionVolume = PositionGetDouble(POSITION_VOLUME); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //-- Print position properties before closing it string positionProperties; positionProperties += "-- " + positionSymbol + " " + EnumToString(positionType) + " Details" + " -------------------------------------------------------------\r\n"; positionProperties += "Ticket: " + (string)positionTicket + "\r\n"; positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n"; positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n"; positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n"; positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n"; positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "_______________________________________________________________________________________"; Print(positionProperties);
A continuación, restablecemos los valores de las estructuras de datos tradeRequest y tradeResult utilizando la función ZeroMemory() para borrar cualquier dato anterior que contengan. A continuación, inicializamos los parámetros de solicitud de operación para cerrar la posición estableciendo la acción de operación como TRADE_ACTION_DEAL para indicar una operación de finalización de operación, la entrada de posición, el símbolo, el volumen y la desviación de precio.
//-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //-- initialize the trade reqiest parameters to close the position tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.volume = positionVolume; tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;
Ahora tenemos que determinar el precio de cierre de la posición y el tipo de orden en función de si la posición es de compra o de venta.
//--- Set the price and order type of the position being closed if(positionType == POSITION_TYPE_BUY) { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID); tradeRequest.type = ORDER_TYPE_SELL; } else//--- For sell type positions { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK); tradeRequest.type = ORDER_TYPE_BUY; }
Por último, restablecemos cualquier error anterior utilizando la función ResetLastError() y luego intentamos cerrar la posición enviando la solicitud de operación. Utilizaremos un bucle for para intentar enviar la solicitud de cierre de posición varias veces para garantizar que la posición se cierre incluso con una conexión a Internet débil o cuando se produzcan errores no críticos. Si la orden se envía y ejecuta correctamente (códigos de retorno 10008 o 10009), imprimimos un mensaje de éxito y devolvemos true. Si el pedido falla, llamamos a la función ErrorAdvisor() para gestionar el error. Si la función ErrorAdvisor() indica un error crítico o si el Asesor Experto se detiene, imprimimos un mensaje de error y devolvemos false para significar que el cierre de la posición falló.
ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful { //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { Print(__FUNCTION__, "_________________________________________________________________________"); PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________"); return(true); //-- exit function break; //--- success - order placed ok. exit for loop } } else //-- position closing request failed { //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { Print(__FUNCTION__, "_________________________________________________________________________"); PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________"); return(false); //-- exit function break; //-- exit for loop } } }
Asegúrese de disponer todos los segmentos de código anteriores en la siguiente secuencia. Aquí están todos los segmentos de código de la función ClosePositionByTicket() juntos en su orden apropiado:
bool ClosePositionByTicket(ulong positionTicket) export { //-- first check if the EA is allowed to trade if(!TradingIsAllowed()) { return(false); //--- algo trading is disabled, exit function } //--- Confirm and select the position using the provided positionTicket ResetLastError(); //--- Reset error cache incase of ticket selection errors if(PositionSelectByTicket(positionTicket)) { //---Position selected Print("..........................................................................................."); Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed."); } else { Print("..........................................................................................."); Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError()); return(false); //-- Exit the function } //--- Position ticket selected, save the position properties string positionSymbol = PositionGetString(POSITION_SYMBOL); double positionVolume = PositionGetDouble(POSITION_VOLUME); ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //-- Print position properties before closing it string positionProperties; positionProperties += "-- " + positionSymbol + " " + EnumToString(positionType) + " Details" + " -------------------------------------------------------------\r\n"; positionProperties += "Ticket: " + (string)positionTicket + "\r\n"; positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n"; positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n"; positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n"; positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n"; positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "_______________________________________________________________________________________"; Print(positionProperties); //-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); //-- initialize the trade reqiest parameters to close the position tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.volume = positionVolume; tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2; //--- Set the price and order type of the position being closed if(positionType == POSITION_TYPE_BUY) { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID); tradeRequest.type = ORDER_TYPE_SELL; } else//--- For sell type positions { tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK); tradeRequest.type = ORDER_TYPE_BUY; } ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful { //--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { //-- Confirm order execution if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009) { Print(__FUNCTION__, "_________________________________________________________________________"); PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________"); return(true); //-- exit function break; //--- success - order placed ok. exit for loop } } else //-- position closing request failed { //-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { Print(__FUNCTION__, "_________________________________________________________________________"); PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________"); return(false); //-- exit function break; //-- exit for loop } } } return(false); }
Guarde y compile el archivo de código fuente PositionsManager.mq5 de la biblioteca, y observará que se genera un nuevo archivo de biblioteca PositionsManager.ex5 en la carpeta Libraries\Toolkit\ donde hemos guardado nuestra biblioteca.
Conclusión
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14822





- 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