Cómo cortar un código de AE para una vida más fácil y menos errores.
Introducción
Hay muchos sistemas de trading que se basan en el análisis técnico, ya sean indicadores o dibujos de gráficos, que tienen una propiedad muy importante. Me refiero a la simetría de unos sistemas así en la dirección de trading. Gracias a esta propiedad, las señales de trading y la mecánica de colocar las órdenes de trade en estos sistemas, puede expresarse generalmente como relativa en sus direcciones.
El acercamiento simple que se describe a continuación, permite utilizar de forma efectiva esta propiedad para reducir considerablemente la longitud del código de los Asesores expertos, basado en estos sistemas simétricos. Los Asesores expertos que utilizan este método, utilizan el mismo código para detectar las señales de trading y generar órdenes de trade, tanto para las posiciones largas como para as cortas.
Es común que, al desarrolla un Asesor experto basándose en un sistema simétrico, primero se generen y procesen los códigos de las señales de trading en una dirección, y luego se copie el código y se perfeccione para la otra dirección. En este caso, es muy fácil cometer un error, y es muy difícil detectarlo después. Por lo que, disminuyendo la cantidad de posibles errores en la lógica del Asesor experto es una ventaja extra del método del que hablamos.
1. Invariable integrada del Asesor experto con respecto a la dirección de trade
El concepto que se analiza está basado en la dirección de trade. Esta dirección puede ser tanto larga (comprar señales y órdenes) o corta (vender señales y órdenes). El objetivo es escribir Asesores expertos de manera que sus códigos sean invariables con respecto a la dirección de trade actual. Para evitar las almohadillas en el texto, se llamará código invariable, teniendo en cuenta que es invariable sólo con respecto a la dirección de trade.
Para esto, se introducirá una función o variable cuyo valor siempre mostrará la dirección actual de trade con uno o dos valores posibles.
La representación de esta variable en el código es muy importante. Aunque el tipo bool parece ser el adecuado para este propósito, sería más efectivo utilizar una representación ago diferente: una representación íntegra. La propia dirección del trade se códifica de la siguiente manera;
- dirección de trade larga: +1
- dirección de trade corta: -1
Una ventaja de esta representación, comparada con la representación lógica, es que se puede utilizar de manera efectiva para hacer varios cálculos y comprobaciones en el código del Asesor experto sin las bifurcaciones adicionales que se usan en los métodos adicionales.
2. Ejemplo de cómo cambiar del código convencional al invariable
Vamos a aclarar esta declaración con varios ejemplos. Sin embargo, empecemos considerando un par de funciones auxiliares que se usarán mucho más adelante:
int sign( double v ) { if( v < 0 ) return( -1 ); return( 1 ); } double iif( bool condition, double ifTrue, double ifFalse ) { if( condition ) return( ifTrue ); return( ifFalse ); } string iifStr( bool condition, string ifTrue, string ifFalse ) { if( condition ) return( ifTrue ); return( ifFalse ); } int orderDirection() { return( 1 - 2 * ( OrderType() % 2 ) ); }
El objetivo de la función sign() es obvio: devuelve 1 para os valores no negativos del argumento, y -1 para los negativos.
La función iif() es un equivalente del lenguaje C del operador llamada "condition ? ifTrue : ifFalse" y permite simplificar considerablemente el Asesor experto invariable haciéndolo más compacto y representativo. Coge argumentos del tipo double, por lo que se puede utilizar con valores tanto de este tipo como de tipos int y datetime Para e mismo trabajo con las series, se necesitará una función iifStr() completamente análoga del tipo serie.
La función orderDirection() devuelve la dirección de la orden de trade actual (es decir, la seleccionada por la función OrderSelect()) de acuerdo a los acuerdos sobre cómo representar las direcciones de trade.
Ahora vamos a considerar ejemplos específicos sobre cómo el método invariable con este código de dirección de trade permite la simplificación de los códigos del Asesor experto:
2.1 Ejemplo 1. Transformar parada de realización
Código típico:
if( OrderType() == OP_BUY ) { bool modified = OrderModify( OrderTicket(), OrderOpenPrice(), Bid - Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); int error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); } } else { modified = OrderModify( OrderTicket(), OrderOpenPrice(), Ask + Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); } }
Código invariable:
double closePrice = iif( orderDirection() > 0, Bid, Ask ); bool modified = OrderModify( OrderTicket(), OrderOpenPrice(), closePrice - orderDirection() * Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); int error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); }
Resumiendo:
- conseguimos evitar una bifurcación condicionada severa;
- sólo se utilizó una serie de llamada a la función OrderModify() en lugar de las dos iniciales; y,
- como consecuencia de (2), se acortó el código del procesamiento de errores.
Por favor, tenga en cuenta que se ha conseguido utilizar sólo una llamada para OrderModify() ya que se utiliza la orden de dirección de trade inmediatamente en la expresión aritméteica para el cálculo del nivel de parada. Si se utilizara una representación lógica de la dirección de trade, sería imposible.
Básicamente, un escritor de Asesores expertos con experiencia, podría hacerlo con una sola llamada a OrderModify() utilizando el método conveniente. Sin embargo, en este caso, esto ocurre de forma completamente natural y no necesita ningún paso adicional.
2.2 Ejemplo 2. Transformar la detección de la señal de trade
Como ejemplo, se considerará la detección de las señales de trade en un sistema de dos movimientos promedio:
double slowMA = iMA( Symbol(), Period(), SlowMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); double fastMA = iMA( Symbol(), Period(), FastMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); if( fastMA > slowMA + Threshold * Point ) { // open a long position int ticket = OrderSend( Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open BUY order, error code: " + GetLastError() ); } } else if( fastMA < slowMA - Threshold * Point ) { // open a short position ticket = OrderSend( Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open SELL order, error code: " + GetLastError() ); } }
Ahora haremos el código invariable con respecto a la dirección del trade:
double slowMA = iMA( Symbol(), Period(), SlowMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); double fastMA = iMA( Symbol(), Period(), FastMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); if( MathAbs( fastMA - slowMA ) > Threshold * Point ) { // open a position int tradeDirection = sign( fastMA - slowMA ); int ticket = OrderSend( Symbol(), iif( tradeDirection > 0, OP_BUY, OP_SELL ), Lots, iif( tradeDirection > 0, Ask, Bid ), Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open " + iifStr( tradeDirection > 0, "BUY", "SELL" ) + " order, error code: " + GetLastError() ); } }
Creo que es obvio que el código se ha vuelto más compacto. Y, naturalmente, las dos comprobaciones de errores se conviertieron en una.
A pesar del hecho de que los ejemplos anteriores son muy simples, las ventajas principales de este método deberían ser muy obvias. En algunos casos más complicados, la diferencia entre el método tradicional y este es incluso más evidente. Vamos a asegurarlo en el ejemplo de un Asesor experto estándar, MACD Sample
3. Cómo simplificar MACD Sample
Para no incluir las almohadillas en el artículo, no se considerará el código entero de este Asesor experto. Vamos a ir a las áreas de códigos que cambiarán bajo este concepto.
El código completo de este AE se incluye en set de envío de MetaTrader 4. También está adjunto en el artículo (archivo MACD Sample.mq4) junto a su versión simplificada (MACD Sample.mq4) para su comodidad.
Empecemos con el bloque escrito para la detección de las señales de trade: El código inicial se da a continuación.
// check for long position (BUY) possibility if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious) { ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "macd sample",16384,0,Green); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("BUY order opened : ",OrderOpenPrice()); } else Print("Error opening BUY order : ",GetLastError()); return(0); } // check for short position (SELL) possibility if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious) { ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point, "macd sample",16384,0,Red); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("SELL order opened : ",OrderOpenPrice()); } else Print("Error opening SELL order : ",GetLastError()); return(0); }
Ahora, con los métodos anteriores, escribiremos de nuevo el código de manera que sea la misma tanto para la señal de comprar como para la de vender
int tradeDirection = -sign( MacdCurrent ); // check if we can enter the market if( MacdCurrent * tradeDirection < 0 && ( MacdCurrent - SignalCurrent ) * tradeDirection > 0 && ( MacdPrevious - SignalPrevious ) * tradeDirection < 0 && MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && ( MaCurrent - MaPrevious ) * tradeDirection > 0 ) { int orderType = iif( tradeDirection > 0, OP_BUY, OP_SELL ); string orderTypeName = iifStr( tradeDirection > 0, "BUY", "SELL" ); double openPrice = iif( tradeDirection > 0, Ask, Bid ); color c = iif( tradeDirection > 0, Green, Red ); ticket = OrderSend( Symbol(), orderType, Lots, openPrice, 3 , 0, openPrice + tradeDirection * TakeProfit * Point, "macd sample", 16384, 0, c ); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print( orderTypeName + " order opened : ", OrderOpenPrice() ); } else Print("Error opening " + orderTypeName + " order : ",GetLastError()); return(0); }
Ahora vayamos al bloque responsable de cerrar las posiciones abiertas y de procesar las paradas de realización. Primero estudiemos su versión inicial, como antes:
if(OrderType()==OP_BUY) // long position is opened { // should it be closed? if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*Point)) { OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if(Bid-OrderOpenPrice()>Point*TrailingStop) { if(OrderStopLoss()<Bid-Point*TrailingStop) { OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop, OrderTakeProfit(),0,Green); return(0); } } } } else // go to short position { // should it be closed? if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) { OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if((OrderOpenPrice()-Ask)>(Point*TrailingStop)) { if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0)) { OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop, OrderTakeProfit(),0,Red); return(0); } } } }
Vamos a transformar este código en uno invariable respecto a la dirección del trade:
tradeDirection = orderDirection(); double closePrice = iif( tradeDirection > 0, Bid, Ask ); c = iif( tradeDirection > 0, Green, Red ); // should it be closed? if( MacdCurrent * tradeDirection > 0 && ( MacdCurrent - SignalCurrent ) * tradeDirection < 0 && ( MacdPrevious - SignalPrevious ) * tradeDirection > 0 && MathAbs( MacdCurrent ) > ( MACDCloseLevel * Point ) ) { OrderClose(OrderTicket(),OrderLots(), closePrice, 3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if( ( closePrice - OrderOpenPrice() ) * tradeDirection > Point * TrailingStop ) { if( OrderStopLoss() == 0 || ( OrderStopLoss() - ( closePrice - tradeDirection * Point * TrailingStop ) ) * tradeDirection < 0 ) { OrderModify( OrderTicket(), OrderOpenPrice(), closePrice - tradeDirection * Point * TrailingStop, OrderTakeProfit(), 0, c ); return(0); } } }
Por favor, tenga en cuenta que la versión inicial del AE comprueba la condición OrderStopLoss() == 0 sólo para posiciones que procesen la parada de realización. Esto es necesario para procesar las situaciones en las que no se consiguió establecer el nivel de parada inicial (por ejemplo, porque estuvo muy cerca del precio de mercado).
El hecho de que esta condición no se compruebe en las posiciones largas puede considerarse como un error típico en la escritura de los Asesores expertos tan simétricos utilizando el método copiar y pegar.
Por favor, tenga en cuenta que este error se ha arreglado automáticamente en ambas direcciones de trade en el código mejorado. También hay que destacar que, si el error ocurrió durante la escritura del código invariable, puede ocurrir también en el procesamiento de las posiciones larga y corta. No hace falta decir que esto aumentaría la probabilidad de su detección durante la prueba.
Eso es todo. Si prueba el Asesor experto con la misma configuración en los mismos datos, verá que son completamente equivalentes. Sin embargo, la versión simplificada es mucho más compacta y sostenible.
4. Recomendaciones para la escritura de Asesores expertos simétricos "desde cero"
Hasta ahora, se han considerado las posibilidades de cómo cambiar del código convencional de un Asesor experto a uno invariable. Sin embargo, el desarrollo de un robot de trading utilizando los principios anteriores "desde cero" es incluso más efectiva.
A primera vista no parece fácil, ya que requiere ciertas capacidades y experiencias en la formulación de condiciones y expresiones invariables con respecto a la dirección de trade. Sin embargo, tras adquirir un poco de práctica, la escritura del código de este estilo será fácil, como si se hiciera sola.
Y ahora voy a intentar dar algunas recomendaciones que puedenn ayudar a empezar a utilizar un método más efectivo:
- Al desarrollar una u otra área de código, primero hay que procesar la dirección del trade largo. En la mayoría de los casos será más fácil sintetizar el código invariable, ya que esta dirección de trade se representa con el valor +1 y no hay que hacer mucho al escribir y analizar las relaciones invariables.
- Si empieza a trabajar en la dirección larga, primero intente escribir una condición sin una variable/función que refleje la dirección del trade. Asegúrese de que la expresión es correcta y añada la dirección del trade. Tras adquirir un poco de experiencia, podrá continuar sin hacer esta división en fases.
- No se "cuelgue" en la dirección larga de trade, a veces es más efectivo expresar la condición para la dirección corta.
- Intente evitar la bifurcación condicional y el uso de la función iif() cuando sea posible con cálculos aritméticos.
Como en la última cláusula, añadiré que se pueden dar situaciones en la que no se podrá hacer sin la bifurcación condicional. Sin embargo, debería intentar agrupar estas situaciones y destinarlas a separar las funciones de ayuda de las direcciones de trade que no dependen de un AE específico. Estás funciones, como las anteriores sign(), iif() y orderDirection(), pueden destinarse a una librería especial que utilizará más tarde su Asesor experto.
Para aclararlo todo, vamos a considerar el siguiente problema:
en una orden de trade, el nivel de parada debe estar en el nivel mínimo de la barra anterior para una posición larga y en el nivel máximo de la barra anterior para una posición corta.
Se puede representar de la siguiente manera en el código:
double stopLevel = iif( tradeDirection > 0, Low[ 1 ], High[ 1 ] );
Parece simple, sin embargo, incluso estas construcciones simples se pueden y deben agruparse en funciones pequeñas y simples para usarlas repetidamente.
Evitemos el operador condicional colocándolo en la función de ayuda para propósitos más generales:
double barPeakPrice( int barIndex, int peakDirection ) { return( iif( peakDirection > 0, High[ barIndex ], Low[ barIndex ] ) ); }
Ahora se pueden expresar os cálculos de los niveles de parada de la siguiente manera:
double stopLevel = barPeakPrice( 1, -tradeDirection );
Por favor, no se deje guiar por las primeras impresiones si la diferencia parece ser insignificante. Esta variante tiene muchas ventajas:
- expresa su objetivo de forma explícita y de forma invariable;
- estimula la escritura del código del AE en un estilo adecuado;
- es más fácil de leer y hace que los desarrollos futuros sean más simples.
Esto es sólo un ejemplo. De hecho, muchos elementos estándar del código del Asesor experto se pueden expresar de forma similar. Podrá hacerlo usted mismo. Por lo que recomiendo que analice el código y lo revise de esta manera.
5. ¿Por qué todo esto?
El concepto que se describe arriba tiene, en mi opinión, las siguientes ventajas:
- reducir el código fuente sin perder la funcionalidad y, como consecuencia, disminuir el tiempo de consumo durante el desarrollo y ajuste de los sistemas de trading;
- reducir las cantidades de errores potenciales;
- aumentar la posibilidad de detectar los errores existentes;
- simplificar la futura modificación del Asesor experto (los cambios se aplican automáticamente tanto a las señales y posiciones largas como a las cortas).
Sólo advertí un inconveniente: Este concepto puede provocar algunas pequeñas dificultades en la comprensión y estudio en las primeras fases. Sin embargo, este inconveniente está más que compensado por las ventajas enumeradas anteriormente. Además, es sólo una cuestión de tiempo y de adquirir experiencia: el desarrollo del código invariable se convertirá en algo natural y fácil de hacer.
Conclusión
El método aquí descrito de escritura de los códigos del Asesor experto en MQL4 se basa en el uso y representación efectiva de la noción de dirección de trade. Permite evitar repetir códigos prácticamente idénticos que pueden verse a menudo en los Asesores expertos escritos utilizando el método convencional. La utilización de método descrito reduce bastante el volumen del código fuente con todas las ventajas que esto puede tener.
En el artículo se proporcionan ejemplos para ayudar a los desarrolladores de sistemas de trading más inexpertos, y a los que tienen algo más de experiencia, a elaborar sus códigos si quieren. Este ejemplo se complementa con algunas recomendaciones del autor, que les ayudará a escribir códigos más compactos para que sean invariables con respecto a la dirección de trade.
Los ejemplos que se han considerado, eran más bien simples con el objetivo de una lectura y entendimiento más fácil. Sin embargo, el método descrito se utilizó de forma satisfactoria en la realización de sistemas mucho más complicados que han aplicado estas herramientas de análisis técnicos como líneas de tendencias, canales, la horca de Andrews, las ondas de Elliot, y algún otro método del análisis del mercado.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1491
- 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