Gestionar las órdenes es sencillo

Andrey Khatimlianskii | 4 febrero, 2016


1. Introducción

Los asesores expertos pueden controlar las posiciones abiertas. El procedimiento consiste en realizar una búsqueda por todas las órdenes mediante un bucle, luego se elige la posición correspondiente por símbolo y valor de MagicNumber, y finalmente se modifica o se cierra la orden. Estos bloques suelen ser muy parecidos y generalmente tienen las mismas funciones. Esta parte repetitiva del código se puede poner en una función, lo que simplifica significativamente la escritura de asesores expertos y hace que el código ocupe mucho menos.

En primer lugar vamos a dividir esta tarea en tres etapas claramente diferenciadas; estas fases se relacionan con tres tipos de expertos diferentes:

  • Expertos que solo pueden abrir una sola posición
  • Expertos que pueden abrir una posición de cada tipo a la vez, por ejemplo, una posición larga y otra corta
  • Expertos que pueden abrir cualquier número de posiciones simultáneamente


2. Una sola posición

Hay muchas estrategias que solo necesitan abrir una posición. Tienen bloques de control bastante sencillos, pero tener que escribirlos consume tiempo y esfuerzo.

Tomemos ahora un asesor experto sencillo cuya señal de apertura es la intersección de las líneas MACD (línea de señal y línea básica). Vamos a simplificar su bloque de control. Este es el aspecto que tenía antes:

extern int  _MagicNumber = 1122;

int start()
  {
//---- Memorizamos los valores del indicador para su posterior análisis
   double MACD_1=iMACD(Symbol(),0,12,26,9,PRICE_CLOSE,
                       MODE_MAIN,1);
   double MACD_2=iMACD(Symbol(),0,12,26,9,PRICE_CLOSE,
                       MODE_MAIN,2);

   int _GetLastError=0,_OrdersTotal=OrdersTotal();
//---- buscamos en todas las posiciones abiertas
   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      // si ocurre algún error al encontrar la posición, vamos a la siguiente
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect( ",z,", SELECT_BY_POS ) - Error #",
               _GetLastError);
         continue;
        }

      // si la posición se abrió, pero no para el símbolo actual, 
      // la saltamos
      if(OrderSymbol()!=Symbol()) continue;

      // si MagicNumber no es igual a _MagicNumber, saltamos 
      // esta posición
      if(OrderMagicNumber()!=_MagicNumber) continue;

      //---- si se abre una posición BUY,
      if(OrderType()==OP_BUY)
        {
         //---- si MACD cruza la línea cero de arriba hacia abajo,
         if(NormalizeDouble(MACD_1,Digits +1)<  0.0 && 
            NormalizeDouble(MACD_2,Digits+1)>=0.0)
           {
            //---- cerramos la posición
            if(!OrderClose(OrderTicket(),OrderLots(),
               Bid,5,Green))
              {
               _GetLastError=GetLastError();
               Alert("Error al cerrar la posición № ",_GetLastError);
               return(-1);
              }
           }
         // si la señal no ha cambiado, salimos; todavía es demasiado pronto 
         // para abrir una posición nueva
         else
           { return(0); }
        }
      //---- si se abre una posición SELL,
      if(OrderType()==OP_SELL)
        {
         //---- si MACD cruza la línea cero de abajo hacia arriba,
         if(NormalizeDouble(MACD_1,Digits+1) >  0.0 && 
            NormalizeDouble(MACD_2,Digits+1)<=0.0)
           {
            //---- cerramos la posición
            if(!OrderClose(OrderTicket(),OrderLots(),
               Ask,5,Red))
              {
               _GetLastError=GetLastError();
               Alert("Error al cerrar la posición № ",_GetLastError);
               return(-1);
              }
           }
         // si la señal no ha cambiado, salimos; todavía es demasiado pronto para 
         // abrir una posición nueva
         else return(0);
        }
     }

//+------------------------------------------------------------------+
//| si se alcanza este punto, no hay ninguna posición abierta        |
//| comprobamos si es posible abrir una posición                     |
//+------------------------------------------------------------------+

//---- si MACD cruza la línea cero de abajo hacia arriba,
   if(NormalizeDouble(MACD_1,Digits+1)>0.0 && 
      NormalizeDouble(MACD_2,Digits+1)<=0.0)
     {
      //---- abrimos una posición BUY
      if(OrderSend(Symbol(),OP_BUY,0.1,Ask,5,0.0,0.0,
         "MACD_test",_MagicNumber,0,Green)<0)
        {
         _GetLastError=GetLastError();
         Alert("Error al enviar la posición № ",_GetLastError);
         return(-1);
        }
      return(0);
     }
//---- si MACD cruza la línea cero de arriba hacia abajo,
   if(NormalizeDouble(MACD_1,Digits+1)<0.0 && 
      NormalizeDouble(MACD_2,Digits+1)>=0.0)
     {
      //---- abrimos una posición SELL
      if(OrderSend(Symbol(),OP_SELL,0.1,Bid,5,0.0,0.0,
         "MACD_test",
         _MagicNumber,0,Red)<0)
        {
         _GetLastError=GetLastError();
         Alert("Error al enviar la posición № ",_GetLastError);
         return(-1);
        }
      return(0);
     }

   return(0);
  }

Ahora vamos a escribir una función que sustituye el bloque que controla las posiciones. Esta función tiene que realizar una búsqueda entre todas las órdenes, encontrar la orden correspondiente, y memorizar todas sus características en variables globales. Su aspecto es como sigue:

