Desarrollamos un Asesor Experto multiplataforma para colocar StopLoss y TakeProfit de acuerdo con nuestros riesgos

Roman Klymenko | 19 agosto, 2019

Introducción

Como seguramente puede saber, a la hora de tradear, se recomienda seguir las reglas de la Gestión de capital, es decir, no debe entrar en la transacción en la que puede perder más de N% de su depósito.

Usted mismo elige el volumen de N. Pero para cumplir esta regla, hay que calcular correctamente el lote con el que tenemos que entrar en la transacción.

Los que aconsejan hacer así en sus clases magistrales, normalmente muestran un archivo Excel que ya incluye las fórmulas necesarias para calcular el lote para cada símbolo. Ellos «simplemente» necesitan insertar el tamaño de su Stop Loss para obtener el volumen del lote necesario.

¿Pero será eso tan «simplemente»? Esta operación puede demorar un minuto o más de su tiempo. Y cuando Usted finalmente decida definir el tamaño del lote, el precio puede ir más lejos del punto de entrada predefinido. Y eso sin mencionar el trabajo extra que Usted hace. Y aún peor, el trabajo manual siempre aumenta la probabilidad de cometer un error.

Por esa razón, vamos a intentar simplificar este proceso. Para eso, crearemos un EA que permitirá definir el precio de apertura y Stop Loss de manera visual. Basándose en estos parámetros y en el nivel del riesgo, será seleccionado el tamaño del lote necesario y se abrirá una posición en dirección necesaria.

Definición de las metas

Pues bien, hemos aclarado la primera tarea.

Otra tarea que va a tener nuestro EA consiste en definir el precio de Take Profit basándose en sus preferencias respecto a la proporción Take Profit/Stop Loss.

Gerchik y otros traders de éxito recomiendan colocar siempre un Take Profit que sea por lo menos 3 veces mayor que su Stop Loss. Es decir, si coloca un Stop Loss de 40 puntos, Take Profit tiene que ser de 120 puntos como mínimo. Si hay pocas probabilidades para que el precio alcance este nivel, es mejor no entrar en el mercado.

Desde el punto de vista del cálculo de la estadística, siempre es mejor usar la misma proporción entre Stop Loss y Take Profit. Por ejemplo, siempre entrar en las transacciones con una proporción de 3 a 1, 4 a 1, etc. Usted mismo decide la proporción a escoger, basándose en los resultados de su negociación.

Pero hay otra cosa, ¿Cómo podemos colocar el nivel de TP sin gastar preciosos minutos de nuestro tiempo para eso?  ¿Usar de nuevo Excel? ¿O esperar a que el método «al ojo» sea bastante conveniente?

Para no pensar en eso, vamos a colocar esta posibilidad en los ajustes del EA Tendremos un parámetro especial que permitirá definir la proporción de SL/TP. Por ejemplo, el valor 4 de este parámetro significará que la proporción tiene que ser de 4 a 1. En este caso, el EA va a colocar automáticamente el nivel de TP 4 veces mayor que el tamaño de SL seleccionado por Usted.

Formato del funcionamiento del Asesor Experto. El último problema que tenemos que resolver antes de empezar el desarrollo es determinar cómo va a funcionar nuestro EA. En realidad, probablemente eso sea la tarea más difícil.

El EA parecido al que ahora vamos a crear yo llevo usando más de un año. Desde su nacimiento, él sufrió unas modificaciones globales.

Inicialmente, al ejecutar el EA, se mostraba una ventana de diálogo que permitía modificar todos los ajustes de la apertura de nueva posición. Estos ajustes se guardaban automáticamente y se usaban durante las siguientes ejecuciones del EA. Esta era la principal ventaja de este método. Es que, normalmente, se definen sólo una vez (según las necesidades) y no se modifican más.

Sin embargo, la ventana de diálogo con configuraciones tiene una gran desventaja: ocupa prácticamente toda la ventana del gráfico. Por eso, el movimiento del precio detrás prácticamente no se nota. Como no usamos esta ventana de diálogo después de la primera vez, ella tapa el gráfico y no supone ningunas ventajas en absoluto.

Por ejemplo, es la ventana del gráfico con la ventana de diálogo de 640 píxeles del ancho:

Versión del EA con la ventana de diálogo

Fig. 1. Versión del EA con la ventana de diálogo

Como se puede observar, la ventana ni siquiera cabe en la pantalla.

Para resolver este problema, con el tiempo, aparecieron dos versiones de este EA.

En la primera versión, este problema fue resuelto escondiendo la ventana de configuraciones por defecto, y mostrándola al pulsar el botón Configuraciones. Todavía se puede comprar este EA en el Market para MetaTrader 5.

