
El prototipo del Robot de trading
Introducción
El ciclo de vida de cualquier sistema de trading se reduce a la apertura y cierre de posiciones. Sin lugar a duda. Pero cuando se trata de la implementación del algoritmo, pues hay tantas opiniones como programadores. Todo el mundo será capaz de resolver el mismo problema a su manera, pero con el mismo resultado final.
A lo largo de los años, se han intentado varios enfoques para la realización de una lógica y estructura del experto a través de la programación. Por el momento, se puede afirmar que se ha establecido una plantilla con una pauta clara que se usa en todos los códigos.
Este enfoque no es universal al 100%, pero puede cambiar tu manera de diseñar la lógica del experto. La cuestión no trata de las oportunidades de trabajar con las ordenes que quieres utilizar con el experto. Todo el sunto está en el principio de creación de un modelo de trading.
1. Principios de diseño de los sistemas de trading y tipos de fuentes de eventos
El enfoque básico para el diseño de un algoritmo, utilizado por la mayoría, es hacer el seguimiento de una posición desde su apertura hasta su cierre. Este es un enfoque lineal. Y si quieres hacer cambios en el código, que a menudo lleva a grandes complicaciones, ya que surge un gran número de condiciones y el código adquiere nuevas ramas de análisis.
La mejor solución para modelar un robot de trading es "servir las condiciones". Y un principio fundamental de este análisis no es cómo surgen estas condiciones del experto y sus posiciones y órdenes, sino lo que debemos hacer con ellas ahora. Este principio básico está cambiando radicalmente la gestión del trading y simplifica el desarrollo de los códigos.
Vamos a abordarlo con más profundidad.
1.1. El principio de "Servir las condiciones"
Como se ha mencionado antes, el experto no necesita saber cómo se ha llevado a cabo el estado actual. Debe saber qué hacer con el ahora según su entorno (valores del parámetro, propiedades de órdenes guardadas, etc.).
Este principio está directamente relacionado con el hecho de que el experto existe sólo de un bucle a otro (en particular de tick a tick), y no le debe importar lo que ocurre con las órdenes anteriores al tick. Por lo tanto, debes utilizar un enfoque orientado a eventos de gestión de órdenes. Es decir, en el tick actual el experto guarda su estado, que será el punto de inicio para la decisión sobre el siguiente tick.
Por ejemplo, debes eliminar todas las órdenes pendientes del experto y sólo entonces puedes continuar analizando los indicadores y colocando nuevas órdenes. La mayoría de los ejemplos de códigos que hemos visto utilizan el bucle "while (true) {intenta eliminar}" o un bucle más suave "while (k < 1000) {intenta eliminar}". Omitiremos la variante de llamada única del comando eliminar sin análisis de errores.
Este método es lineal, se "cuelga" el experto por un tiempo indefinido.
Por lo tanto, sería más correcto no meter el experto en bucles, salvo para almacenar órdenes o eliminar órdenes, de este modo, a cada nuevo tick se comprobará la orden mientras se intenta borrar la orden pendiente. En este caso, mientras el experto lee los parámetros del estado, sabe que en este momento tiene que borrar órdenes. Y tratará de eliminarlas. Si ocurre un error de trading, el experto simplemente bloqueará su análisis y funcionamiento hasta el próximo bucle.
1.2. El segundo principio más importante del diseño, es la máxima abstracción posible desde la dirección de la posición en cuestión (Compra/Venta), la divisa y el gráfico. Todas las funciones del experto se deben implementar de este modo, que está dirección o símbolo sean analizados en casos excepcionales en los que realmente no se puede evitar (por ejemplo, si tenemos en cuenta el crecimiento favorable del precio para la posición abierta, si bien existen distintos opciones para evitar detalles específicos). Intenta siempre evitar dicho diseño de "bajo nivel". Esto reducirá por lo menos a la mitad el código y el procesamiento de escritura de funciones. Y esto los haría "independientes del trading".
La implementación de este principio es reemplazar el análisis explícito de los tipos de órdenes, propiedades de símbolos y parámetros calculados dependientes por funciones macro. Más adelante en el artículo veremos su implementación en detalle.
1.3. Tercer principio – segmentación del algoritmo en unidades lógicas (módulos independientes)
En la práctica, podemos decir que el mejor enfoque es la separación las operaciones del experto en funciones individuales. Creo que estarás de acuerdo en que es difícil escribir todo el algoritmo del experto en una función, y que esto complica el análisis y la edición posterior. Así que no tenemos que hacerlo en MQL5, ya que ahora proporciona un control casi completo de tu entorno.
Por lo tanto, las unidades lógicas (es decir, apertura, seguimiento, cierre de ordenes) se deben implementar por separado la una de la otra con un análisis completo de los parámetros del entorno y de los eventos. Con este enfoque, el diseño del experto se hace flexible. Puedes añadir fácilmente nuevos módulos independientes en el sin tocar los que ya existen, o deshabilitar los módulos existentes sin modificar el código principal.
Las fuentes de eventos para los sistemas expertos son:
1. Indicadores. Un ejemplo es el análisis de las líneas de valores del indicador, sus intersecciones, combinaciones, etc. Los indicadores también pueden ser: el tiempo actual, los datos obtenidos de Internet, etc. En la mayoría de los casos, se utilizan los eventos del indicador para señalar la apertura y cierre de las órdenes. Salvo para ajustarlos (en general Trailing Stop Loss o orden pendiente para el indicador).
Por ejemplo, a la implementación práctica de un indicador se le puede llamar un experto, que analiza la intersección del indicador MA rápido y lento con mayor apertura en el sentido de la intersección.
2. Ordenes existentes, posiciones y su estado. Por ejemplo, la pérdida actual o el volumen del beneficio, la presencia/ausencia de posiciones u órdenes pendientes, el beneficio de la posición cerrada, etc. La implementación práctica de estos eventos es mucho más amplia y más diversa, ya que hay más opciones que las relacionan que con los eventos del indicador.
El ejemplo más sencillo de un experto, basado únicamente en el evento de trading, se rellena para promediar una posición existente y colocar su salida en el beneficio deseado. Es decir, la presencia de pérdidas en una posición disponible sería un evento para colocar una nueva orden de promediado.
O, por ejemplo, Trailing Stop Loss. Esta función comprueba un evento, cuando el precio se mueve en beneficios para un determinado puntos desde el Stop Loss anterior. Como resultado, el experto mueve el Stop Loss detrás del precio.
3. Eventos externos. Aunque esto no suele ocurrir en un sistema puramente experto, pero en general hay que tenerlo en cuenta para tomar decisiones. Esto incluye ajustar las órdenes, posiciones, procesar los errores de trading, procesar los eventos del gráfico (moviendo/creando/borrando objetos, pulsando botones, etc.). En general, estos son eventos, que no se pueden comprobar en el historial y ocurren sólo cuando el experto está ejecutándose.
Un ejemplo destacado de este tipo de expertos son los sistemas de información de trading con un control gráfico del trading.
2. La clase base CExpertAdvisor -constructor experto
¿Cuál es la función de un experto de trading? En el siguiente diagrama se muestra el esquema general de interacciones del programa MQL.
Figura 1. Esquema general de interacciones de los elementos del programa MQL.
Como puedes observar en el esquema, primero está la entrada al bucle de trabajo (esta puede ser un tick o una señal del temporizador). En esta etapa se puede filtrar el tick sin procesarlo en el primer bloque. Esto ocurre cuando no hace falta el experto para funcionar en cada tick, pero sólo en una barra nueva, o cuando el experto está simplemente deshabilitado.
A continuación, el programa entra en el segundo bloque -módulos para trabajar con órdenes y posiciones, y sólo entonces se llama a los bloques de procesamiento de eventos desde los módulos. Cada módulo puede consultar el evento que le interesa sólo.
Se puede llamar a esta secuencia como en el esquema con lógica directa, ya que primero determina QUÉ hará el experto (qué eventos utiliza el módulo de procesamiento), y sólo entonces CÓMO y PORQUÉ lo hará (obtener las señales del evento).
La lógica directa es coherente con nuestra percepción del mundo y de la lógica universal. Después de todo, el ser humano piensa primero en conceptos concretos, los resume, los clasifica e identifica la relación entre los mismos.
En este sentido, el diseño de expertos no es la excepción. En primer lugar, se declara lo que debe hacer un experto (abrir y cerrar posiciones, parada de protección), y sólo entonces se indica en que eventos y cómo debe hacerlo. Pero en cualquier caso, esto no vale a la inversa: se recibe la señal y luego se piensa dónde y cómo procesarla. Esta es la lógica inversa, y es mejor no utilizarla, ya que el resultado obtenido sería un código engorroso con un gran número de ramas de condiciones.
He aquí un ejemplo de lógica directa e inversa. Haz la apertura/cierre mediante la señal RSI.
- En la lógica inversa el experto comienza recibiendo el valor del indicador, y después comprueba la dirección de la señal y qué es lo que tienes que hacer con la posición: abrir la Compra o cerrar la Venta, o al revés, abrir la Venta y cerrar la compra. Es decir, que el punto de entrada sirve para recibir y analizar la señal.
- En la lógica directa es justo lo contrario. El experto tiene dos módulos para abrir y cerrar posiciones, y simplemente comprueba las condiciones para la ejecución de estos módulos. Es decir, después de entrar en el módulo de apertura, el experto recibe el valor del indicador y comprueba si es una señal para abrir una posición. A continuación, después de entrar al módulo de cierre de órdenes, el experto comprueba si se trata de una señal para cerrar una posición. Es decir, no ha ningún punto de entrada, hay módulos que funcionan independientemente del estado de análisis del sistema (el primer principio del diseño).
Ahora, si quieres complicar el experto, será mucho más fácil hacerlo con la segunda opción que con la primera. Bastaría con crear un nuevo módulo de procesamiento de eventos.
En la primera opción hubieras tenido que repasar la estructura del procesamiento de la señal o incrustarla como una función separada.
Para entender mejor este enfoque, se presentan aquí los distintos esquemas de trabajo en el marco de cuatro expertos diferentes.
Figura 2. Ejemplos de implementación de expertos
a). Un experto basado sólo en las señales de algún indicador. Puede abrir y cerrar posiciones cuando cambia la señal. Ejemplo -un experto MA.
b). Un experto con control gráfico del trading.
c). Un experto basado en indicadores, pero dispone además de Trailing Stop Loss y tiempo de funcionamiento. Ejemplo -revender a muy corto plazo con operaciones abiertas mediante el indicador MA.
d). Un experto sin indicadores, con promediado de posiciones. Comprueba los parámetros de la posición sólo cuando se abre una nueva barra. Ejemplo -Experto de promediado.
3. Implementación de la clase Experto
Crear una clase mediante las reglas y requisitos mencionados arriba, y que será base para todos los expertos futuros.
Estas van a ser las funcionalidades mínimas que debe incluir la clase CExpertAdvisor:
1. Inicialización
- Registrar los indicadores
- Establecer los valores iniciales de los parámetros
- Ajustar al símbolo requerido y el período de tiempo
2. Funciones para la obtención de las señales
- Tiempo de funcionamiento permitido (intervalos de trading)
- Definir la señal para abrir/cerrar posiciones u órdenes
- Definir el filtro (tendencia, tiempo, etc.)
- Temporizador de Iniciar/Detener
3. Funciones de servicio
- Calcular el precio de apertura, los niveles de Stop Loss y Take Profit, el volumen de la orden
- Enviar peticiones de trading (abrir, cerrar, modificar)
4. Módulos de trading
- Procesar las señales y los filtros
- Controlar las posiciones y las órdenes
- Trabajar en las funciones del experto: OnTrade(), OnTimer(), OnTester(), OnChartEvent().
5. Desinicialización
- Mensajes de salida, informes
- Borrar el gráfico, descargar los indicadores
Se dividen todas las funciones de la clase en tres grupos. A continuación, se presenta el esquema general de las funciones anidadas y sus descripciones.
Figura 3. Esquema de las funciones anidadas de un experto
1. Funciones macro
Este pequeño grupo de funciones es la base para trabajar con los tipos de órdenes, los parámetros de símbolos y los valores de precios para colocar órdenes (apertura y cierre). Estas funciones macro proporcionan el segundo principio del diseño -la abstracción. Trabajan en el contexto del símbolo utilizado por el experto.
Las funciones macro de conversión de tipos trabajan en función de la evolución del mercado -compra o venta. Por lo tanto, para no crear tus propias constantes, es mejor que utilices las que ya existen - ORDER_TYPE_BUY y ORDER_TYPE_SELL. Estos son algunos ejemplos del uso de macros y sus resultados.
//--- Macro de conversión de tipo long BaseType(long dir); // devuelve el tipo base de la orden para la dirección indicada long ReversType(long dir); // devuelve el tipo inverso de la orden para la dirección indicada long StopType(long dir); // devuelve el tipo detener la orden para la dirección indicada long LimitType(long dir); // devuelve el tipo límite de la orden para la dirección indicada //--- Macro de normalización double BasePrice(long dir); // devuelve el precio Bid/Ask para la dirección indicada double ReversPrice(long dir); // devuelve el precio Bid/Ask para la dirección inversa long dir,newdir; dir=ORDER_TYPE_BUY; newdir=ReversType(dir); // newdir=ORDER_TYPE_SELL newdir=StopType(dir); // newdir=ORDER_TYPE_BUY_STOP newdir=LimitType(dir); // newdir=ORDER_TYPE_BUY_LIMIT newdir=BaseType(newdir); // newdir=ORDER_TYPE_BUY double price; price=BasePrice(dir); // price=Ask price=ReversPrice(dir); // price=Bid
En el desarrollo de los expertos la macro te permite no indicar la dirección procesada y te ayuda a crear un código más compacto.
2. Funciones de servicio
Se diseñan estas funciones para trabajar con órdenes y posiciones. Son de bajo nivel, igual que las funciones macro. Por comodidad, se pueden dividir en dos categorías: funciones de información y funciones de ejecución. Todas realizan un sólo tipo de acción, sin analizar ningún evento. Llevan a cabo órdenes desde controladores expertos de alto nivel.
Ejemplos de funciones de información: encontrar el precio máximo del precio de apertura de las órdenes pendientes actuales, averiguar cómo se ha cerrado una posición -con beneficio o pérdida; obtener el número y la lista de los tickets de las órdenes de los expertos, etc.
Ejemplos de funciones de ejecución: cerrar las órdenes indicadas; modificación del Stop Loss en la posición indicada, etc.
Este grupo es el más amplio. Este es el tipo de funcionalidad, en el que se basa todo el trabajo del experto. Se pueden encontrar numerosos ejemplos de estas funciones en el foro https://www.mql5.com/ru/forum/107476. Pero aparte de esto, la librería estándar de MQL5 ya contiene clases que asumen parte del trabajo de colocar órdenes y posiciones, sobre todo -la clase CTrade.
Pero cualquier tarea tuya requerirá la creación de nuevas implementaciones o ligeras modificaciones a las que ya existen.
3. Módulos de procesamiento de eventos
Este es un grupo de funciones con una superestructura de un nivel más alto que los dos primeros grupos. Como se ha mencionado antes, estos son bloques ya hechos, utilizados en la implementación de tu experto. En general, están incluidos en las funciones de procesamiento de eventos del programa MQL: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). Este grupo no es muy amplio, y se pueden ajustar los contenidos de estos módulos de una tarea a otra. Pero fundamentalmente, nada ha cambiado.
En estos módulos, todo debe ser abstracto (el segundo principio del diseño) para que se pueda llamar al módulo tanto para comprar como para vender. Esto se consigue, por supuesto, mediante macros.
Procede entonces con la implementación
1. Inicialización, Desinicialización
class CExpertAdvisor { protected: bool m_bInit; // flag de inicialización correcta ulong m_magic; // número mágico del experto string m_smb; // símbolo sobre el cual trabaja el experto ENUM_TIMEFRAMES m_tf; // período de tiempo de funcionamiento CSymbolInfo m_smbinf; // parámetros del símbolo int m_timer; // tiempo para el temporizador public: double m_pnt; // considerar cotizaciones con 5/3 dígitos los stops CTrade m_trade; // objeto para la ejecución de las órdenes de trading string m_inf; // cadena de comentario para la información sobre el funcionamiento del experto
Este es el ajuste de parámetros mínimo para que puedan trabajar las funciones del experto.
Los parámetros m_smb y m_tf están puestos en las propiedades del experto especialmente para decirle al mismo con qué divisa y que período trabajar. Por ejemplo, si pones m_smb = "USDJPY", el experto trabajará con este símbolo, sin importar con qué símbolo se ejecutó. Si pones tf = PERIOD_H1, entonces todas las señales y análisis de indicadores tendrán lugar en el gráfico H1.
Además, existen los métodos de clase. Los tres primeros métodos son la inicialización y desinicialización de un experto.
public: //---Inicialización void CExpertAdvisor(); // constructor void ~CExpertAdvisor(); // destructor virtual bool Init(long magic,string smb,ENUM_TIMEFRAMES tf); // inicialización
El constructor y el destructor de la clase base no hacen nada.
El método Init() realiza la inicialización de los parámetros el experto mediante el símbolo, el período de tiempo y el número mágico.
//------------------------------------------------------------------ CExpertAdvisor void CExpertAdvisor::CExpertAdvisor() { m_bInit=false; } //------------------------------------------------------------------ ~CExpertAdvisor void CExpertAdvisor::~CExpertAdvisor() { } //------------------------------------------------------------------ Init bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf) { m_magic=magic; m_smb=smb; m_tf=tf; // definir los parámetros de inicialización m_smbinf.Name(m_smb); // inicializar el símbolo m_pnt=m_smbinf.Point(); // calcular el multiplicador para cotización de 5/3 dígitos if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10; m_trade.SetExpertMagicNumber(m_magic); // definir el número mágico para el experto m_bInit=true; return(true); // trading permitido }
2. Funciones para la obtención de las señales
Estas funciones analizan el mercado y los indicadores.
bool CheckNewBar(); // comprobar si hay una barra nueva bool CheckTime(datetime start,datetime end); // comprobar el tiempo de trading permitido virtual long CheckSignal(bool bEntry); // comprobar la señal virtual bool CheckFilter(long dir); // comprobar el filtro de la dirección
Las dos primeras funciones tienen una implementación bastante específica y se pueden utilizar en otras clases heredadas de esta misma clase.
//------------------------------------------------------------------ CheckNewBar bool CExpertAdvisor::CheckNewBar() // función para comprobar si hay una barra nueva { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) // copiar la barra { Print("CopyRates of ",m_smb," failed, no history"); return(false); } if(rt[1].tick_volume>1) return(false); // check volume return(true); } //--------------------------------------------------------------- CheckTime bool CExpertAdvisor::CheckTime(datetime start,datetime end) { datetime dt=TimeCurrent(); // tiempo actual if(start<end) if(dt>=start && dt<end) return(true); // comprobar si estamos en el rango if(start>=end) if(dt>=start|| dt<end) return(true); return(false); }
Las dos últimas dependen siempre de esos indicadores que estás utilizando. Es sencillamente imposible configurar todas estas funciones para todos los casos.
Lo fundamental -es importante entender que las funciones de las señales CheckSignal() y CheckFilter() pueden analizar absolutamente todos los indicadores y sus combinaciones! Es decir, los módulos de trading en los cuales se incluyen estás señales posteriormente, son independientes.
Esto te permite utilizar el experto una vez escrito como plantilla para otros expertos que funcionan con el mismo principio. Sólo tienes que modificar los indicadores analizados y añadir nuevas condiciones de filtrado.
3. Las funciones de servicio
Como ya se ha mencionado, este grupo de funciones es el más amplio. Para los casos prácticos descritos en este artículo, bastaría con implementar estas cuatro funciones:
double CountLotByRisk(int dist,double risk,double lot); // calcular el lote en función del riesgo ulong DealOpen(long dir,double lot,int SL,int TP); // ejecutar la transacción con los parámetros indicados ulong GetDealByOrder(ulong order); // obtener el ticket de transacción mediante el ticket de orden double CountProfitByDeal(ulong ticket); // calcular el beneficio mediante el ticket de transacción
//------------------------------------------------------------------CountLotByRisk double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calcular el lote en función del riesgo { if(dist==0 || risk==0) return(lot); m_smbinf.Refresh(); return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue()))); } //------------------------------------------------------------------ DealOpen ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP) { double op,sl,tp,apr,StopLvl; // determinar los parámetros del precio m_smbinf.RefreshRates(); m_smbinf.Refresh(); StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // recordar el nivel de stop apr = ReversPrice(dir); op = BasePrice(dir); // precio de apertura sl = NormalSL(dir, op, apr, SL, StopLvl); // stop loss tp = NormalTP(dir, op, apr, TP, StopLvl); // take profit // posición abierta m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp); ulong order = m_trade.ResultOrder(); if(order<=0) return(0); // ticket de orden return(GetDealByOrder(order)); // devolver el ticket de transacción } //------------------------------------------------------------------ GetDealByOrder ulong CExpertAdvisor::GetDealByOrder(ulong order) // obtener el ticket de transacción mediante el ticket de orden { PositionSelect(m_smb); HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER)); uint total=HistoryDealsTotal(); for(uint i=0; i<total; i++) { ulong deal=HistoryDealGetTicket(i); if(order==HistoryDealGetInteger(deal,DEAL_ORDER)) return(deal); // recordar el ticket de transacción } return(0); } //------------------------------------------------------------------ CountProfit double CExpertAdvisor::CountProfitByDeal(ulong ticket) // ganancia de la posición mediante ticket de transacción { CDealInfo deal; deal.Ticket(ticket); // ticket de transacción HistorySelect(deal.Time(),TimeCurrent()); // seleccionar todas las transacciones después de esta uint total = HistoryDealsTotal(); long pos_id = deal.PositionId(); // obtener el identificador de la transacción double prof = 0; for(uint i=0; i<total; i++) // buscar todas las transacciones con este identificador { ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue; prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // resumir el beneficio } return(prof); // devolver el beneficio }
4. Módulos de trading
Por último, este grupo de funciones enlaza todo el proceso de trading, procesando los eventos mediante las funciones de servicio y las macros. Hay pocas unidades lógicas de operaciones de trading, dependen de los objetivos que hayas definido. Sin embargo, podemos distinguir los conceptos comunes, que existen en todos los expertos.
virtual bool Main(); // módulo principal de control del proceso de trading virtual void OpenPosition(long dir); // módulo de apertura de posiciones virtual void CheckPosition(long dir); // comprobar posiciones y abrir otras virtual void ClosePosition(long dir); // cerrar posiciones virtual void BEPosition(long dir,int BE); // mover el Stop Loss al punto muerto virtual void TrailingPosition(long dir,int TS); // Trailing de posiciones Stop Loss virtual void OpenPending(long dir); // módulo de apertura de órdenes pendientes virtual void CheckPending(long dir); // trabajar con las órdenes actuales y abrir otras virtual void TrailingPending(long dir); // mover las órdenes pendientes virtual void DeletePending(long dir); // borrar las órdenes pendientes
En los siguientes ejemplos, consideraremos unas implementaciones concretas de estas funciones.
Añadir nuevas funciones no va a resultar difícil, ya que hemos elegido el enfoque correcto y elaborado la estructura del experto. Si vas a utilizar exactamente este esquema, tus diseños van a requerir unos esfuerzos y tiempo mínimo, y se podrá leer el código incluso después de un año.
Por supuesto, tus expertos no se limitan a ellas. En la clase CExpertAdvisor hemos añadido sólo los métodos más necesarios. Puedes añadir nuevos controladores a las clases heredadas, modificar las existentes, ampliar tus propios módulos, creando así una única librería. Disponer de tal librería, el desarrollo de expertos "llave en mano" llevaría de media hora a dos días.
4. Ejemplos de uso de la clase CExpertAdvisor
4.1. Ejemplo de funcionamiento en función de las señales del indicadorComo primer ejemplo, vamos a empezar con la tarea más sencilla -considera el Expert Advisor MovingAverage (ejemplo básico de MetaTrader 5) mediante la clase CExpertAdvisor. Vamos a complicarlo un poco.
Algoritmo:
a) Condición para la apertura de una posición
- Si el precio cruza el MA de abajo hacia arriba, entonces abre la posición para Comprar.
- Si el precio cruza el MA de arriba hacia abajo, entonces abre la posición para Vender.
- Establecer los valores de SL (Stop Loss), TP (TakeProfit).
- Se calcula el lote de la posición mediante el parámetro Risk -cuánto perderá el depósito cuando se activa el Stop Loss.
b) Condición para el cierre de una posición
- Si el precio cruza el MA de abajo hacia arriba, entonces cierra la posición para Vender.
- Si el precio cruza el MA de arriba hacia abajo, entonces cierra la posición para Comprar.
c) Limitación
- Poner límites al tiempo de funcionamiento de un experto diariamente, desde HourStart hasta HourEnd.
- El experto realiza operaciones de trading sólo con la aparición de una nueva barra.
d) Asistencia de posición
- Utilizar simplemente un Trailing Stop a una distancia de TS.
Para nuestro experto, vamos a necesitar siete funciones de la clase CExpertAdvisor:
- Función de señal -CheckSignal()
- Filtro de ticks -CheckNewBar()
- Filtro de tiempo -CheckTime()
- Función de servicio para abrir posiciones -DealOpen()
- Tres módulos de trabajo -OpenPosition(), ClosePosition(), TrailingPosition()
La función CheckSignal() y los módulos se deben definir en una clase heredada para resolver sus tareas concretas. También hay que añadir la inicialización del indicador.
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" input double Risk = 0.1; // Riesgo input int SL = 100; // Distancia Stop Loss input int TP = 100; // Distancia Take Profit input int TS = 30; // Distancia Trailing Stop input int pMA = 12; // Período Moving Average input int HourStart = 7; // Hora de inicio del trading input int HourEnd = 20; // Hora de finalización del trading //--- class CMyEA : public CExpertAdvisor { protected: double m_risk; // volumen del riesgo int m_sl; // Stop Loss int m_tp; // Take Profit int m_ts; // Trailing Stop int m_pMA; // período MA int m_hourStart; // Hora de inicio del trading int m_hourEnd; // Hora de finalización del trading int m_hma; // indicador MA public: void CMyEA(); void ~CMyEA(); virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // inicialización virtual bool Main(); // función principal virtual void OpenPosition(long dir); // abrir posición con la señal virtual void ClosePosition(long dir); // cerrar posición con la señal virtual long CheckSignal(bool bEntry); // comprobar la señal }; //------------------------------------------------------------------ CMyEA void CMyEA::CMyEA() { } //----------------------------------------------------------------- ~CMyEA void CMyEA::~CMyEA() { IndicatorRelease(m_hma); // eliminar el indicador MA } //------------------------------------------------------------------ Init bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // inicializar la clase principal m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA; // copiar los parámetros m_hourStart=HourStart; m_hourEnd=HourEnd; m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // crear un indicador MA if(m_hma==INVALID_HANDLE) return(false); // salir en caso de error m_bInit=true; return(true); // trading permitido } //------------------------------------------------------------------ Main bool CMyEA::Main() // función principal { if(!CExpertAdvisor::Main()) return(false); // llamar a la función de la clase principal if(Bars(m_smb,m_tf)<=m_pMA) return(false); // si hay un número insuficiente de barras if(!CheckNewBar()) return(true); // buscar barras nuevas // comprobar cada dirección long dir; dir=ORDER_TYPE_BUY; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); dir=ORDER_TYPE_SELL; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); return(true); } //------------------------------------------------------------------ OpenPos void CMyEA::OpenPosition(long dir) { if(PositionSelect(m_smb)) return; // si hay una orden, entonces salir if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) return; if(dir!=CheckSignal(true)) return; // si no hay ninguna señal para la dirección actual double lot=CountLotByRisk(m_sl,m_risk,0); if(lot<=0) return; // salir si no está definido el lote DealOpen(dir,lot,m_sl,m_tp); // abrir posición } //------------------------------------------------------------------ ClosePos void CMyEA::ClosePosition(long dir) { if(!PositionSelect(m_smb)) return; // salir si no hay ninguna posición if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) { m_trade.PositionClose(m_smb); return; } // cerrar las órdenes si no es el intervalo de tiempo del trading if(dir!=PositionGetInteger(POSITION_TYPE)) return; // si no se puede comprobar la dirección de la posición if(dir!=CheckSignal(false)) return; // si la señal de cierre no coincide con la posición actual m_trade.PositionClose(m_smb,1); // cerrar posición } //------------------------------------------------------------------ CheckSignal long CMyEA::CheckSignal(bool bEntry) { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) { Print("CopyRates ",m_smb," no está cargado el historial"); return(WRONG_VALUE); } double ma[1]; if(CopyBuffer(m_hma,0,0,1,ma)!=1) { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); } if(rt[0].open<ma[0] && rt[0].close>ma[0]) return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condición para comprar if(rt[0].open>ma[0] && rt[0].close<ma[0]) return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condición para vender return(WRONG_VALUE); // si no hay ninguna señal } CMyEA ea; // instancia de clase //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // inicializar el experto return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.Main(); // procesar el tick entrante }
Vamos a analizar la estructura de la función Main(). Se suele dividir en dos partes.
En la primera parte se llama a la función principal. Esta función procesa los posibles parámetros que afectan el funcionamiento de un experto de manera global. Estas incluyen la comprobación de la asignación de permiso al experto para hacer trading y la validación de los datos en el historial.
En la segunda parte se procesan los eventos del mercado directamente.
Se prueba el filtro CheckNewBar() -comprobar si hay una barra nueva. Y se llama a los módulos para las dos direcciones del trading uno tras otro.
Todo está organizado de manera bastante abstracta en los módulos (segundo principio del diseño). No hay un acceso directo a las propiedades del símbolo. Y tres módulos -OpenPosition(), ClosePosition() y TrailingPosition() -dependen únicamente de estos parámetros que les llegan desde el exterior. Esto te servirá para llamar a estos módulos para comprobar las órdenes, tanto de Compra como de Venta.
4.2. Ejemplo de uso de CExpertAdvisor -Experto sin indicadores, analizando el estado de la posición y el resultado
Para este ejemplo, vamos a tomar el sistema que hace trading en la posición opuesta con mucho incremento después de la pérdida (a este tipo de expertos se le suele llamar "Martingale").
a) Colocar una orden inicial
- cuando el experto inicia la compra, abre la primera posición para Comprar con un lote inicial.
b) Abrir posiciones posteriores
- Si se cierra la posición anterior con ganancia, se abre una posición en la misma dirección con el primer lote inicial.
- Si se cierra la posición anterior con pérdida, entonces se abre una posición en la dirección opuesta con un lote superior (mediante un factor).
Para nuestro experto, vamos a necesitar tres funciones de la clase CExpertAdvisor:
- Abrir posición -DealOpen().
- Obtener el valor de la ganancia de la posición cerrada mediante el ticket de transacción -CountProfitByDeal().
- Módulos de trabajo -OpenPosition(), CheckPosition().
Puesto que el experto analiza sólo los resultados de las transacciones, pero ningún indicador, usaremos los eventos OnTrade() para un máximo rendimiento. Es decir, el experto que ha colocado la primera orden inicial para la Compra, colocará todas las órdenes posteriores, pero sólo después de cerrar esta posición. Así que vamos a colocar la primera orden en OnTick() y el trabajo posterior en OnTrade().
Como siempre, la función Init() sólo inicializa los parámetros de la clase con los parámetros externos del experto.
El módulo OpenPosition() abre la posición inicial y se bloquea mediante el flag m_first.
El módulo CheckPosition() controla los demás cambios de posición.
Se llama a estos módulos desde las respectivas funciones del experto: OnTick() y OnTrade().
//+------------------------------------------------------------------+ //| eMarti.mq5 | //| Copyright Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" #include <Trade\DealInfo.mqh> input double Lots = 0.1; // Lote input double LotKoef = 2; // multiplicador de lote para pérdidas input int Dist = 60; // distancia al Stop Loss y Take Profit //--- class CMartiEA : public CExpertAdvisor { protected: double m_lots; // Lot double m_lotkoef; // multiplicador de lote para pérdidas int m_dist; // distancia al Stop Loss y Take Profit CDealInfo m_deal; // última transacción bool m_first; // flag para la apertura de la primera posición public: void CMartiEA() { } void ~CMartiEA() { } virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // inicialización virtual void OpenPosition(); virtual void CheckPosition(); }; //------------------------------------------------------------------ Init bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // inicializar la clase principal m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist; // copiar los parámetros m_deal.Ticket(0); m_first=true; m_bInit=true; return(true); // trading permitido } //------------------------------------------------------------------ OnTrade void CMartiEA::OpenPosition() { if(!CExpertAdvisor::Main()) return; // llamar a la función principal if(!m_first) return; // si la posición inicial ya está abierta ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // abrir posición inicial if(deal>0) { m_deal.Ticket(deal); m_first=false; } // si existe la posición } //------------------------------------------------------------------ OnTrade void CMartiEA::CheckPosition() { if(!CExpertAdvisor::Main()) return; // llamar a la función principal if(m_first) return; // si no está colocada la posición inicial if(PositionSelect(m_smb)) return; // si la posición existe // comprobar la ganancia de la posición anterior double lot=m_lots; // lote inicial long dir=m_deal.Type(); // dirección anterior if(CountProfitByDeal(m_deal.Ticket())<0) // si han habido pérdidas { lot=NormalLot(m_lotkoef*m_deal.Volume()); // aumentar el lote dir=ReversType(m_deal.Type()); // posición opuesta } ulong deal=DealOpen(dir,lot,m_dist,m_dist); // abrir posición if(deal>0) m_deal.Ticket(deal); // recordar el ticket } CMartiEA ea; // instancia de clase //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // inicializar el experto return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------OnTick void OnTick() { ea.OpenPosition(); // procesar el tick - abrir la primera orden } //------------------------------------------------------------------ OnTrade void OnTrade() { ea.CheckPosition(); // procesar el evento de trading }
5. Trabajar con eventos
En este artículo has visto ejemplos de procesamiento de dos eventos - NewTick y Trade representados con las funciones OnTick() y OnTrade() respectivamente. En la mayoría de los casos, se usan constantemente estos dos eventos.
Hay cuatro funciones de procesamiento de eventos para los expertos:
-
OnChartEvent procesa un amplio grupo de eventos: cuando se trabaja con objetos gráficos, teclado, ratón y eventos personalizados. Por ejemplo, se utiliza la función para crear expertos interactivos o expertos basados en el principio de la administración gráfica de las órdenes. O simplemente para crear crear controles activos e los parámetros del programa MQL (mediante botones y campos de edición). En general, se usa esta función para procesar los eventos externos de un experto.
-
OnTimer, se le llama cuando se procesa un evento del temporizador del sistema. Se utiliza los siguientes casos: cuando el programa MQL necesita analizar su entorno de manera regular, para calcular los valores de los indicadores, cuando hace falta para referirse continuamente a las fuentes externas de señales, etc. En términos generales, la función OnTimer() es una alternativa ,incluso el mejor sustituto de:
while(true) { /* llevar a cabo el análisis */; Sleep(1000); }.
Es decir, el experto no tiene que trabajar en un bucle infinito en su inicio, pero lo suficiente para llamar a sus funciones desde OnTick() hasta OnTimer(). - OnBookEvent procesa un evento generado cuando cambia el estado de la Profundidad del mercado. Este evento puede proceder del exterior y llevar a cabo su procesamiento según la tarea.
- OnTester, se le llama después de probar el experto en determinado intervalo de fechas, antes de la función OnDeinit() para una posible revisión de las pruebas generadas, al utilizar la optimización genética mediante el parámetro Custom max.
No olvides que es siempre aconsejable utilizar los eventos o las combinaciones de los mismos para solucionar tareas concretas.
Epílogo
Como puedes ver, la escritura de un experto, cuando se dispone del esquema adecuado, no requiere mucho tiempo. Debido a las nuevas posibilidades de procesamiento de eventos en MQL5, disponemos de una estructura más flexible de administración de procesos de trading. Pero todo esto se convierte en una herramienta muy potente si preparas tus algoritmos de trading de manera correcta.
Este artículo describe los tres principios fundamentales para su creación -acontecibilidad, abstracción, modularidad. Vas a llevar a cabo tu trading de manera más fácil, si construyes tus expertos basándote en estos "tres pilares".
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/132





- 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