int _Ticket = 0, _Type = 0; double _Lots = 0.0, 
_OpenPrice=0.0,_StopLoss=0.0;
double _TakeProfit=0.0; datetime _OpenTime=-1;
double _Profit=0.0,_Swap=0.0;
double _Commission=0.0; string _Comment="";
datetime _Expiration=-1;

void OneOrderInit(int magic)
  {
   int _GetLastError,_OrdersTotal=OrdersTotal();

   _Ticket=0; _Type=0; _Lots=0.0; _OpenPrice=0.0;
   _StopLoss=0.0;
   _TakeProfit=0.0; _OpenTime=-1; _Profit=0.0;
   _Swap=0.0;
   _Commission=0.0; _Comment=""; _Expiration=-1;

   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect( ",z,", SELECT_BY_POS ) - Error #",_GetLastError);
         continue;
        }
      if(OrderMagicNumber()==magic && OrderSymbol()==
         Symbol())
        {
         _Ticket     = OrderTicket();
         _Type       = OrderType();
         _Lots       = NormalizeDouble( OrderLots(), 1 );
         _OpenPrice  = NormalizeDouble( OrderOpenPrice(), Digits);
         _StopLoss   = NormalizeDouble(OrderStopLoss(),Digits);
         _TakeProfit = NormalizeDouble( OrderTakeProfit(), Digits);
         _OpenTime   = OrderOpenTime();
         _Profit     = NormalizeDouble( OrderProfit(), 2 );
         _Swap       = NormalizeDouble( OrderSwap(), 2 );
         _Commission = NormalizeDouble( OrderCommission(), 2 );
         _Comment    = OrderComment();
         _Expiration = OrderExpiration();
         return;
        }
     }
  }

Como se puede observar, es bastante sencillo. Hay 11 variables, cada una de las cuales almacena el valor de una característica de la posición; por ejemplo, el ticket, el tipo de orden, el tamaño del lote, etc. El valor de estas variables de establece a cero al inicio de la función. Las variables se declaran a nivel global y no se inicializan en la llamada a la función. La información del tick precedente no hace falta; todos los datos deben ser recientes. A continuación, las posiciones abiertas se buscan de manera habitual, y cuando el símbolo y el MagicNumber coinciden, entonces las características de la orden se almacenan en las variables correspondientes.

Ahora añadamos esta función a nuestro asesor experto:

extern int  _MagicNumber = 1122;
 
#include <OneOrderControl.mq4>
 
int start()
{
    int _GetLastError = 0;
    
// Memorizamos los parámetros de la posición abierta (si está disponible)
    OneOrderInit( _MagicNumber );
 
    //---- Memorizamos los valores del indicador para su posterior análisis
    double MACD_1 = iMACD(Symbol(), 0, 12, 26, 9, PRICE_CLOSE, 
                          MODE_MAIN, 1 );
    double MACD_2 = iMACD(Symbol(), 0, 12, 26, 9, PRICE_CLOSE, 
                          MODE_MAIN, 2 );
 
    // Ahora, en lugar de buscar posiciones, miramos si hay 
    // alguna posición abierta:
    if ( _Ticket > 0 )
    {
        //---- si se abre una posición BUY,
        if ( _Type == OP_BUY )
        {
            //---- si MACD cruza la línea cero de arriba hacia abajo,
            if(NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
               NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0)
            {
                //---- cerramos la posición
                if(!OrderClose( _Ticket, _Lots, Bid, 5, Green))
                {
                  _GetLastError = GetLastError();
                  Alert( "Error al cerrar la orden № ", _GetLastError);
                  return(-1);
                }
            }
            // si la señal no ha cambiado, salimos; es demasiado pronto 
            // para abrir una posición nueva
            else return(0);
        }
        //---- si se abre una posición SELL,
        if ( _Type == OP_SELL )
        {
            //---- si MACD cruza la línea cero de abajo hacia arriba,
            if(NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
               NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0)
            {
                //---- cerramos la posición
                if(!OrderClose( _Ticket, _Lots, Ask, 5, Red))
                {
                    _GetLastError = GetLastError();
                    Alert( "Error al cerrar la orden № ", _GetLastError);
                    return(-1);
                }
            }
            // si la señal no ha cambiado, salimos; es demasiado pronto 
            // para abrir una posición nueva
            else return(0);
        }
    }
    // si no hay ninguna posición abierta por el experto 
    // ( _Ticket == 0 )
    // si MACD cruza la línea cero de abajo hacia arriba,
    if(NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
       NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0)
    {
        //---- abrimos una posición BUY
        if(OrderSend(Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0,
           "CrossMACD", _MagicNumber, 0, Green ) < 0)
        {
            _GetLastError = GetLastError();
            Alert( "Error al enviar la orden № ", _GetLastError );
            return(-1);
        }
        return(0);
    }
    //---- si MACD cruza la línea cero de arriba hacia abajo,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
    {
        //---- abrimos una posición SELL
        if(OrderSend(Symbol(), OP_SELL, 0.1, Bid, 5, 0.0, 0.0,
           "CrossMACD", 
              _MagicNumber, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Error al enviar la orden № ", _GetLastError );
            return(-1);
        }
        return(0);
    }
 
    return(0);
}

Como se observa, el código del experto es ahora mucho más compacto y se lee mejor. Este es el caso más sencillo.

Ahora vamos a resolver la siguiente tarea.

 

3. Una posición de cada tipo

Para implementar la otra función necesitamos un experto más complicado. Tiene que abrir unas cuantas posiciones de tipos diferentes, para trabajar con todas ellas. Este es el algoritmo del experto:
  • el experto tiene que colocar dos órdenes pendientes en el momento de su lanzamiento: un BuyStop al nivel de Ask+20 puntos y un SellStop al nivel de Bid+20 puntos;
  • si una de las órdenes se dispara la otra se tiene que eliminar;
  • la posición abierta debe acompañarse de un Trailing Stop, y
  • cuando esta se cierra con un StopLoss o con un TakeProfit empezamos de nuevo, es decir, colocamos dos órdenes pendientes otra vez.

Este es el código del experto:

extern int    _MagicNumber = 1123;

extern double Lot          = 0.1;
extern int    StopLoss     = 60;
// distancia al StopLoss en puntos (0 - desactivar)
extern int    TakeProfit=100;
// distancia al TakeProfit en puntos (0 - desactivar)
extern int    TrailingStop=50;
// Trailing Stop en puntos (0 - desactivar)

extern int    Luft=20;
// distancia al nivel donde se coloca la orden pendiente

int start()
  {
// Variables donde se memorizan los tickets de las órdenes 
// de cada tipo
   int BuyStopOrder=0,SellStopOrder=0,BuyOrder=0,SellOrder=0;
   int _GetLastError=0,_OrdersTotal=OrdersTotal();
// buscamos en todas las posiciones abiertas y memorizamos, las posiciones 
// de qué tipo ya se han abierto:
   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      // si ocurre algún error durante la búsqueda de la posición, vamos 
      // a la siguiente
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect(",z,", SELECT_BY_POS) - Error #",_GetLastError);
         continue;
        }

      // si la posición no se abrió para el símbolo actual, la saltamos
      if(OrderSymbol()!=Symbol()) continue;

      // si el MagicNumber no es igual a _MagicNumber, saltamos esta 
      // posición
      if(OrderMagicNumber()!=_MagicNumber) continue;

      // dependiendo del tipo de la posición, cambiamos el valor 
      // de la variable:
      switch(OrderType())
        {
         case OP_BUY:      BuyOrder      = OrderTicket(); break;
         case OP_SELL:     SellOrder     = OrderTicket(); break;
         case OP_BUYSTOP:  BuyStopOrder  = OrderTicket(); break;
         case OP_SELLSTOP: SellStopOrder = OrderTicket(); break;
        }
     }