La segunda versión del EA se arregla sin la ventana de diálogo. Todos los ajustes del EA se definen a través de los parámetros de entrada. Eso nos libera de la necesidad de usar la ventana de diálogo, pero al mismo tiempo nos condena a una de las siguientes torturas:

Como todos nosotros nos consideramos programadores, espero que elige la tercera opción. Es que ahora vamos a crear precisamente un clon de la segunda versión del EA. Será aproximadamente así:

Versión del EA sin la ventana de diálogo

Fig. 2. Versión del EA sin la ventana de diálogo

Parámetro de entrada

Para comprender mejor todo el frente de trabajo, primero vamos a echar un vistazo a los parámetros de entrada de nuestro EA:

Parámetros de entrada del EA

Fig. 3. Parámetros de entrada del EA

Toda nuestra negociación será construida a partir de SL, tal como los gurús del trading recomiendan. Por esa razón, en primer lugar, preste atención en dos primeros parámetros: "Tipo de Stop Loss" y "Tamaño de Stop Loss en $ o %".

Por defecto, SL se define en dólares, en lo que indica el parámetro"Tipo de Stop Loss". Además, se puede definir SL en por cientos de su balance. Si tenemos seleccionado SL en por cientos, el valor definido no puede superar 5% del depósito. Eso se hace para evitar errores a la hora de definir los parámetros del EA.

El parámetro "Tamaño de Stop Loss en $ o %" determina el importe que Usted acepta perder cuando SL ocurre.

Tamaño de Stop Loss en centavos (teclas 7 y 8). Además, existe el parámetro — "Tamaño de Stop Loss en centavos (teclas 7 y 8)" para definir SL.

Luego, en el EA, vamos a implementar un conjunto de teclas que permiten definir SL del tamaño necesario con un clic. Por ejemplo, si Usted siempre usa SL de 7 centavos, basta con pulsar la tecla 7 en el teclado para definir SL a esta distancia del precio actual. Pero hablaremos de eso más tarde.

Hay que entender que este parámetro no determina el importe que perderá con SL, sino la distancia del precio de apertura hasta el precio en el que se dispara SL.

No entrar en la transacción si con el lote mínimo el riesgo es mayor que el especificado Puesto que el tamaño del lote con el que necesita entrar en la transacción se calcula automáticamente a base del parámetro Tamaño de Stop Loss en $ o %, puede surgir una situación cuando con el lote mínimo posible de su bróker, el riesgo en la transacción será mayor que ha sido establecido por Usted.

En este caso, puede entrar en la transacción con el lote mínimo ignorando su riesgo, o cancelar la apertura de la transacción. Este parámetro se usa precisamente para elegir el comportamiento deseado.

Por defecto, el EA no le permitirá entrar en la transacción si el riesgo será superado.

Cancelar orden limitada dentro de, horas. Además de la entrada por mercado, el EA permite crear órdenes limitadas a partir del precio especificado por Usted. Además, Usted puede usar este parámetro para limitar el tiempo de vida de la orden limitada.

El tiempo de vida se establece en horas. Por ejemplo, si el tiempo de vida es de 2 horas y dentro de 2 horas desde el momento de colocación de la orden limitada, ella no ha disparado y no se ha convertido en una posición abierta, esta orden limitada se elimina.

Multiplicador para Take Profit. Si Usted negocia con una determinada proporción de SL/TP, este parámetro le permite configurar la colocación automática de TP de acuerdo con su regla.

Por defecto, el valor de este parámetro es igual a 4. Es decir, TP va a colocarse en tal precio para que el beneficio sea igual al tamaño de sus 4 pérdidas cuando se dispara TP.

Parámetros principales. Además, Usted puede:

Función de apertura de la posición

Puesto que escribimos un EA multiplataforma, él debe trabajar en MetaTrader 4 y MetaTrader 5. Sin embargo, la funcionalidad de la apertura de la posición de estas versiones de los EAs es diferente. Para que nuestro código funcione en ambas versiones de la plataforma, vamos a usar una compilación condicional.

Ya comentaba más de una vez en mis artículos de qué se trata. Por ejemplo, en el artículo Creando un EA multiplataforma.

En suma, el código de la compilación condicional es el siguiente:

#ifdef __MQL5__ 
   //código en MQL5
#else 
   //código en MQL4
#endif 

A lo largo del artículo, usaremos las posibilidades de la compilación condicional sólo 3 veces, dos de las cuales están relacionadas con la función de la apertura de la posición. El resto del código va a trabajar de la misma forma tanto en MetaTrader 4, como en MetaTrader 5.

Además, ya desarrollamos la función de apertura de la posición en el artículo Creando un EA multiplataforma. Por eso, simplemente vamos a cogerla de ahí. Lo único es que vamos a modificarla un poco para que ella permita establecer el tiempo de vida de las órdenes limitadas que se abren:

// posibles tipos de las órdenes para la función de apertura de las posiciones
enum TypeOfPos
  {
   MY_BUY,
   MY_SELL,
   MY_BUYSTOP,
   MY_BUYLIMIT,
   MY_SELLSTOP,
   MY_SELLLIMIT,
   MY_BUYSLTP,
   MY_SELLSLTP,
  }; 

//Selección del tipo de ejecución para MT5
#ifdef __MQL5__ 
   enum TypeOfFilling //Tipo de ejecución de la transacción
     {
      FOK,//ORDER_FILLING_FOK
      RETURN,// ORDER_FILLING_RETURN
      IOC,//ORDER_FILLING_IOC
     }; 
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; //Modo de ejecución de la orden
#endif 


/*
función para abrir una posición o colocar una orden limitada
*/
bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym="", datetime expiration=0){
      if( !StringLen(sym) ){
         sym=_Symbol;
      }
      int curDigits=(int) SymbolInfoInteger(sym, SYMBOL_DIGITS);
      if(sl>0){
         sl=NormalizeDouble(sl,curDigits);
      }
      if(tp>0){
         tp=NormalizeDouble(tp,curDigits);
      }
      if(price>0){
         price=NormalizeDouble(price,curDigits);
      }else{
         #ifdef __MQL5__ 
         #else
            MqlTick latest_price;
            SymbolInfoTick(sym,latest_price);
            if( mytype == MY_SELL ){
               price=latest_price.ask;
            }else if( mytype == MY_BUY ){
               price=latest_price.bid;
            }
         #endif 
      }
   #ifdef __MQL5__ 
      ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL;
      ENUM_ORDER_TYPE type=ORDER_TYPE_BUY;
      switch(mytype){
         case MY_BUY:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSLTP:
            action=TRADE_ACTION_SLTP;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_STOP;
            break;
         case MY_BUYLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_LIMIT;
            break;
         case MY_SELL:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSLTP:
            action=TRADE_ACTION_SLTP;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_STOP;
            break;
         case MY_SELLLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_LIMIT;
            break;
      }
      
      MqlTradeRequest mrequest;
      MqlTradeResult mresult;
      ZeroMemory(mrequest);
      
      mrequest.action = action;
      mrequest.sl = sl;
      mrequest.tp = tp;
      mrequest.symbol = sym;
      if(expiration>0){
         mrequest.type_time = ORDER_TIME_SPECIFIED_DAY;
         mrequest.expiration = expiration;
      }
      if(position>0){
         mrequest.position = position;
      }
      if(StringLen(comment)){
         mrequest.comment=comment;
      }
      if(action!=TRADE_ACTION_SLTP){
         if(price>0){
            mrequest.price = price;
         }
         if(volume>0){
            mrequest.volume = volume;
         }
         mrequest.type = type;
         mrequest.magic = EA_Magic;
         switch(useORDER_FILLING_RETURN){
            case FOK:
               mrequest.type_filling = ORDER_FILLING_FOK;
               break;
            case RETURN:
               mrequest.type_filling = ORDER_FILLING_RETURN;
               break;
            case IOC:
               mrequest.type_filling = ORDER_FILLING_IOC;
               break;
         }
         mrequest.deviation=100;
      }
      if(OrderSend(mrequest,mresult)){
         if(mresult.retcode==10009 || mresult.retcode==10008){
            if(action!=TRADE_ACTION_SLTP){
               switch(type){
                  case ORDER_TYPE_BUY:
//                     Alert("Order Buy #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
                  case ORDER_TYPE_SELL:
//                     Alert("Order Sell #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
               }
            }else{
//               Alert("Order Modify SL #:",mresult.order," sl",sl," tp",tp," !!");
            }
            return true;
         }else{
            msgErr(GetLastError(), mresult.retcode);
         }
      }
   #else 
      int type=OP_BUY;
      switch(mytype){
         case MY_BUY:
            type=OP_BUY;
            break;
         case MY_BUYSTOP:
            type=OP_BUYSTOP;
            break;
         case MY_BUYLIMIT:
            type=OP_BUYLIMIT;
            break;
         case MY_SELL:
            type=OP_SELL;
            break;
         case MY_SELLSTOP:
            type=OP_SELLSTOP;
            break;
         case MY_SELLLIMIT:
            type=OP_SELLLIMIT;
            break;
      }
      
      if(OrderSend(sym, type, volume, price, 100, sl, tp, comment, EA_Magic, expiration)<0){
            msgErr(GetLastError());
      }else{
         switch(type){
            case OP_BUY:
               Alert("Order Buy sl",sl," tp",tp," p",price," !!");
               break;
            case OP_SELL:
               Alert("Order Sell sl",sl," tp",tp," p",price," !!");
               break;
            }
            return true;
      }
   
   #endif 
   return false;
}

