Casi un constructor para crear asesores
Índice
- Introducción
- 1. Funcionalidad del asesor después del constructor
- 2. Algoritmo general del constructor
- 3. Añadimos el indicador estándar — trabajando con el archivo 'Indicators Code.mq5'
- 4. Añadiendo un indicador personalizado
- 5. Captando una transacción - el código simplificado We catch the transaction
- 6. Creando un asesor (señales de apertura de posiciones) con ayuda del constructor
- 7. Creando un asesor (señales de colocación de una orden pendiente) con ayuda del constructor
- Conclusión
Introducción
Desde el principio, nuestro objetivo ha sido utilizar la Biblioteca Estándar. Recuerdo mi primera tarea, a saber, implementar la funcionalidad más simple: conectar la clase comercial CTrade y ejecutar el método Buy o Sell. Elegí la biblioteca estándar, por la brevedad y concisión del código obtenido. El código corto a continuación, ejecutado en forma de script, abre una posición BUY con un volumen de 1.0 lote:
//+------------------------------------------------------------------+ //| Open Buy.mq5 | //| Copyright © 2018-2021, Vladimir Karputov | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018-2021, Vladimir Karputov" #property version "1.001" //--- #include <Trade\Trade.mqh> CTrade m_trade; // trading object //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- m_trade.Buy(1.0); // open Buy position, volume 1.0 lot }
Poco a poco, el nivel de exigencia fue aumentando, y me encontré con errores comerciales casi cada vez que escribía un nuevo asesor experto. Así que quería cada vez con más intensidad escribir el código correcto y olvidarme de estos errores para siempre. Y luego salió un artículo muy importante Qué comprobaciones debe superar un robot comercial antes de ser publicado en el Mercado Cuando se publicó este artículo, ya sabía que era necesario disponer de funciones de control fiables para la ejecución de órdenes comerciales. A partir de ese momento, comencé a adquirir gradualmente funciones comprobadas que, con la ayuda del método 'copy->paste', se pueden insertar fácilmente en un asesor y para su posterior uso.
Dado que los indicadores casi siempre se usan en el trabajo de un asesor, comencé a adquirir funciones para crear correctamente el manejador de un indicador
RECUERDE: el estilo de MQL5 implica que el manejador del indicador se cree UNA VEZ y esto se hace, por regla general, en OnInit,
además de las funciones para obtener los datos del indicador. Desde la versión 2.XXX, comencé a mantener dos ramas de desarrollo del constructor: un código de procedimiento regular, y un código en forma de clase (la tarea principal de la clase es la implementación de asesores multidivisa).
Y el constructor seguía desarrollándose, de forma gradual, se le fueron añdiendo las configuraciones más populares:
- Stop Loss y Take Profit,
- Trailing,
- cálculo del lote como porcentaje del riesgo o como lote constante o mínimo,
- control del intervalo temporal dentro del cual se comercia,
- presencia de una sola posición en el mercado,
- reversión de señales comerciales,
- cierre forzado de posiciones con una señal contraria ...
Cada parámetro de entrada implicaba la creación de bloques de código y nuevas funciones.
Para el uso diario, decidí recopilar las funciones más populares y un conjunto completo de parámetros de entrada en el asesor 'Trading engine 3.mq5': de hecho, este es un asesor listo para usar capaz de ocuparse de mucho trabajo de rutina. Queda ya para cada caso específico añadir o quitar funciones, o cambiar la interacción entre bloques de código.
1. Funcionalidad del asesor después del constructor
El asesor creado por el diseñador dispone de multitud de configuraciones que se pueden combinar para crear estrategias únicas. En la versión 4.XXX, se aplican las siguientes reglas:
- se usa el símbolo actual (el símbolo en cuyo gráfico se está ejecutando el asesor)
- Los Take Profit, Stop Loss y Trailings en los parámetros de entrada se establecen en Points. Points: tamaño del punto del instrumento actual en la divisa cotizada, por ejemplo, para la pareja 'EURSD' será 1.00055-1.00045 = 10 puntos.
Siempre podrá ver lo que indica 'points' en el gráfico del símbolo arrastrando la herramienta Retícula:
Fig. 1. Puntos
Bien, aquí tenemos los parámetros de entrada del asesor obtenido después de usar el constructor:
- "Trading settings" — parámetros comerciales
- Working timeframe— marco temporal de trabajo. Puede distinguirse del marco temporal del gráfico en el que se inicia el asesor. Es el marco temporal en el que se crea el indicador (a menos que se especifique explícitamente otro marco temporal en el indicador). También se usa para monitorear el momento en que nace una nueva barra (para los casos en los que necesitamos buscar una señal comercial solo en el momento en que aparece una nueva barra o cuando necesitamos iniciar el trailing solo en el momento en que surge una nueva barra).
- Stop Loss — Stop loss; si establecemos 0, esto indicará la desactivación del parámetro.
- Take Profit — Take profit; si establecemos 0, esto indicará la desactivación del parámetro.
- Trailing on ... — dos opciones para comprobar la posibilidad del Trailing: 'bar #0 (at every tick)' - en cada tick o 'bar #1 (on a new bar)' — solo cuando surge una nueva barra.
- Search signals on ... — dos opciones para realizar la búsqueda de señales comerciales: 'bar #0 (at every tick)' - en cada tick o 'bar #1 (on a new bar)' - solo cuando surge una nueva barra.
- Trailing Stop (min distance from price to Stop Loss) — el Trailing stop es la distancia mínima entre el precio y el Stop loss de la posición. El trailing comienza a funcionar solo si la posición es rentable y el precio se aleja del precio de apertura a la distancia 'Trailing Stop' + 'Trailing Step'. El funcionamiento del trailing se muestra en las imágenes del código TrailingStop.
- Trailing Step — paso del trailing.
- "Position size management (lot calculation)" — cálculo del lote
- Money management lot: Lot OR Risk — selección del sistema de cálculo del lote. El lote puede ser tanto constante (establecemos ' Money management' en 'Constant lot' y el tamaño del lote en ' The value for "Money management"') como dinámico, en porcentaje del riesgo por transacción (establecemos ' Money management' en 'Risk in percent for a deal' e indicamos el porcentaje de riesgo en ' The value for "Money management"'). También podemos indicar un lote constante igual al lote mínimo — estableciendo ' Money management' en 'Lots Min'.
- The value for "Money management" — valor para ' Money management'
- "Trade mode" — modo comercial
- Trade mode: — selección del modo comercial. Puede ser 'Allowed only BUY positions' (solo se pueden abrir posiciones BUY), 'Allowed only SELL positions' (solo se pueden abrir posiciones SELL) y 'Allowed BUY and SELL positions' (se pueden abrir tanto posiciones BUY como posiciones SELL)
- "DEMA" — parámetros del indicador personalizado. En este lugar, usted pegará luego su indicador y sus parámetros
- DEMA: averaging period
- DEMA: horizontal shift
- DEMA: type of price
- "Time control" — segmento temporal de trabajo. Se establece el segmento temporal en el que podemos realizar la búsqueda de señales comerciales
- Use time control — bandera, activada/desactivada Time control
- Start Hour — horas de inicio del periodo
- Start Minute — minutos de inicio del periodo
- End Hour — horas de finalización del periodo
- End Minute — minutos de finalización del periodo
- "Pending Order Parameters" — parámetros relacionados con las órdenes pendientes
- Pending: Expiration, in minutes ('0' -> OFF) — duración de la orden pendiente ('0' indica que el parámetro está desactivado).
- Pending: Indent — distancia de la orden pendiente respecto al precio actual (se usa en el caso de que el precio de la orden pendiente no se establezca explícitamente)
- Pending: Maximum spread ('0' -> OFF) — spread máximo ( '0' indica que el parámetro está desactivado). Si el spread actual es mayor que el establecido, la orden pendiente no se colocará (el asesor esperará a que el spread disminuya)
- Pending: Only one pending — bandera, activada/desactivada, en el mercado solo se permite una orden pendiente
- Pending: Reverse pending type — bandera, activada/desactivada, reversión de la orden pendiente
- Pending: New pending -> delete previous ones — es decir, si se ordena la colocación de una orden pendiente, se eliminarán de forma preliminar todas las demás órdenes pendientes
- "Additional features" — parámetros adicionales
- Positions: Only one — bandera, activada/desactivada, en el mercado se permite tener solo una posición
- Positions: Reverse — bandera, activada/desactivada, reversión de una orden comercial
- Positions: Close opposite — bandera, activada/desactivada, si hay una orden comercial, se cerrarán de forma preliminar todas las posiciones y solo después se ejecutará la orden comercial
- Print log — bandera, activada/desactivada, mostrar información ampliada sobre órdenes y errores
- Coefficient (if Freeze==0 Or StopsLevels==0) — coeficiente que considera el nivel stop
- Deviation — deslizamiento establecido
- Magic number — identificador único del asesor
2. Algoritmo general del constructor
A nivel programático general (en el encabezado del asesor), se declara la matriz 'SPosition' ('SPosition' es el nombre de la matriz), que consta de estructuras 'STRUCT_POSITION'. Al principio, esta matriz tiene un tamaño cero; después de que se haya procesado la señal comercial, la matriz también volverá al tamaño cero.
Desde OnTick, se llama la función 'SearchTradingSignals'; si hay una señal (recibimos la señal si no hay posiciones abiertas en el mercado), esta función genera una orden comercial (crea para cada orden comercial una estructura 'STRUCT_POSITION' en la matriz). También en OnTick, se comprueba la presencia de órdenes comerciales, verificando para ello el tamaño de la matriz 'SPosition: (si este es superior a cero, significará que hay una orden comercial y esta orden comercial se reenviará para su ejecución a 'OpenBuy' o a 'OpenSell'). El control de la ejecución de órdenes comerciales se realiza en OnTradeTransaction:
Fig. 2. Algoritmo general (simple)
Siempre se asume que el asesor trabaja en el símbolo actual, es decir, en el símbolo en cuyo gráfico está instalado el asesor. Ejemplo: el asesor se coloca en el gráfico 'USDPLN', eso significa que el asesor funciona en el símbolo 'USDPLN'.
2.1. Estructura 'STRUCT_POSITION'
Esta estructura es el corazón del asesor, y ejecuta dos papeles al mismo tiempo: en la estructura hay campos en los que se anota la orden comercial (la anotación se realiza en 'SearchTradingSignals'), y campos para controlar la ejecución de la orden comercial (el control se realiza en OnTradeTransaction).
//+------------------------------------------------------------------+ //| Structure Positions | //+------------------------------------------------------------------+ struct STRUCT_POSITION { ENUM_POSITION_TYPE pos_type; // position type double volume; // position volume (if "0.0" -> the lot is "Money management") double lot_coefficient; // lot coefficient bool waiting_transaction; // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction ulong waiting_order_ticket; // waiting order ticket, ticket of the expected order bool transaction_confirmed; // transaction confirmed, "true" -> transaction confirmed //--- Constructor STRUCT_POSITION() { pos_type = WRONG_VALUE; volume = 0.0; lot_coefficient = 0.0; waiting_transaction = false; waiting_order_ticket = 0; transaction_confirmed = false; } };
Unos campos son responsables de la orden comercial, mientras que otros lo son del control de la orden comercial. La estructura contiene un constructor, es decir, una función especial 'STRUCT_POSITION()' que se llama al crear el objeto de estructura e inicializa los elementos de la estructura.
Campos de la orden comercial:
- pos_type — tipo de posición que debemos abrir(puede ser 'POSITION_TYPE_BUY' o 'POSITION_TYPE_SELL')
- volume — volumen de la posición. Si el volumen es '0.0', entonces el volumen de la posición se tomará del grupo de parámetros de entrada 'Position size management (lot calculation)
- lot_coefficient — si este coeficiente es mayor que '0.0', entonces el volumen de la posición se multiplicará por este coeficiente
Campos de control sobre la ejecución de una orden comercial
- waiting_transaction — bandera que indica que la orden comercial se ha ejecutado correctamente y debemos esperar la confirmación
- waiting_order_ticket — número de orden obtenido al ejecutar una orden comercial
- transaction_confirmed — bandera que indica que la ejecución de una orden comercial ha sido confirmada
Después de levantar la bandera en el campo 'transaction_confirmed', la orden comercial es eliminada de la estructura. Por consiguiente, si el asesor no tiene una orden comercial, la estructura tendrá un tamaño igual a cero. Podrá leer más sobre los campos de estructura y el control en el capítulo "Captando una transacción — el código simplificado We catch the transaction".
¿Por qué exactamente este algoritmo?
Podría parecer que basta con comprobar si el método Buy ha retornado 'true' o 'false', y que si es 'true', podemos decidir que la orden comercial ha sido ejecutada. Y en muchas situaciones, este enfoque funcionaría, pero hay momentos en los que el retorno de 'true' no ofrece garantías de obtener un resultado, cosa que, a propósito, se menciona en la documentación sobre los métodos Buy y Sell:
Observación
La finalización satisfactoria del método no siempre significa la finalización satisfactoria de una operación comercial. Debemos verificar el resultado de la ejecución de la solicitud comercial (código de retorno del servidor comercial) llamando al método ResultRetcode(), así como el valor retornado por ResultDeal().
Y la confirmación final y precisa de la ejecución de una transacción comercial puede ser la presencia de una entrada en la historia comercial. Por eso he elegido este algoritmo: si el método se ejecuta con éxito, comprobamos ResultDeal (ticket de la transacción), comprobamos ResultRetcode (código del resultado de la ejecución de la solicitud) y recordamos ResultOrder (ticket de la orden), mientras que el ticket de la orden lo captamos en OnTradeTransaction).
3. Añadimos el indicador estándar — trabajando con el archivo 'Indicators Code.mq5'
Para mayor comodidad, los bloques de código listos para usar (la declaraсión de las variables para almacenar los manejadores, los parámetros de entrada, la creación de identificadores) se recopilan en el asesor 'Indicators Code.mq5'. Los parámetros de entrada de los indicadores y las variables para almacenar los manejadores se encuentran en el encabezado del asesor; la creación de los manejadores, como era de esperar, está escrita en OnInit. Un pequeño matiz: los nombres de las variables para almacenar los manejadores se forman según la siguiente plantilla: 'handle_' + 'indicador', por ejemplo 'handle_iStdDev'. Todo el trabajo con 'Indicators Code.mq5' se reduce a operaciones de 'copiar y pegar'.
RECUERDE: el estilo MQL5 implica que el manejador del indicador se cree UNA VEZ y esto se hace (como norma general) en OnInit
3.1. Ejemplo de adición del indicador 'iRVI' (Relative Vigor Index, RVI)
Vamos a crear el asesor 'Add indicator.mq5'. En el editor MetaEditor, llamamos al 'MQL Wizard', por ejemplo, clicando en el botón , y seleccionamos 'Expert Advisor (template)'
Fig. 3. 'MQL Wizard' -> 'Expert Advisor (plantilla)'
En el siguiente paso, recomiendo encarecidamente añadir al menos un parámetro de entrada
Fig. 4. 'Expert Advisor (plantilla)' -> 'Añadir parámetro'
Esta técnica añade automáticamente al código las líneas para el bloque de parámetros de entrada:
//--- input parameters input int Input1=9;
'MQL Wizard' ha creado un asesor vacío, ahora vamos a añadirle el indicador 'iRVI' (Relative Vigor Index, RVI). En 'Indicators Code.mq5', realizamos la búsqueda 'handle_iRVI' (la búsqueda se llama con 'ctrl' + 'F'). La búsqueda encuentra la variable en la que se almacena el manejador:
Fig. 5. manejador RVI
Copiamos la línea encontrada y la pegamos en el asesor 'Add indicator', en el encabezado:
//--- input parameters input int Input1=9; //--- int handle_iRVI; // variable for storing the handle of the iRVI indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit()
Continuamos la búsqueda y encontramos el bloque de creación del manejador:
Fig. 6. manejador iRVI
Copiamos las líneas encontradas y las pegamos en el asesor 'Add indicator', en OnInit:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iRVI handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period); //--- if the handle is not created if(handle_iRVI==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(Inp_RVI_period), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); }
Ahora, añadimos los parámetros de entrada del indicador. En 'Indicators Code.mq5', clicamos en la ruleta del ratón, por ejemplo, en 'Inp_RVI_period', y saltaremos directamente al bloque de parámetros de entrada:
Fig. 7. manejador iRVI
Copiamos las líneas y las pegamos en los parámetros de entrada:
#property version "1.00" //--- input parameters input group "RVI" input ENUM_TIMEFRAMES Inp_RVI_period = PERIOD_D1; // RVI: timeframe input int Inp_RVI_ma_period = 15; // RVI: averaging period //--- int handle_iRVI; // variable for storing the handle of the iRVI indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+
Si compilamos, obtendremos un error: el compilador se quejará de 'm_symbol' y de 'm_init_error'. Y esto es correcto, ya que estas variables se encuentran en el código que obtenemos después de que el constructor funcione, y el asesor 'Add indicator' ha sido creado exclusivamente para demostrar cómo trabaja con el archivo 'Indicators Code.mq5'.
4. Añadiendo un indicador personalizado
Añadimos el indicador personalizado MA on DeMarker. En primer lugar, se trata de un indicador personalizado; en segundo lugar, este indicador utiliza group. Por analogía con el apartado anterior, creamos el asesor 'Add custom indicator'. Después de ello, deberemos copiar los parámetros de entrada del indicador personalizado y pegarlos en el asesor:
#property version "1.00" //--- input parameters input group "DeMarker" input int Inp_DeM_ma_period = 14; // DeM: averaging period input double Inp_DeM_LevelUP = 0.7; // DeM: Level UP input double Inp_DeM_LevelDOWN = 0.3; // DeM: Level DOWN input group "MA" input int Inp_MA_ma_period = 6; // MA: averaging period input ENUM_MA_METHOD Inp_MA_ma_method = MODE_EMA; // MA: smoothing type //---
Encontramos en el archivo 'Indicators Code.mq5' la variable 'handle_iCustom', la variable para almacenar el manejador del indicador personalizado y la pegamos en el asesor:
//+------------------------------------------------------------------+ //| Add custom indicator.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.00" //--- input parameters input group "DeMarker" input int Inp_DeM_ma_period = 14; // DeM: averaging period input double Inp_DeM_LevelUP = 0.7; // DeM: Level UP input double Inp_DeM_LevelDOWN = 0.3; // DeM: Level DOWN input group "MA" input int Inp_MA_ma_period = 6; // MA: averaging period input ENUM_MA_METHOD Inp_MA_ma_method = MODE_EMA; // MA: smoothing type //--- int handle_iCustom; // variable for storing the handle of the iCustom indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit()
Encontramos en el archivo 'Indicators Code.mq5', en OnInit(), el bloque para crear el indicador personalizado y lo pegamos en el asesor:
int handle_iCustom; // variable for storing the handle of the iCustom indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iCustom handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA", Inp_DEMA_ma_period, Inp_DEMA_ma_shift, Inp_DEMA_applied_price); //--- if the handle is not created if(handle_iCustom==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(Inp_DEMA_period), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function |
Aquí tendremos que esforzarnos un poco. Deberemos escribir el marco temporal ('InpWorkingPeriod'), la ruta al indicador (suponemos que el indicador se encuentra en la carpeta raíz 'Indicators') y los parámetros de entrada:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iCustom handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker", "DeMarker", Inp_DeM_ma_period, Inp_DeM_LevelUP, Inp_DeM_LevelDOWN, "MA", Inp_MA_ma_period, Inp_MA_ma_method); //--- if the handle is not created if(handle_iCustom==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(InpWorkingPeriod), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); }
5. Captando una transacción — el código simplificado We catch the transaction
ATENCIÓN: este asesor es una versión simplificada. Muchas funciones tienen un código más corto que en los constructores completos
Si no hay posiciones abiertas por este asesor en el mercado, anotaremos una orden comercial para abrir una posición BUY. La confirmación de que la posición está abierta se imprime desde OnTradeTransaction y desde OnTick. Búsqueda y anotación de una orden comercial en la función 'SearchTradingSignals':
//+------------------------------------------------------------------+ //| Search trading signals | //+------------------------------------------------------------------+ bool SearchTradingSignals(void) { if(IsPositionExists()) return(true); //--- int size_need_position=ArraySize(SPosition); if(size_need_position>0) return(true); ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_BUY; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY"); return(true); }
Si no hay posiciones abiertas por el asesor en el mercado y si el tamaño de la matriz 'SPosition' es igual a cero, aumentamos el tamaño de la matriz en uno, creando así un objeto de la estructura 'STRUCT_POSITION', que a su vez llamará al constructor 'STRUCT_POSITION()'. Después de llamar al constructor, los elementos de la estructura se inicializan (por ejemplo, volume — el volumen de la posición está en '0.0' — significa que el volumen de la posición se tomará del grupo de parámetros de entrada 'Position size management (lot calculation)'). Queda por escribir en la estructura solo el tipo de orden comercial, en este caso, la orden puede leerse como: "Abrir una posición Buy".
Después de escribir la orden comercial, la matriz 'SPosition' constará de una estructura, y los elementos de esta estructura tendrán los siguientes valores:
Elemento | Valor | Observación |
---|---|---|
pos_type | POSITION_TYPE_BUY | escrito en 'SearchTradingSignals' |
volume | 0.0 | inicializado en el constructor de la estructura |
lot_coefficient | 0.0 | inicializado en el constructor de la estructura |
waiting_transaction | false | inicializado en el constructor de la estructura |
waiting_order_ticket | 0 | inicializado en el constructor de la estructura |
transaction_confirmed | false | inicializado en el constructor de la estructura |
5.1. En el nuevo tick, entramos en OnTick
Principio general de acción en OnTick:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int size_need_position=ArraySize(SPosition); if(size_need_position>0) { for(int i=size_need_position-1; i>=0; i--) { if(SPosition[i].waiting_transaction) { if(!SPosition[i].transaction_confirmed) { if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed); return; } else if(SPosition[i].transaction_confirmed) { ArrayRemove(SPosition,i,1); return; } } if(SPosition[i].pos_type==POSITION_TYPE_BUY) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } if(SPosition[i].pos_type==POSITION_TYPE_SELL) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } } } //--- search for trading signals only at the time of the birth of new bar if(!RefreshRates()) return; //--- search for trading signals if(!SearchTradingSignals()) return; //--- }
Al principio de OnTick, comprobamos el tamaño de la matriz 'SPosition' (es la matriz de estructuras 'STRUCT_POSITION'). Si el tamaño de la matriz es superior a cero, comenzamos el recorrido hacia cero, con dos escenarios disponibles:
- si en la estructura, la bandera 'waiting_transaction' está en 'true' (esto sognificará que se ha ejecutado la preparación de la orden comercial y deberemos esperar la confirmación), comprobamos la bandera 'transaction_confirmed'
- si es 'false', significará que la transacción aún no ha sido confirmada (por ejemplo, esto se puede dar si se emite una orden comercial, un nuevo tick ha llegado, y en OnTradeTransaction aún no hay confirmación). Luego imprimimos un mensaje sobre ello, salimos con return, y aguardamos un nuevo tick con la esperanza de que la información se actualice
- si es 'true', entonces se confirmará la transacción; luego eliminamos esta estructura de la matriz y salimos con return
-
si la estructura tiene la bandera 'waiting_transaction' en 'false' (lo cual significará que esta orden comercial se acaba de registrar y aún no se ha ejecutado), alzaremos la bandera 'waiting_transaction' y reenviaremos la orden comercial en 'OpenPosition'. Tenga en cuenta que el bloque de código podría simplificarse hasta tener la forma
SPosition[i].waiting_transaction=true; OpenPosition(i); return;
pero lo hemos dejado así para que resulte más sencillo comprender la forma completa del constructor de asesores:
if(SPosition[i].pos_type==POSITION_TYPE_BUY) { if(InpCloseOpposite) { if(count_sells>0) { ClosePositions(POSITION_TYPE_SELL); return; } } if(InpOnlyOne) { if(count_buys+count_sells==0) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } else { ArrayRemove(SPosition,i,1); return; } } SPosition[i].waiting_transaction=true; OpenPosition(i); return; } if(SPosition[i].pos_type==POSITION_TYPE_SELL) { if(InpCloseOpposite) { if(count_buys>0) { ClosePositions(POSITION_TYPE_BUY); return; } } if(InpOnlyOne) { if(count_buys+count_sells==0) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } else { ArrayRemove(SPosition,i,1); return; } } SPosition[i].waiting_transaction=true; OpenPosition(i); return; }
Continuamos monitoreando, recordemos el bloque de código:
if(SPosition[i].pos_type==POSITION_TYPE_BUY) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; }
La orden comercial acaba de anotarse en la estructura (en la función 'SearchTradingSignals') y la bandera 'waiting_transaction' está en 'false', esto significa que ponemos la bandera 'waiting_transaction' en 'true' y transmitimos a la función 'OpenPosition' el parámetro 'i', es decir, el número ordinal de la estructura en la matriz. 'OpenPosition'. Como nuestro tipo de orden comercial es 'POSITION_TYPE_BUY', transmitimos el número ordinal de la estructura a la función 'OpenBuy'.
5.2. Función 'OpenBuy'
La tarea de la función es superar las comprobaciones preliminares, enviar una solicitud comercial de apertura de una posición BUY y monitorear su resultado.
La primera comprobación es la verificación de SYMBOL_VOLUME_LIMIT
SYMBOL_VOLUME_LIMIT | El máximo permitido para un símbolo dado en cuanto al volumen total de la posición abiertas y las órdenes pendientes en una dirección (compra o venta). Por ejemplo, si el límite es de 5 lotes, podemos tener una posición de compra abierta con un volumen de 5 lotes y colocar una orden pendiente Sell Limit con un volumen de 5 lotes. Pero, al mismo tiempo, no podemos colocar una orden pendiente Buy Limit (ya que el volumen total en una dirección excederá el límite) o colocar una orden Sell Limit con un volumen de más de 5 lotes. |
Si la verificación falla, eliminamos el elemento de estructura de la matriz 'SPosition'.
La segunda comprobación es el margen
Obtenemos el tamaño del margen libre que quedará depués de la operación comercial (FreeMarginCheck), y el tamaño del margen necesario para una operación comercial (MarginCheck). Después de esto, nos protegemos. Siempre nos cubrimos y dejamos después de la operación una suma como mínimo igual a FreeMarginCheck:
if(free_margin_check>margin_check)
Si no hemos superado la comprobación, entonces imprimimos la variable 'free_margin_check' y eliminamos el elemento de la estructura desde la matriz 'SPosition'.
La tercera comprobación es el resultado bool de la operación Buy
Si el método Buy ha retornado el resultado 'false', entonces imprimimos el error y anotamos en el campo 'waiting_transaction' el valor 'false' (la causa más extendida es el error 10004, requote), de esta forma, en el nuevo tick se realizará un intento de abrir nuevamente una posición BUY. Si el resultado es 'true' (más abajo, mostramos un bloque de código en el que el método Buy ha retornado el resultado 'true')
if(m_trade.ResultDeal()==0) { if(m_trade.ResultRetcode()==10009) // trade order went to the exchange { SPosition[index].waiting_transaction=true; SPosition[index].waiting_order_ticket=m_trade.ResultOrder(); } else { SPosition[index].waiting_transaction=false; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(), ", description of result: ",m_trade.ResultRetcodeDescription()); } if(InpPrintLog) PrintResultTrade(m_trade,m_symbol); } else { if(m_trade.ResultRetcode()==10009) { SPosition[index].waiting_transaction=true; SPosition[index].waiting_order_ticket=m_trade.ResultOrder(); } else { SPosition[index].waiting_transaction=false; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(), ", description of result: ",m_trade.ResultRetcodeDescription()); } if(InpPrintLog) PrintResultTrade(m_trade,m_symbol); }
entonces comprobamos ResultDeal (ticket de la transacción).
Si el ticket de la transacción es igual a cero entonces comprobamos ResultRetcode (código del resultado de ejecución de la solicitud). Hemos obtenido el código de retorno '10009' (por ejemplo, la orden comercial se ha enviado a un sistema comercial externo, por ejemplo, a la Bolsa, y por eso el ticket de la transacción es igual a cero) entonces, esto significa que en el campo 'waiting_transaction' debemos anotar 'true', mientras que en el campo 'waiting_order_ticket' anotamos ResultOrder (ticket de la orden); de lo contrario, (código de retorno distinto a '10009') deberemos anotar en el campo 'waiting_transaction' 'false' e imprimir un mensaje sobre el error.
Si el ticket de la transacción no es igual a cero (por ejemplo, la ejecución se lleva a cabo en este servidor comercial), deberemos realizar verificaciones similares para el código de retorno y escribir de forma similar los valores en los campos 'waiting_transaction' y 'waiting_order_ticket'.
5.3. OnTradeTransaction
Si la orden comercial se ha enviado con éxito, deberemos esperar la confirmación de que la transacción se ha completado y ha sido registrada en la historia comercial. En OnTradeTransaction, trabajamos con la variable 'trans' (estructura del tipo MqlTradeTransaction). En la estructura, nos interesan solo dos campos: 'deal' y 'type':
struct MqlTradeTransaction { ulong deal; // Deal ticket ulong order; // Order ticket string symbol; // Symbol name ENUM_TRADE_TRANSACTION_TYPE type; // Trading transaction type ENUM_ORDER_TYPE order_type; // Order type ENUM_ORDER_STATE order_state; // Order state ENUM_DEAL_TYPE deal_type; // Deal type ENUM_ORDER_TYPE_TIME time_type; // Order type by lifetime datetime time_expiration; // Order expiration time double price; // Price double price_trigger; // Stop Limit order trigger price double price_sl; // Stop Loss level double price_tp; // Take Profit level double volume; // Volume in lots ulong position; // Position ticket ulong position_by; // Opposite position ticket };
En OnTradeTransaction, en cuanto captamos la transacción TRADE_TRANSACTION_DEAL_ADD (adición de una transacción a la historia), realizamos la siguiente comprobación: intentamos seleccionar una transacción en la historia a través de HistoryDealSelect y si no lo logramos, imprimimos el error; si la transacción existe en la historia comercial, iniciamos un ciclo hacia la matriz 'SPosition'. En un ciclo, nos fijamos solo en las estructuras cuyo campo 'waiting_transaction' esté en 'true' y el campo 'waiting_order_ticket' sea igual al ticket de la orden de la transacción que hemos seleccionado. Si detectamos una coincidencia, anotaremos en el campo 'transaction_confirmed' el valor 'true', esto significará que la orden comercial se ha ejecutado y confirmado.
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { //--- get transaction type as enumeration value ENUM_TRADE_TRANSACTION_TYPE type=trans.type; //--- if transaction is result of addition of the transaction in history if(type==TRADE_TRANSACTION_DEAL_ADD) { ResetLastError(); if(HistoryDealSelect(trans.deal)) m_deal.Ticket(trans.deal); else { Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError()); return; } if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic) { if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) { int size_need_position=ArraySize(SPosition); if(size_need_position>0) { for(int i=0; i<size_need_position; i++) { if(SPosition[i].waiting_transaction) if(SPosition[i].waiting_order_ticket==m_deal.Order()) { Print(__FUNCTION__," Transaction confirmed"); SPosition[i].transaction_confirmed=true; break; } } } } } } }
En el nuevo tick, entramos en OnTick. Allí, la estructura que muestra 'true' en el campo 'transaction_confirmed' será eliminada de la matriz 'SPosition'. Por consiguiente, se ha emitido una orden comercial, realizando a continuación un seguimiento de dicha orden hasta que aparezca en la historia comercial.
6. Creando un asesor (señales de apertura de posiciones) con ayuda del constructor
Antes de crear un asesor, deberemos pensar en la estrategia comercial en sí. Vamos a considerar una estrategia simple basada en el indicador DEMA (Double Exponential Moving Average, DEMA). Esta estrategia estará escrita en el constructor por defecto. Buscaremos la señal de apertura de una posición solo en el momento en que surja una nueva barra, mientras que la propia señal comercial será un indicador que aumentará o disminuirá paulatinamente:
Fig. 8. Estrategia DEMA
Recuerde: cualquier estrategia se puede modificar en gran medida ajustando los parámetros. Por ejemplo, podemos dejar el Take Profit y el Stop Loss, pero desactivar el Trailing. O viceversa: desactivar el Take Profit y el Stop Loss, y dejar el Trailing. O bien restringir la dirección en que comerciaremos: permitir solo BUY o solo SELL. Como alternativa, podemos habilitar 'Time control' y restringir el comercio por la noche; o al contrario, configurar el comercio solo por la noche. También podemos cambiar en gran medida el sistema comercial configurando los parámetros del grupo 'Additional features'.
En general, la columna vertebral de una estrategia comercial se construye en la función 'SearchTradingSignals', y todos los demás parámetros se encargan de "sondear" el mercado en busca de enfoques óptimos.
Bien, vamos a crear un nuevo archivo, a saber, una plantilla para un asesor (deberemos seguir los pasos indicados en las figuras 3 y 4). En el paso 4, tendremos que dar un nombre único al asesor, digamos 'iDEMA Full EA.mq5'. Así, obtenemos el siguiente espacio en blanco:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.00" //--- input parameters input int Input1=9; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Ahora, copiamos el código completo del archivo 'Trading engine 3.mq5' y lo pegamos en lugar de las líneas. Debemos editar el encabezado del asesor. Tras pegar el código, obtenemos el siguiente encabezado:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //+------------------------------------------------------------------+ //| Trading engine 3.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "4.003" #property description "barabashkakvn Trading engine 4.003" #property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)" /* barabashkakvn Trading engine 4.003 */ #include <Trade\PositionInfo.mqh>
Le damos al encabezado el siguiente aspecto:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.001" #property description "iDEMA EA" #property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)" /* barabashkakvn Trading engine 4.003 */ #include <Trade\PositionInfo.mqh>
Si realizamos la compilación, no obtendremos un solo error. El asesor obtenido podrá incluso comerciar.
6.1. Función 'SearchTradingSignals'
Esta es la función más importante encargada de verificar la disponibilidad de las órdenes comerciales. Vamos a analizar esta función bloque por bloque.
No más de una posición por barra:
if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal return(true);
Comprobación del intervalo temporal comercial:
if(!TimeControlHourMinute()) return(true);
Recibiendo datos del indicador. Obtenemos los datos del indicador en la matriz 'dema', a la que se le asigna el orden de indexación inverso usando ArraySetAsSeries (el elemento de la matriz [0] se corresponderá con la barra más a la derecha en el gráfico). Los datos los obtendremos con la función personalizada 'iGetArray':
double dema[]; ArraySetAsSeries(dema,true); int start_pos=0,count=6; if(!iGetArray(handle_iCustom,0,start_pos,count,dema)) { return(false); } int size_need_position=ArraySize(SPosition); if(size_need_position>0) return(true);
Señal de apertura de una posición BUY. Si es necesario (la variable 'InpReverse' almacena el valor del parámetro de entrada 'Positions: Reverse'), la señal comercial se invertirá. Si hay una restricción en la dirección de comercio (la variable 'InpTradeMode' almacena el valor del parámetro de entrada 'Trade mode:'), se tendrá en cuenta esta limitación:
//--- BUY Signal if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=sell) { ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_BUY; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY"); return(true); } } else { if(InpTradeMode!=buy) { ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_SELL; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL"); return(true); } } }
El bloque de código de la señal SELL es similar.
7. Creando un asesor (señales de colocación de una orden pendiente) con ayuda del constructor
El nombre del asesor será 'iDEMA Full EA Pending.mq5', para ello, abra el asesor 'iDEMA Full EA.mq5' y guárdelo con el nuevo nombre.
En primer lugar, siempre se desarrolla una estrategia comercial y solo entonces, para esta estrategia, se implementa el código. Vamos a modificar ligeramente la estrategia que usamos en el capítulo 6. Para ello, crearemos un asesor (señales de apertura de posiciones) usando el constructor; en lugar de una señal para abrir una posición BUY, tendremos una señal para colocar una orden pendiente Buy stop, y en lugar de una señal para abrir una posición SELL, tendremos una señal para colocar una orden pendiente Sell stop. Para las órdenes pendientes, se usarán los siguientes parámetros:
- Pending: Expiration, in minutes ('0' -> OFF) — duración de la orden pendiente ('0' indica que el parámetro está desactivado) -> 600
- Pending: Indent — distancia de la orden pendiente respecto al precio actual (se usa en el caso de que el precio de la orden pendiente no se establezca explícitamente) -> 50
- Pending: Maximum spread ('0' -> OFF) — spread máximo ( '0' indica que el parámetro está desactivado). Si el spread actual es mayor que el establecido, la orden pendiente no se colocará (el asesor esperará a que el spread disminuya) -> 12
- Pending: Only one pending — bandera, activada/desactivada, en el mercado solo se permite una orden pendiente -> true
- Pending: Reverse pending type — bandera, activada/desactivada, reversión de la orden pendiente -> false
- Pending: New pending -> delete previous ones — es decir, si se ordena la colocación de una orden pendiente, se eliminarán de forma preliminar todas las demás órdenes pendientes -> true
La función 'SearchTradingSignals' adoptará el aspecto siguiente:
//+------------------------------------------------------------------+ //| Search trading signals | //+------------------------------------------------------------------+ bool SearchTradingSignals(void) { if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal return(true); if(!TimeControlHourMinute()) return(true); double dema[]; ArraySetAsSeries(dema,true); int start_pos=0,count=6; if(!iGetArray(handle_iCustom,0,start_pos,count,dema)) { return(false); } int size_need_pending=ArraySize(SPending); if(size_need_pending>0) return(true); //--- if(InpPendingOnlyOne) if(IsPendingOrdersExists()) return(true); if(InpPendingClosePrevious) m_need_delete_all=true; //--- BUY Signal if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=sell) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP"); return(true); } } else { if(InpTradeMode!=buy) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP"); return(true); } } } //--- SELL Signal if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=buy) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP"); return(true); } } else { if(InpTradeMode!=sell) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP"); return(true); } } } //--- /*if(InpPendingOnlyOne) if(IsPendingOrdersExists()) return(true); if(InpPendingClosePrevious) m_need_delete_all=true; int size_need_pending=ArraySize(SPending); ArrayResize(SPending,size_need_pending+1); if(!InpPendingReverse) SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; else SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; SPending[size_need_pending].indent=m_pending_indent; if(InpPendingExpiration>0) SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60); if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/ //--- return(true); }
Tenga en cuenta que no escribimos el precio de una orden pendiente en la estructura SPending, lo cual significa que se usará el precio actual más la distancia de retraso.
Bien, hemos obtenido una señal comercial, pero se ha activado (se ha colocado una orden pendiente) solo cuando el spread se ha vuelto menor al especificado:
Fig. 9. iDEMA Full EA Pending
Archivos adjuntos al artículo:
Nombre | Tipo de archivo | Descripción |
---|---|---|
Código del indicador | Asesor | Contiene las variables para almacenar los manejadores, los parámetros de entrada de los indicadores y los bloques para crear los mismos |
Add indicator.mq5 | Asesor | Ejemplo de trabajo con el archivo 'Add indicator.mq5' - añadimos un indicador estándar |
Add custom indicator.mq5 | Asesor | Ejemplo de adición de un indicador personalizado |
Trading engine 4.mq5 | Asesor | Constructor |
iDEMA Full EA.mq5 | Asesor | Asesor creado con la ayuda del constructor - señales de apertura de posiciones |
iDEMA Full EA Pending.mq5 | Asesor | Asesor creado con el constructor - señales para colocar órdenes pendientes |
Conclusión
Espero que este conjunto de funciones comerciales lo ayude a crear asesores expertos más fiables, preparados para las cambiantes condiciones comerciales del mercado. Y nunca dude en experimentar con los parámetros, ya que activando y desactivando algunos de ellos puede cambiar tremendamente la estrategia.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9717
- 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