//---- Si tenemos las dos órdenes pendientes, salimos, 
//---- tenemos que esperar a que una de las dos se dispare
   if( BuyStopOrder > 0 && SellStopOrder > 0 ) return(0);

// buscamos en todas las posiciones abiertas por segunda vez, ahora 
// trabajaremos con ellas:
   _OrdersTotal=OrdersTotal();
   for(z=_OrdersTotal-1; z>=0; z --)
     {
      // si ocurre algún error en la búsqueda de una posición, vamos 
      // a la siguiente
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect(",z,", SELECT_BY_POS) - Error #",_GetLastError);
         continue;
        }

      // si la posición no fue abierta para el símbolo actual,
      // la saltamos
      if(OrderSymbol()!=Symbol()) continue;

      // si el MagicNumber no es igual a _MagicNumber, 
      // saltamos esta posición
      if(OrderMagicNumber()!=_MagicNumber) continue;

      // dependiendo del tipo de la posición, cambiamos 
      // el valor de la variable:
      switch(OrderType())
        {
         //---- si hay una posición BUY abierta,
         case OP_BUY:
           {
            // si todavía no se ha borrado la orden SellStop, 
            // la borramos:
            if(SellStopOrder>0)
              {
               if(!OrderDelete(SellStopOrder))
                 {
                  Alert("Error al borrar la posición #",GetLastError());
                  return(-1);
                 }
              }
            // comprobamos si el StopLoss se tiene que mover:
            // si el tamaño del Trailing Stop no es muy pequeño,
            if(TrailingStop>MarketInfo(Symbol(),
               MODE_STOPLEVEL))
              {
               // si el beneficio es mayor que los puntos 
               // del TrailingStop,
               if(NormalizeDouble(Bid-OrderOpenPrice(),Digits)>NormalizeDouble(TrailingStop*Point,Digits))
                 {
                  // si el nuevo nivel de StopLoss es mayor que 
                  // el nivel actual de la 
                  // posición
                  // (o si la posición no tiene 
                  // StopLoss),
                  if(NormalizeDouble(Bid-TrailingStop*Point,Digits)>OrderStopLoss() || OrderStopLoss()<=0.0)
                    {
                     //---- modificamos la orden
                     if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Bid-TrailingStop*Point,Digits),OrderTakeProfit(),OrderExpiration()))
                       {
                        Alert("Error al modificar la orden #",GetLastError());
                        return(-1);
                       }
                    }
                 }
              }
            // si hay una posición abierta, salimos, 
            // no hacemos nada
            return(0);
           }
         // El siguiente bloque es exactamente el mismo 
         // bloque que procesa una posición BUY,
         // por esto no lo comentamos...
         case OP_SELL:
           {
            if(BuyStopOrder>0)
              {
               if(!OrderDelete(BuyStopOrder))
                 {
                  Alert("Error al borrar la posición #",GetLastError());
                  return(-1);
                 }
              }
            if(TrailingStop>MarketInfo(Symbol(),
               MODE_STOPLEVEL))
              {
               if(NormalizeDouble(OrderOpenPrice()-Ask,Digits)>NormalizeDouble(TrailingStop*Point,Digits))
                 {
                  if(NormalizeDouble(Ask+TrailingStop*Point,Digits)<OrderStopLoss() || OrderStopLoss()<=0.0)
                    {
                     if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Ask+TrailingStop*Point,Digits),OrderTakeProfit(),OrderExpiration()))
                       {
                        Alert("Error al modificar la orden #",GetLastError());
                        return(-1);
                       }
                    }
                 }
              }
            return(0);
           }
        }
     }