En MetaTrader 5, cuando se abre una posición, es necesario seleccionar su tipo. Por eso, para MetaTrader 5 añadimos otro parámetro de entrada: "Modo de ejecución de la orden".

Diferentes brókers soportan diferentes tipos de la ejecución de las órdenes. Tal vez, el tipo de ejecución ORDER_FILLING_FOK sea el más popular entre los brókers. Por eso, fue elegido por defecto. No obstante, si su bróker no soporta este tipo, Usted puede seleccionar fácilmente el que se soporta.

Localización del Asesor Experto

Otro mecanismo cogido del artículo Creando un EA multiplataforma es la posibilidad de localizar los mensajes de texto del EA. Por eso, no vamos a considerarlo de nuevo. Por favor, lea el artículo mencionado, si le interesa el formato de su funcionamiento.

Programamos la interfaz del EA

En este artículo, no vamos a considerar el desarrollo del EA desde cero. Además, se supone que Usted ya está familiarizado con el lenguaje MQL por lo menos a nivel básico.

En este artículo, vamos a considerar la implementación de las partes principales del EA. Eso le ayudará si quiere usarlo o, tal vez, mejorarlo implementando una funcionalidad adicional.

Empezamos con la interfaz de nuestro EA.

Al ser iniciado el EA, se crearán los siguientes elementos de la interfaz:

De esta manera, para crear una orden con parámetros necesarios (definidos con la ayuda de los parámetros del EA), hay que desplazar la línea roja al precio con el alcance del cual ocurre SL.

Si colocamos la línea roja por encima del precio actual, se abre una posición Short. Si la línea roja se encuentra por debajo del precio actual, se abre una posición Long.

El volumen de la posición a abrir será calculado automáticamente de tal manera que, cuando SL ocurre, Usted pierda el importe lo más próximo posible al que ha sido indicado en los ajustes del EA. Así que le queda sólo pulsar el botón de la apertura de la posición. Como resultado, se abrirá una posición por el precio de mercado.

Si quiere entrar en el mercado usando una orden limitada, tiene que pulsar adicionalmente el botón "Mostrar línea del precio de apertura (0)", y desplazar la línea verde al precio necesario para abrir una orden limitada. La dirección y el tipo de la orden limitada se define por el EA automáticamente a base de la posición de la línea de SL y la línea de TP (SL por encima de precio de apertura o por debajo).

En principio, puede no pulsar el botón "Mostrar línea del precio de apertura (0)". Después de que mueva la línea roja de SL al precio necesario, este botón se pulsará automáticamente y aparecerá la línea verde del precio de apertura. Si la mueve, se abrirá una orden limitada. Si no la toca, se abrirá una posición por el mercado.

Pues bien, hemos estudiado el principio del funcionamiento. Ahora podemos comenzar con la programación.

Trabajo con el comentario para el gráfico. Para el trabajo con el comentario para el gráfico, se usa la función estándar Comment. Por eso, nos queda sólo preparar la línea que la función Comment mostrará en el gráfico. Para este propósito, creamos nuestra propia función getmespread:

/*
  Mostramos la información sobre el spread y la hora del cierre de la sesión en el comentario para el gráfico
*/
void getmespread(){
   string msg="";
   
   //obtenemos el spread en la moneda del instrumento
   curSpread=lastme.ask-lastme.bid;
   
   // si el mercado no está cerrado, mostramos la información sobre el spread
   if( !isClosed ){
      if(curSpread>0){
         StringAdd(msg, langs.Label1_spread+": "+(string) DoubleToString(curSpread, (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))+" "+currencyS+" ("+DoubleToString(curSpread/curPoint, 0)+langs.lbl_point+")");
         StringAdd(msg, "; "+DoubleToString(((curSpread)/lastme.bid)*100, 3)+"%");
      }else{
         StringAdd(msg, langs.Label1_spread+": "+langs.lblNo);
      }
      StringAdd(msg, "; ");
   }
   
   // mostramos la hora del cierre del mercado si no hemos conseguido determinarla antes
   if(StringLen(time_info)){
      StringAdd(msg, "   "+time_info);
   }
      
   Comment(msg);
}

Vamos a llamar a la función getmespread al inicializar el EA (OnInit), y en cada tick nuevo (OnTick).

En la función getmespread, usamos cinco variables globales de nuestro EA: lastme, isClosed, time_info, currencyS, curPoint.

La información sobre Ask y Bid del último precio recibido se guarda en la variable lastme. Su contenido se actualiza en las funciones OnInit y OnTick con el comando:

SymbolInfoTick(_Symbol,lastme);