//+------------------------------------------------------------------+
//| Si la ejecución alcanza este punto, significa que no hay         |
//| órdenes pendientes o posiciones abiertas                         |
//+------------------------------------------------------------------+
//---- Colocamos BuyStop y SellStop:
   double _OpenPriceLevel,_StopLossLevel,_TakeProfitLevel;
   _OpenPriceLevel=NormalizeDouble(Ask+Luft*Point,Digits);

   if(StopLoss>0)
     { _StopLossLevel=NormalizeDouble(_OpenPriceLevel-StopLoss*Point,Digits); }
   else
     { _StopLossLevel=0.0; }

   if(TakeProfit>0)
     { _TakeProfitLevel=NormalizeDouble(_OpenPriceLevel+TakeProfit*Point,Digits); }
   else
     { _TakeProfitLevel=0.0; }

   if(OrderSend(Symbol(),OP_BUYSTOP,Lot,_OpenPriceLevel,5,_StopLossLevel,_TakeProfitLevel,"",_MagicNumber)<0)
     {
      Alert("Error al enviar la orden #",GetLastError());
      return(-1);
     }

   _OpenPriceLevel=NormalizeDouble(Bid-Luft*Point,Digits);

   if(StopLoss>0)
     { _StopLossLevel=NormalizeDouble(_OpenPriceLevel+StopLoss*Point,Digits); }
   else
     { _StopLossLevel=0.0; }

   if(TakeProfit>0)
     { _TakeProfitLevel=NormalizeDouble(_OpenPriceLevel-TakeProfit*Point,Digits); }
   else
     { _TakeProfitLevel=0.0; }

   if(OrderSend(Symbol(),OP_SELLSTOP,Lot,_OpenPriceLevel,
      5,_StopLossLevel,_TakeProfitLevel,"",_MagicNumber)<0)
     {
      Alert("Error al enviar la orden #",GetLastError());
      return(-1);
     }
   return(0);
  }

Escribamos la función que simplifica el bloque de control de las posiciones abiertas. Tiene que encontrar por una orden de cada tipo y almacenar las características en variables globales. Este es el código:

// variables globales donde se almacenan las características de la orden:
int _BuyTicket=0,_SellTicket=0,_BuyStopTicket=0;
int _SellStopTicket=0,_BuyLimitTicket=0,_SellLimitTicket=0;

double _BuyLots=0.0,_SellLots=0.0,_BuyStopLots=0.0;
double _SellStopLots=0.0,_BuyLimitLots=0.0,
_SellLimitLots=0.0;

double _BuyOpenPrice=0.0,_SellOpenPrice=0.0,
_BuyStopOpenPrice=0.0;
double _SellStopOpenPrice=0.0,_BuyLimitOpenPrice=0.0,
_SellLimitOpenPrice=0.0;

double _BuyStopLoss=0.0,_SellStopLoss=0.0,_BuyStopStopLoss=0.0;
double _SellStopStopLoss=0.0,_BuyLimitStopLoss=0.0,_SellLimitStopLoss=0.0;

double _BuyTakeProfit=0.0,_SellTakeProfit=0.0,
_BuyStopTakeProfit=0.0;
double _SellStopTakeProfit=0.0,_BuyLimitTakeProfit=0.0,
_SellLimitTakeProfit=0.0;

datetime _BuyOpenTime=-1,_SellOpenTime=-1,
_BuyStopOpenTime=-1;
datetime _SellStopOpenTime=-1,_BuyLimitOpenTime=-1,
_SellLimitOpenTime=-1;

double _BuyProfit=0.0,_SellProfit=0.0,_BuySwap=0.0,
_SellSwap=0.0;
double _BuyCommission=0.0,_SellCommission=0.0;

string _BuyComment="",_SellComment="",_BuyStopComment="";
string _SellStopComment="",_BuyLimitComment="",
_SellLimitComment="";

datetime _BuyStopExpiration=-1,_SellStopExpiration=-1;
datetime _BuyLimitExpiration=-1,_SellLimitExpiration=-1;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OneTypeOrdersInit(int magic)
  {
// establecemos a cero el valor de las variables:
   _BuyTicket=0; _SellTicket=0; _BuyStopTicket=0;
   _SellStopTicket=0; _BuyLimitTicket=0; _SellLimitTicket=0;

   _BuyLots=0.0; _SellLots=0.0; _BuyStopLots=0.0;
   _SellStopLots=0.0; _BuyLimitLots=0.0; _SellLimitLots=0.0;

   _BuyOpenPrice=0.0; _SellOpenPrice=0.0; _BuyStopOpenPrice=0.0;
   _SellStopOpenPrice=0.0; _BuyLimitOpenPrice=0.0;
   _SellLimitOpenPrice=0.0;

   _BuyStopLoss=0.0; _SellStopLoss=0.0; _BuyStopStopLoss=0.0;
   _SellStopStopLoss=0.0; _BuyLimitStopLoss=0.0;
   _SellLimitStopLoss=0.0;

   _BuyTakeProfit = 0.0; _SellTakeProfit = 0.0;
   _BuyStopTakeProfit = 0.0;
   _SellStopTakeProfit=0.0; _BuyLimitTakeProfit=0.0;
   _SellLimitTakeProfit=0.0;

   _BuyOpenTime=-1; _SellOpenTime=-1; _BuyStopOpenTime=-1;
   _SellStopOpenTime=-1; _BuyLimitOpenTime=-1;
   _SellLimitOpenTime=-1;

   _BuyProfit=0.0; _SellProfit=0.0; _BuySwap=0.0;
   _SellSwap=0.0;
   _BuyCommission=0.0; _SellCommission=0.0;

   _BuyComment=""; _SellComment=""; _BuyStopComment="";
   _SellStopComment=""; _BuyLimitComment="";
   _SellLimitComment="";

   _BuyStopExpiration=-1; _SellStopExpiration=-1;
   _BuyLimitExpiration=-1; _SellLimitExpiration=-1;

   int _GetLastError=0,_OrdersTotal=OrdersTotal();
   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect(",z,",SELECT_BY_POS) - Error #",
               _GetLastError);
         continue;
        }
      if(OrderMagicNumber()==magic && OrderSymbol()==
         Symbol())
        {
         switch(OrderType())
           {
            case OP_BUY:
               _BuyTicket     = OrderTicket();
               _BuyLots       = NormalizeDouble( OrderLots(), 1 );
               _BuyOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                Digits);
               _BuyStopLoss=NormalizeDouble(OrderStopLoss(),
                                            Digits);
               _BuyTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                              Digits);
               _BuyOpenTime   = OrderOpenTime();
               _BuyProfit     = NormalizeDouble( OrderProfit(), 2 );
               _BuySwap       = NormalizeDouble( OrderSwap(), 2 );
               _BuyCommission = NormalizeDouble( OrderCommission(),
                                                2);
               _BuyComment=OrderComment();
               break;
            case OP_SELL:
               _SellTicket     = OrderTicket();
               _SellLots       = NormalizeDouble( OrderLots(), 1 );
               _SellOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                 Digits);
               _SellStopLoss=NormalizeDouble(OrderStopLoss(),
                                             Digits);
               _SellTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                               Digits);
               _SellOpenTime   = OrderOpenTime();
               _SellProfit     = NormalizeDouble( OrderProfit(), 2 );
               _SellSwap       = NormalizeDouble( OrderSwap(), 2 );
               _SellCommission = NormalizeDouble( OrderCommission(),
                                                 2);
               _SellComment=OrderComment();
               break;
            case OP_BUYSTOP:
               _BuyStopTicket     = OrderTicket();
               _BuyStopLots       = NormalizeDouble( OrderLots(), 1 );
               _BuyStopOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                    Digits);
               _BuyStopStopLoss=NormalizeDouble(OrderStopLoss(),
                                                Digits);
               _BuyStopTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                                  Digits);
               _BuyStopOpenTime   = OrderOpenTime();
               _BuyStopComment    = OrderComment();
               _BuyStopExpiration = OrderExpiration();
               break;
            case OP_SELLSTOP:
               _SellStopTicket     = OrderTicket();
               _SellStopLots       = NormalizeDouble( OrderLots(), 1 );
               _SellStopOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                     Digits);
               _SellStopStopLoss=NormalizeDouble(OrderStopLoss(),
                                                 Digits);
               _SellStopTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                                   Digits);
               _SellStopOpenTime   = OrderOpenTime();
               _SellStopComment    = OrderComment();
               _SellStopExpiration = OrderExpiration();
               break;
            case OP_BUYLIMIT:
               _BuyLimitTicket     = OrderTicket();
               _BuyLimitLots       = NormalizeDouble( OrderLots(), 1 );
               _BuyLimitOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                     Digits);
               _BuyLimitStopLoss=NormalizeDouble(OrderStopLoss(),
                                                 Digits);
               _BuyLimitTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                                   Digits);
               _BuyLimitOpenTime   = OrderOpenTime();
               _BuyLimitComment    = OrderComment();
               _BuyLimitExpiration = OrderExpiration();
               break;
            case OP_SELLLIMIT:
               _SellLimitTicket     = OrderTicket();
               _SellLimitLots       = NormalizeDouble( OrderLots(), 1 );
               _SellLimitOpenPrice  = NormalizeDouble( OrderOpenPrice(),
                                                      Digits);
               _SellLimitStopLoss=NormalizeDouble(OrderStopLoss(),
                                                  Digits);
               _SellLimitTakeProfit=NormalizeDouble(OrderTakeProfit(),
                                                    Digits);
               _SellLimitOpenTime   = OrderOpenTime();
               _SellLimitComment    = OrderComment();
               _SellLimitExpiration = OrderExpiration();
               break;
           }
        }
     }
  }

Ahora vamos a adjuntar la función al experto: 

extern int    _MagicNumber = 1123;

extern double Lot          = 0.1;
extern int    StopLoss     = 60;
// distancia al StopLoss en puntos (0 - desactivar)
extern int    TakeProfit=100;
// distancia al TakeProfit en puntos (0 - desactivar)
extern int    TrailingStop=50;
// Trailing Stop en puntos (0 - desactivar)

extern int    Luft=20;
// distancia al nivel de colocación de la orden pendiente