Las demás variables se inicializan enla función OnInit. isClosed y time_info se inicializan de la siguiente manera:

  isClosed=false;
  // obtenemos fecha actual
  TimeToStruct(TimeCurrent(), curDay);
  // obtenemos el horario de la negociación con el instrumento para hoy
  if(SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){
      time_info="";
      TimeToStruct(dto, curEndTime);
      TimeToStruct(dfrom, curStartTime);
         
         isEndTime=true;
         string tmpmsg="";
         tmp_val=curEndTime.hour;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val+":");
         tmp_val=curEndTime.min;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val);
         if(curEndTime.hour==curDay.hour){
            if(tmp_val>curDay.min){
            }else{
               isClosed=true;
            }
         }else{
            if(curEndTime.hour==0){
            }else{
               if( curEndTime.hour>1 && (curDay.hour>curEndTime.hour || curDay.hour==0)){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isClosed=true;
               }else if(curDay.hour<curStartTime.hour ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }else if(curDay.hour==curStartTime.hour && curDay.min<curStartTime.min ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }
            }
         }

         if(isEndTime){
            StringAdd(time_info, langs.lblshow_TIME+": "+tmpmsg+time_info);
         }else{
            StringAdd(time_info, langs.lblshow_TIME2+": "+tmpmsg+time_info);
         }
  }

En la variable currencyS, vamos a almacenar el símbolo de la divisa que se usa para calcular el beneficio del instrumento actual. Utilice el siguiente comando para obtenerlo:

currencyS=SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);

El tamaño del punto del instrumento será almacenado en la variable curPoint:

curPoint=SymbolInfoDouble(_Symbol, SYMBOL_POINT);

Línea de SL. Una vez iniciado el EA, vemos sólo una línea, es decir, la línea roja para colocar SL.

Vamos a mostrar esta línea en la función OnInit, igual como los botones. Pero antes de diseñar la línea, hay que verificar si ella ya existe en el gráfico. Si ella existe, no es necesario crear una línea nueva ni los demás elementos de la interfaz. En vez de eso, en las variables globales, colocamos el precio en el cual se encuentra esta línea, así como el precio en el cual se encuentra la línea para abrir la transacción, si esta línea existe en el gráfico:

  // si en el gráfico hay líneas de SL y del precio de apertura, entonces
  // colocamos en las variables los precios en los cuales ellas se encuentran
  if(ObjectFind(0, exprefix+"_stop")>=0){
      draw_stop=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
      if(ObjectFind(0, exprefix+"_open")>=0){
         draw_open=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
      }
  // de lo contrario, creamos la interfaz del EA
  }else{
      draw_open=lastme.bid;
      draw_stop=draw_open-(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)*curPoint);
      ObjectCreate(0, exprefix+"_stop", OBJ_HLINE, 0, 0, draw_stop);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_ANCHOR,ANCHOR_TOP);

      // otros elementos de la interfaz
  }

¿Puede surgir una situación cuando el gráfico ya contiene los elementos de la interfaz del EA inicializado?

Si Usted no ha implementado el código que, al cerrar el EA, va a eliminar todos los elementos de la interfaz que han sido creados, entonces, eso no sólo puede ocurrir, sino pasará obligatoriamente. Pero incluso si este código ha sido implementado, igualmente puede surgir un error en el trabajo del EA que provocará su cierre, mientras que los elementos de la interfaz creados por él permanecerán en el gráfico. Por esa razón, antes de crear algún elemento gráfico, siempre es mejor comprobar si este elemento ya existe en el gráfico.

Bien, la línea roja está creada. Es más, la hemos hecho seleccionada por defecto. Por eso, no es necesario hacer doble clic en ella para activarla. Basta con moverla al precio necesario. No obstante, si la desplaza ahora, no pasará nada. Es que el código que va a ejecutar algunas acciones después de que la línea sea desplazada todavía no está implementado.

Cualquier interacción con los elementos de la interfaz del gráfico se realiza en la función estándar OnChartEvent. El desplazamiento de los elementos de la interfaz genera un evento con ID CHARTEVENT_OBJECT_DRAG. De esta manera, para ejecutar algo después de desplazar la línea en el gráfico, es necesario interceptar este evento en la función OnChartEvent, verificar el elemento con con el nombre accionado, y si es nuestro elemento, podemos ejecutar el código que necesitamos:

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 
   switch(id){
      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }
         break;
   }
}

Después de mover la línea roja, se inicia la función setstopbyline, que «recuerda» el nivel de SL para la futura orden:

/*
«recuerda» el nivel de SL para la futura orden
*/
void setstopbyline(){
   // obtenemos el precio en el que se encuentra la línea de SL
   double curprice=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
   // si el precio se diferencia del precio en el que la línea de SL ha sido colocada durante la inicialización del EA, entonces
   if(  curprice>0 && curprice != draw_stop ){
      double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      if( tmp_double>0 && tmp_double!=1 ){
         if(tmp_double<1){
            resval=DoubleToString(curprice/tmp_double, 8);
            if( StringFind(resval, ".00000000")>0 ){}else{
               curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
            }
         }else{
            if( MathMod(curprice,tmp_double) ){
               curprice= MathFloor(curprice/tmp_double)*tmp_double;
            }
         }
      }
      draw_stop=STOPLOSS_PRICE=curprice;
                  
      updatebuttontext();
      ChartRedraw(0);
   }
}

Aparte de la función setstopbyline, el desplazamiento de la línea roja causa la visualización de la línea de apertura (función showOpenLine) y provoca el cambio del estado del botón "Mostrar línea del precio de apertura (0)".

Botón y línea del precio de apertura. El botón "Mostrar línea del precio de apertura (0)" también se crea cuando el EA se inicializa.

      if(ObjectFind(0, exprefix+"_openbtn")<0){
         ObjectCreate(0, exprefix+"_openbtn", OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XDISTANCE,0); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YDISTANCE,33); 
         ObjectSetString(0,exprefix+"_openbtn",OBJPROP_TEXT, langs.btnShowOpenLine); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XSIZE,333); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_FONTSIZE, 8);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YSIZE,25); 
      }

Como ya ha sido mencionado antes, cualquier interacción con los elementos de la interfaz se procesa dentro de la función estándar OnChartEvent, incluyendo la pulsación de los botones. El evento con ID CHARTEVENT_OBJECT_CLICK se encarga de eso. Sólo tenemos que interceptarlo verificando la fuente del evento, y ejecutar acciones necesarias. Para eso, añadimos case adicional al operador switch de la función OnChartEvent:

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

La función updateOpenLine, que se invoca al pulsar el botón "Mostrar línea del precio de apertura (0)", es un pequeño envoltorio para llamar a la función principal showOpenLine. Esta, en su vez, simplemente muestra el precio de apertura en el gráfico:

void showOpenLine(){
   if(ObjectFind(0, exprefix+"_open")<0){
      draw_open=lastme.bid;
      ObjectCreate(0, exprefix+"_open", OBJ_HLINE, 0, 0, draw_open);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_ANCHOR,ANCHOR_TOP); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_COLOR,clrGreen);
   }
}

Sólo nos queda rescribir el manipulador de eventos CHARTEVENT_OBJECT_DRAG para que éste reaccione al movimiento tanto de la línea de SL, como de la línea del precio de apertura:

      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }else if(sparam==exprefix+"_open"){
               curprice=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
               if( curprice>0 && curprice != draw_open ){
                  double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                  if( tmp_double>0 && tmp_double!=1 ){
                     if(tmp_double<1){
                        resval=DoubleToString(curprice/tmp_double, 8);
                        if( StringFind(resval, ".00000000")>0 ){}else{
                           curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
                        }
                     }else{
                        if( MathMod(curprice,tmp_double) ){
                           curprice= MathFloor(curprice/tmp_double)*tmp_double;
                        }
                     }
                  }
                  draw_open=open=OPEN_PRICE=curprice;
                  
                  updatebuttontext();
                  ObjectSetString(0,exprefix+"Edit3",OBJPROP_TEXT,0, (string) NormalizeDouble(draw_open, _Digits));
                  ChartRedraw(0);
               }
         }
         break;

Línea de Take Profit. A parte de la línea roja y verde, nos queda implementar otra línea (punteada). Ella va a aparecer después de que Usted mueva la línea roja de SL al precio necesario. Va a mostrar el precio en el cual ocurre el TP en la transacción:

Líneas de SL, de TP y del precio de apertura

Fig. 4. Líneas de SL, de TP y del precio de apertura

Botón de apertura de posición. Este botón se muestra de la misma manera que el botón Mostrar línea del precio de apertura (0).

Al pulsar este botón, también va a generarse el evento CHARTEVENT_OBJECT_CLICK. Ya hemos considerado como trabajar con este evento. Como resultado, al pulsar el botón de apertura de la posición, va a ejecutarse la función startPosition:

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_send"){
            startPosition();
         }else if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

Eliminación de los elementos de la interfaz tras la conclusión del EA. No hay que olvidar de una eliminación correcta de los elementos de la interfaz después de que el EA termine su trabajo. Es que si no cuidamos de eso, todos los elementos se quedarán en el gráfico.

Para ejecutar cualquier comando tras la conclusión del EA, basta con escribirlos dentro de la función OnDeinit:

void OnDeinit(const int reason)
  {

     if(reason!=REASON_CHARTCHANGE){
        ObjectsDeleteAll(0, exprefix);
        Comment("");
     }
      
  }

La variable reason contiene los datos sobre la razón de la conclusión del trabajo del EA. En este momento, la única razón que nos importa es el cambio del timeframe (REASON_CHARTCHANGE). El caso es que, por defecto, el cambio del timeframe provoca el cierre y reinicio del EA. Para nosotros, este comportamiento no es más aceptable. Es que en este caso, al cambiar el timeframe, perdemos los precios de SL y de la apertura que probablemente ya tengamos definidos.

Por eso, primero, comprobamos en la función OnDeinit si el cambio del timeframe es el motivo de la eliminación del EA. Y sólo en el caso cuando el motivo del cierre es otro, eliminamos todos los elementos de la interfaz del EA y limpiamos los comentarios del gráfico.

Implementamos las teclas del contros rápido del EA

Claro que la colocación de las órdenes con ratón es una técnica muy acertada. Pero a veces puede ser útil la posibilidad de trabajar más rápidamente con el EA usando las teclas del teclado.

La pulsación de las teclas del teclado también se refiere a la interacción con los elementos de la interfaz del EA, es decir, a la interacción con el gráfico entero en el que el EA está iniciado. Así que necesitamos interceptar las pulsaciones de las teclas del teclado en la función OnChartEvent.

Al pulsar cualquier tecla, ocurre el evento CHARTEVENT_KEYDOWN. En este caso, el código de la tecla pulsada se coloca en el parámetro sparam. Estos conocimientos son suficientes para empezar a procesar la pulsación de las teclas:

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 
   string text="";
   double curprice=0;
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // presionando botones en el gráfico
         break;
      case CHARTEVENT_OBJECT_DRAG:
         // desplazamiento de líneas
         break;
      case CHARTEVENT_KEYDOWN:
         switch((int) sparam){
            // cerrar el EA sin colocar órdenes
            case 45: //x
               closeNotSave();
               break;
            // colocar una orden y cerrar el EA
            case 31: //s
               startPosition();
               break;
            // definir SL lo más bajo posible para abrir una posición Long
            case 22: //u
               setMinStopBuy();
               break;
            // definir SL lo más bajo posible para abrir una posición Sell
            case 38: //l
               setMinStopSell();
               break;
            // cancelar el precio de apertura definido
            case 44: //z
               setZero();
               ChartRedraw();
               break;
            // definir SL a 0.2% del precio actual para abrir una posición Long
            case 3: //2
               set02StopBuy();
               break;
            // definir SL a 0.2% del precio actual para abrir una posición Short
            case 4: //3
               set02StopSell();
               break;
            // colocar SL a 7% del precio actual (parámetro CENT_STOP)
            // para abrir una posición Long
            case 8: //7
               set7StopBuy();
               break;
            // colocar SL a 7% del precio actual (parámetro CENT_STOP)
            // para abrir una posición Short
            case 9: //8
               set7StopSell();
               break;
         }
         break;
   }
}

Así, si Usted define un SL fijo igual al mínimo posible, 0.2% del precio o en centavos del precio, entonces ni siquiera tiene que usar el ratón. Inicia el EA, pulsa la tecla "2" para definir SL igual a 0.2% del precio en Long, pulsa la tecla "S", y la posición se abre.

Si Usted usa MetaTrader 5, incluso puede iniciar el EA con el ratón, asignándole los atajos de teclado. Si alguien desconoce esta posibilidad en MetaTrader 5, entonces, abra el menú contextual del EA necesario en la ventana «Navegador», elija el punto Definir tecla aceleradora, y su vida será más fácil:

Definir teclas aceleradoras para los EAs

Fig. 5. Definir teclas aceleradoras para los EAs

Calcular volumen necesario de transacciones

Nos queda por analizar sólo la función para abrir posiciones (startPosition). No obstante, no tiene prácticamente nada interesante. Simplemente, comprobamos la presencia de todos los datos necesarios para nosotros, a saber: precios de SL, precio de apertura de la posición, ajustes del EA. Después de eso, se calcula el volumen del lote con el que tenemos que entrar en la transacción para cumplir con nuestros riesgos. Y se llama a la función pdxSendOrder, considerada al principio del artículo.

Lo más interesante es el mecanismo del cálculo del volumen de la transacción.

Primero, tenemos que calcular cuánto vamos a perder en caso de SL en los volúmenes lo menos posibles. La implementación de esta funcionalidad en MQL5 se diferencia de MQL4.

En MQL5 hay función especial OrderCalcProfit, que permite calcular el tamaño del beneficio que obtendrá cuando el precio del instrumento se mueve hasta el nivel especificado. Con su ayuda, es fácil de calcular tanto un beneficio potencial, como posibles pérdidas con SL especificado.