#include <OneTypeOrdersControl.mq4>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int start()
  {
   int _GetLastError=0;

//---- Memorizamos los parámetros de las posiciones abiertas (si están disponibles)
   OneTypeOrdersInit(_MagicNumber);

//---- Si tenemos las dos órdenes pendientes, salimos, 
//---- tenemos que esperar a que una de las dos se dispare
   if( _BuyStopTicket > 0 && _SellStopTicket > 0 ) return(0);

//---- si hay una posición BUY abierta,
   if(_BuyTicket>0)
     {
      //---- si el SellStop no se ha borrado todavía, lo borramos:
      if(_SellStopTicket>0)
        {
         if(!OrderDelete(_SellStopTicket))
           {
            Alert("Error al borrar la posición #",GetLastError());
            return(-1);
           }
        }
      //---- comprobamos si el StopLoss se tiene que mover:
      //---- si el tamaño del Trailing Stop no es demasiado pequeño,
      if(TrailingStop>MarketInfo(Symbol(),
         MODE_STOPLEVEL))
        {
         //---- si el beneficio de la posición supera TrailingStop puntos,
         if(NormalizeDouble(Bid-_BuyOpenPrice,Digits)>
            NormalizeDouble(TrailingStop*Point,Digits))
           {
            //---- si el nivel nuevo de StopLoss supera el de la posición 
            //     actual
            //---- (o si la posición no tiene StopLoss),
            if(NormalizeDouble(Bid-TrailingStop*Point,
               Digits)>_BuyStopLoss
               || _BuyStopLoss<=0.0)
              {
               //---- modificamos la orden
               if(!OrderModify(_BuyTicket,_BuyOpenPrice,
                  NormalizeDouble(Bid-TrailingStop*Point,
                  Digits),
                  _BuyTakeProfit,0))
                 {
                  Alert("Error al modificar la orden #",
                        GetLastError());
                  return(-1);
                 }
              }
           }
        }
      //---- si hay una posición abierta, salimos, no hacemos nada
      return(0);
     }

//---- El siguiente bloque es idéntico al que procesa 
//     la posición BUY,
//---- por esto no lo comentamos...
   if(_SellTicket>0)
     {
      if(_BuyStopTicket>0)
        {
         if(!OrderDelete(_BuyStopTicket))
           {
            Alert("Error al borrar la posición #",GetLastError());
            return(-1);
           }
        }
      if(TrailingStop>MarketInfo(Symbol(),MODE_STOPLEVEL))
        {
         if(NormalizeDouble(_SellOpenPrice-Ask,Digits)>
            NormalizeDouble(TrailingStop*Point,Digits))
           {
            if(NormalizeDouble(Ask+TrailingStop*Point,
               Digits)<_SellStopLoss
               || _SellStopLoss<=0.0)
              {
               if(!OrderModify(_SellTicket,_SellOpenPrice,
                  NormalizeDouble(Ask+TrailingStop*Point,
                  Digits),
                  _SellTakeProfit,0))
                 {
                  Alert("Error al modificar la orden #",
                        GetLastError());
                  return(-1);
                 }
              }
           }
        }
      return(0);
     }

//+------------------------------------------------------------------+
//| Si la ejecución alcanza este punto, significa que no hay         |
//| órdenes pendientes o posiciones abiertas                         |
//+------------------------------------------------------------------+
//---- Colocamos BuyStop y SellStop:
   double _OpenPriceLevel,_StopLossLevel,_TakeProfitLevel;
   _OpenPriceLevel=NormalizeDouble(Ask+Luft*Point,Digits);

   if(StopLoss>0)
      _StopLossLevel=NormalizeDouble(_OpenPriceLevel -
                                     StopLoss*Point,Digits);
   else
      _StopLossLevel=0.0;

   if(TakeProfit>0)
      _TakeProfitLevel=NormalizeDouble(_OpenPriceLevel+
                                       TakeProfit*Point,Digits);
   else
      _TakeProfitLevel=0.0;

   if(OrderSend(Symbol(),OP_BUYSTOP,Lot,_OpenPriceLevel,
      5,_StopLossLevel,_TakeProfitLevel,"",_MagicNumber)<0)
     {
      Alert("Error al enviar la orden #",GetLastError());
      return(-1);
     }

   _OpenPriceLevel=NormalizeDouble(Bid-Luft*Point,Digits);

   if(StopLoss>0)
      _StopLossLevel=NormalizeDouble(_OpenPriceLevel+
                                     StopLoss*Point,Digits);
   else
      _StopLossLevel=0.0;

   if(TakeProfit>0)
      _TakeProfitLevel=NormalizeDouble(_OpenPriceLevel -
                                       TakeProfit*Point,Digits);
   else
      _TakeProfitLevel=0.0;

   if(OrderSend(Symbol(),OP_SELLSTOP,Lot,_OpenPriceLevel,
      5,_StopLossLevel,_TakeProfitLevel,"",
      _MagicNumber)<0)
     {
      Alert("Error al enviar la orden #",GetLastError());
      return(-1);
     }

   return(0);
  }

Aquí, la diferencia entre el experto inicial y el revisado es mucho más notable, el bloque que controla las posiciones es muy sencillo y se entiende fácilmente.

Ahora es el turno de los expertos más complicados, los que no tienen ninguna limitación en la cantidad de posiciones abiertas simultáneas.

 

4. Control de todas las posiciones

Es suficiente utilizar variables que almacenen las características de las órdenes. Hay que crear algunos arrays, uno por característica. Con esto la función es prácticamente la misma:
  • al inicio establecemos a cero todos los arrays;
  • buscamos en todas las órdenes y guardamos en los arrays las características de los que tienen el símbolo y el MagicNumber igual al parámetro 'magic' de la función;
  • se añade una variable global que almacena el número total de órdenes del experto, esto mejora la usabilidad y sirve de ayuda en el momento de acceder a los arrays;

Este es el código:

// variable que almacena la cantidad de órdenes del experto:
int _ExpertOrdersTotal=0;

// arrays donde se almacenan las características de la orden:
int _OrderTicket[],_OrderType[];
double _OrderLots[],_OrderOpenPrice[],_OrderStopLoss[],
_OrderTakeProfit[];
double _OrderProfit[],_OrderSwap[],_OrderCommission[];
datetime _OrderOpenTime[],_OrderExpiration[];
string _OrderComment[];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void AllOrdersInit(int magic)
  {
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

// cambiamos los tamaños del array según la 
// cantidad de posiciones
// (if _OrdersTotal = 0, cambiamos el tamaño de los arrays por 1)
   int temp_value=MathMax(_OrdersTotal,1);
   ArrayResize(_OrderTicket,temp_value);
   ArrayResize(_OrderType,temp_value);
   ArrayResize(_OrderLots,temp_value);
   ArrayResize(_OrderOpenPrice,temp_value);
   ArrayResize(_OrderStopLoss,temp_value);
   ArrayResize(_OrderTakeProfit,temp_value);
   ArrayResize(_OrderOpenTime,temp_value);
   ArrayResize(_OrderProfit,temp_value);
   ArrayResize(_OrderSwap,temp_value);
   ArrayResize(_OrderCommission,temp_value);
   ArrayResize(_OrderComment,temp_value);
   ArrayResize(_OrderExpiration,temp_value);

// establecemos a cero los arrays
   ArrayInitialize(_OrderTicket,0);
   ArrayInitialize(_OrderType,0);
   ArrayInitialize(_OrderLots,0);
   ArrayInitialize(_OrderOpenPrice,0);
   ArrayInitialize(_OrderStopLoss,0);
   ArrayInitialize(_OrderTakeProfit,0);
   ArrayInitialize(_OrderOpenTime,0);
   ArrayInitialize(_OrderProfit,0);
   ArrayInitialize(_OrderSwap,0);
   ArrayInitialize(_OrderCommission,0);
   ArrayInitialize(_OrderExpiration,0);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect(",z,",SELECT_BY_POS) - Error #",
               _GetLastError);
         continue;
        }
      if(OrderMagicNumber()==magic && OrderSymbol()==
         Symbol())
        {
         // llenamos los arrays
         _OrderTicket[_ExpertOrdersTotal]=OrderTicket();
         _OrderType[_ExpertOrdersTotal] = OrderType();
         _OrderLots[_ExpertOrdersTotal] =
                                         NormalizeDouble(OrderLots(),1);
         _OrderOpenPrice[_ExpertOrdersTotal]=
                                             NormalizeDouble(OrderOpenPrice(),Digits);
         _OrderStopLoss[_ExpertOrdersTotal]=
                                            NormalizeDouble(OrderStopLoss(),Digits);
         _OrderTakeProfit[_ExpertOrdersTotal]=
                                              NormalizeDouble(OrderTakeProfit(),Digits);
         _OrderOpenTime[_ExpertOrdersTotal]=OrderOpenTime();
         _OrderProfit[_ExpertOrdersTotal]=
                                          NormalizeDouble(OrderProfit(),2);
         _OrderSwap[_ExpertOrdersTotal]=
                                        NormalizeDouble(OrderSwap(),2);
         _OrderCommission[_ExpertOrdersTotal]=
                                              NormalizeDouble(OrderCommission(),2);
         _OrderComment[_ExpertOrdersTotal]=OrderComment();
         _OrderExpiration[_ExpertOrdersTotal]=
                                              OrderExpiration();
         _ExpertOrdersTotal++;
        }
     }

// cambiamos el tamaño de los arrays según la cantidad 
// de posiciones que pertenecen al experto
// (if _ExpertOrdersTotal = 0, cambiamos el tamaño de los arrays por 1)
   temp_value=MathMax(_ExpertOrdersTotal,1);
   ArrayResize(_OrderTicket,temp_value);
   ArrayResize(_OrderType,temp_value);
   ArrayResize(_OrderLots,temp_value);
   ArrayResize(_OrderOpenPrice,temp_value);
   ArrayResize(_OrderStopLoss,temp_value);
   ArrayResize(_OrderTakeProfit,temp_value);
   ArrayResize(_OrderOpenTime,temp_value);
   ArrayResize(_OrderProfit,temp_value);
   ArrayResize(_OrderSwap,temp_value);
   ArrayResize(_OrderCommission,temp_value);
   ArrayResize(_OrderComment,temp_value);
   ArrayResize(_OrderExpiration,temp_value);
  }
// variable que almacena la cantidad de órdenes del experto:
int _ExpertOrdersTotal=0;