Para calcular las pérdidas en MQL4, se usa una fórmula más complicada.

Como resultado, tenemos la siguiente función para calcular las pérdidas:

double getMyProfit(double fPrice, double fSL, double fLot, bool forLong=true){
   double fProfit=0;
   
   fPrice=NormalizeDouble(fPrice,_Digits);
   fSL=NormalizeDouble(fSL,_Digits);
   #ifdef __MQL5__ 
      if( forLong ){
         if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }else{
         if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }
   #else
      if( forLong ){
         fProfit=(fPrice-fSL)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }else{
         fProfit=(fSL-fPrice)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }
   #endif 
   if( fProfit!=0 ){
      fProfit=MathAbs(fProfit);
   }
   
   return fProfit;
}

Una vez obtenido el tamaño de las pérdidas en el volumen mínimo, nos queda determinar el volumen de la transacción con el que las pérdidas no van a superar el riesgo especificado en los ajustes del EA:

      profit=getMyProfit(open, STOPLOSS_PRICE, lot);
      if( profit!=0 ){
         // si en el volumen mínimo el tamaño de las pérdidas es menor que sus riesgos,
         // entonces calculamos el volumen de la transacción necesario
         if( profit<stopin_value ){
            // obtenemos el volumen de la transacción necesaria
            lot*=(stopin_value/profit);
            // corregimos el volumen si no corresponde al paso mínimo posible
            // de acuerdo con el instrumento
            if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.01 ){
               lot=(floor(lot*100))/100;
            }else if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.1 ){
               lot=(floor(lot*10))/10;
            }else{
               lot=floor(lot);
            }
         // si en el volumen mínimo, el tamaño de las pérdidas es mayor que sus riesgos,
         // entonces, cancelamos la apertura de la posición si eso está especificado en los ajustes del EA
         }else if( profit>stopin_value && EXIT_IF_MORE ){
            Alert(langs.wrnEXIT_IF_MORE1+": "+(string) lot+" "+langs.wrnEXIT_IF_MORE2+": "+(string) profit+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(string) stopin_value+" "+AccountInfoString(ACCOUNT_CURRENCY)+")!");
            return;
         }
      }

Restricciones de entrada

El EA comprueba una serie de condiciones durante su trabajo para que Usted no gaste su tiempo intentando abrir una transacción donde eso está prohibido.

Por ejemplo, al inicializar el EA, se comprueba el volumen mínimo permitido del lote para el instrumento actual. Si este valor es 0, el EA no será iniciado. Porque, normalmente, Usted no podrá abrir una posición para los instrumentos con tales configuraciones:

   if(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)==0){
      Alert(langs.wrnMinVolume);
      ExpertRemove();
   }

Además, se comprueba el modo del acceso a la negociación del instrumento. Si la negociación para el instrumento está prohibida o sólo se puede cerrar las transacciones abiertas antes, el EA tampoco será iniciado:

   if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
      Alert(langs.wrnOnlyClose);
      ExpertRemove();
   }

En el momento de la apertura de una posición, se verifica si el precio de apertura establecido y SL son correctos. Por ejemplo, si el paso mínimo del precio es de 0,25, pero su SL tiene el precio igual a 23,29, el bróker simplemente no aceptará su orden. En general, en estos casos, el EA ajusta automáticamente el precio al valor correcto (el precio de SL no será 23,29, sino 23,25 o 23,5). Usted simplemente no podrá establecer el precio «no valido». Pero por sis acaso, se realiza una verificación adicional:

   if( SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)>0 && SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)!=1 ){
      resval=DoubleToString(STOPLOSS_PRICE/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), 8);
      if( StringFind(resval, ".00000000")>0 ){}else{
         Alert(langs.wrnSYMBOL_TRADE_TICK_SIZE+" "+(string) SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)+"! "+langs.wrnSYMBOL_TRADE_TICK_SIZE_end);
         return;
      }
   }

Conclusiones

Está claro que hemos creado sólo los recursos básicos para colocar órdenes. Pero inclusos estas posibilidades simplifican considerablemente la vida a los que operan usando los niveles de Gerchik o cualquier otro nivel.

Espero que Usted no vaya a usar más las tablas de Excel como no es debido. Gracias a eso, la velocidad y la precisión de su negociación aumentarán, igualmente como el beneficio.

Cualquier mejora del EA está permitida y bienvenida.

Si no domina la programación, pero realmente necesita implementar una funcionalidad que no está presente en este EA, por favor, envíe un mensaje privado. Aunque, lo más probable, no sea gratis =)

Otra opción es consultar la funcionalidad de las versiones ampliadas de este EA en el Market:

Tal vez, algo le pueda servir dentro de esta funcionalidad.