// arrays donde se almacenan las características de la orden:
int _OrderTicket[],_OrderType[];
double _OrderLots[],_OrderOpenPrice[],_OrderStopLoss[],
_OrderTakeProfit[];
double _OrderProfit[],_OrderSwap[],_OrderCommission[];
datetime _OrderOpenTime[],_OrderExpiration[];
string _OrderComment[];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void AllOrdersInit(int magic)
  {
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

// cambiamos los tamaños del array según la 
// cantidad de posiciones
// (if _OrdersTotal = 0, cambiamos el tamaño de los arrays por 1)
   int temp_value=MathMax(_OrdersTotal,1);
   ArrayResize(_OrderTicket,temp_value);
   ArrayResize(_OrderType,temp_value);
   ArrayResize(_OrderLots,temp_value);
   ArrayResize(_OrderOpenPrice,temp_value);
   ArrayResize(_OrderStopLoss,temp_value);
   ArrayResize(_OrderTakeProfit,temp_value);
   ArrayResize(_OrderOpenTime,temp_value);
   ArrayResize(_OrderProfit,temp_value);
   ArrayResize(_OrderSwap,temp_value);
   ArrayResize(_OrderCommission,temp_value);
   ArrayResize(_OrderComment,temp_value);
   ArrayResize(_OrderExpiration,temp_value);

// establecemos a cero los arrays
   ArrayInitialize(_OrderTicket,0);
   ArrayInitialize(_OrderType,0);
   ArrayInitialize(_OrderLots,0);
   ArrayInitialize(_OrderOpenPrice,0);
   ArrayInitialize(_OrderStopLoss,0);
   ArrayInitialize(_OrderTakeProfit,0);
   ArrayInitialize(_OrderOpenTime,0);
   ArrayInitialize(_OrderProfit,0);
   ArrayInitialize(_OrderSwap,0);
   ArrayInitialize(_OrderCommission,0);
   ArrayInitialize(_OrderExpiration,0);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z --)
     {
      if(!OrderSelect(z,SELECT_BY_POS))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect(",z,",SELECT_BY_POS) - Error #",
               _GetLastError);
         continue;
        }
      if(OrderMagicNumber()==magic && OrderSymbol()==
         Symbol())
        {
         // llenamos los arrays
         _OrderTicket[_ExpertOrdersTotal]=OrderTicket();
         _OrderType[_ExpertOrdersTotal] = OrderType();
         _OrderLots[_ExpertOrdersTotal] =
                                         NormalizeDouble(OrderLots(),1);
         _OrderOpenPrice[_ExpertOrdersTotal]=
                                             NormalizeDouble(OrderOpenPrice(),Digits);
         _OrderStopLoss[_ExpertOrdersTotal]=
                                            NormalizeDouble(OrderStopLoss(),Digits);
         _OrderTakeProfit[_ExpertOrdersTotal]=
                                              NormalizeDouble(OrderTakeProfit(),Digits);
         _OrderOpenTime[_ExpertOrdersTotal]=OrderOpenTime();
         _OrderProfit[_ExpertOrdersTotal]=
                                          NormalizeDouble(OrderProfit(),2);
         _OrderSwap[_ExpertOrdersTotal]=
                                        NormalizeDouble(OrderSwap(),2);
         _OrderCommission[_ExpertOrdersTotal]=
                                              NormalizeDouble(OrderCommission(),2);
         _OrderComment[_ExpertOrdersTotal]=OrderComment();
         _OrderExpiration[_ExpertOrdersTotal]=
                                              OrderExpiration();
         _ExpertOrdersTotal++;
        }
     }

// cambiamos el tamaño de los arrays según la cantidad 
// de posiciones que pertenecen al experto
// (if _ExpertOrdersTotal = 0, cambiamos el tamaño de los arrays por 1)
   temp_value=MathMax(_ExpertOrdersTotal,1);
   ArrayResize(_OrderTicket,temp_value);
   ArrayResize(_OrderType,temp_value);
   ArrayResize(_OrderLots,temp_value);
   ArrayResize(_OrderOpenPrice,temp_value);
   ArrayResize(_OrderStopLoss,temp_value);
   ArrayResize(_OrderTakeProfit,temp_value);
   ArrayResize(_OrderOpenTime,temp_value);
   ArrayResize(_OrderProfit,temp_value);
   ArrayResize(_OrderSwap,temp_value);
   ArrayResize(_OrderCommission,temp_value);
   ArrayResize(_OrderComment,temp_value);
   ArrayResize(_OrderExpiration,temp_value);
  }

A continuación vamos a ilustrar el funcionamiento de la función. Escribimos un experto sencillo que muestra la información de todas las posiciones que abre el asesor experto.

El código es bastante sencillo:

extern int _MagicNumber    = 0;

#include AllOrdersControl.mq4>

int start()
  {
   AllOrdersInit(_MagicNumber);

   if(_ExpertOrdersTotal>0)
     {
      string OrdersList=StringConcatenate(Symbol(),
                                          ", MagicNumber ",_MagicNumber,":\n");
      for(int n=0; n _ExpertOrdersTotal; n++)
        {
         OrdersList=StringConcatenate(OrdersList,
                                      "Orden # ",_OrderTicket[n],
                                      ", pérdida/ganancia: ",
                                      DoubleToStr(_OrderProfit[n],2),
                                      " ",AccountCurrency(),"\n");
        }
      Comment(OrdersList);
     }

   return(0);
  }
Si  _MagicNumber se establece a 0, el experto mostrará la lista de posiciones abiertas manualmente:

 

5. Conclusión

Ya para terminar, me gustaría comparar la velocidad de los expertos que buscan órdenes por si mismos con aquellos que implementan funciones. Para ello se han probado las dos versiones en el modo "Todos los ticks", 10 veces consecutivas con optimización _MagicNumber. La misma plataforma MetaTrader ha medido el tiempo de las pruebas, el tiempo transcurrido se mide automáticamente. Estos son los resultados:

Asesor Experto

Tiempo empleado por los 10 tests (mm:ss)

CrossMACD_beta

(sin función)

                                              07:42

CrossMACD

11:37

DoublePending_beta

(sin función)

08:18

DoublePending

                                             09:42

Como se observa en la tabla, los expertos que utilizan funciones trabajan un poco más despacio, por lo menos esto es lo que se deduce de las pruebas. Es un coste razonable que se compensa con la usabilidad y la simplicidad del código fuente del programa.

En cualquier caso, el lector es libre de decidir si le conviene utilizarlo o no.