MagicNumber, el identificador "mágico" de la orden

Andrey Khatimlianskii | 19 enero, 2016


1. Preámbulo

En МТ3, el manejo de las posiciones abiertas requería bastante tiempo. Los traders tenían a su disposición una herramienta bastante limitada para trabajar con la lista de posiciones abiertas y cerradas. Hacía falta aplicar soluciones bastante complicadas para poder distinguir qué posiciones pertenecían a un experto y cuáles eran de otro. En MT4 esta situación ha cambiado completamente. Ahora los traders pueden utilizar una completa variedad de funciones para gestionar las posiciones abiertas, colocar órdenes y obtener acceso a la información de las posiciones cerradas.

Se ha añadido un parámetro especial llamado MagicNumber para identificar las órdenes. Este artículo explica cómo funciona este parámetro.

2. ¿Qué es el MagicNumber?

Referencia MQL4:

int OrderSend( string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment=NULL, int magic=0, datetime expiration=0, color arrow_color=CLR_NONE)

magic - Número mágico de la orden. Se puede utilizar como identificador definido por el usuario.

Es decir, uno puede asignar un número único a una orden en el momento de la apertura. En consecuencia, este número sirve para distinguir una orden determinada del resto de órdenes. No tiene sentido aplicar esta característica cuando se hace trading manual, pero es muy importante para los asesores expertos cuando se hace trading automático.


Ejemplo 1: Un trader humano y un experto están operando en el terminal cliente al mismo tiempo.
Tarea: El experto tiene que operar de acuerdo a su algoritmo, y no hace nada con posiciones abiertas manualmente.
Solución: El experto asigna un MagicNumber único, diferente de cero, a la posición que se va a abrir. En el futuro solamente gestionará las posiciones cuyo MagicNumber sea igual al predeterminado.

Ejemplo 2: Dos expertos que implementan diferentes algoritmos se encuentran operando en el terminal cliente al mismo tiempo.
Tarea: Cada uno de los expertos tiene que gestionar sus propias órdenes.
Solución: Cada experto utiliza su propio MagicNumber (diferente de cero) en el momento de abrir posiciones. En el futuro solamente gestionarán las posiciones cuyo MagicNumber sea igual al predeterminado.

Ejemplo 3: Varios asesores expertos, un trader humano y un experto ayudante implementan un Trailing Stop no estándar; todos operan en el terminal cliente de forma simultánea.
Tarea: Los expertos trabajan de acuerdo a sus algoritmos y no hacen nada con posiciones abiertas manualmente. El experto ayudante que implementa el Trailing Stop puede modificar solo las posiciones abiertas manualmente, pero no las colocadas por otros expertos.
Solución: Los asesores expertos utilizan sus números mágicos únicos y solo gestionan sus posiciones. El experto ayudante solo debe modificar las posiciones con un MagicNumber igual a 0.

Los tres ejemplos son bastante realistas, seguro que muchos lectores se habrán tenido que enfrentar a los problemas planteados. En todos los casos el número mágico soluciona el problema. No es la solución única, pero sí la más sencilla.


3. Implementación

Ahora vamos a resolver esta tarea específica: crear un experto que trabaje solamente con sus propias posiciones sin prestar atención a las posiciones abiertas manualmente, ni a las posiciones de otros asesores expertos.

Primero escribiremos un experto sencillo cuya señal de apertura de posiciones es cuando el indicador MACD cruza la línea cero. Este es el código del experto:

int start()
{
    //---- Recordamos los valores del indicador para futuros análisis
    //---- Nótese que utilizamos la primera y la segunda barra. Esto permite un retraso de una hora. 
    //---- es decir, la señal aparecerá más tarde, pero protege contra la repetición de apertura/cierre
    //---- de posiciones en una barra
    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();
    //---- búsqueda en todas las posiciones abiertas
    for ( int z = _OrdersTotal - 1; z >= 0; z -- )
    {
        //---- si ocurre un error en el momento de seleccionar 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 se abrió para un símbolo diferente del actual, saltamos
        if ( OrderSymbol() != Symbol() ) continue;
 
        //---- si se ha abierto la 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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose # ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una posición nueva.
            else return(0);
        }
        //---- si se ha abierto la 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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose # ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una posición nueva.
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| si la ejecución alcanza este punto no hay posición abierta       |
//| comprobamos si todavía 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", 0, 0, Green ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Error OrderSend # ", _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", 0, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Error OrderSend # ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

Ahora vamos a adjuntar el experto al gráfico. Veamos cómo funciona:


Todo está bien pero hay un problema. Si abrimos una posición durante la operación del experto, este entenderá que tal posición es suya y actuará en consecuencia. Pero esto no es lo que queremos.

Modificaremos nuestro experto de tal forma que gestione solo sus propias posiciones:

Teniendo en cuenta pues los cambios mencionados, el código es el siguiente:

extern int Expert_ID = 1234;
 
int start()
{
    //---- Recordamos los valores del indicador para futuros análisis
    //---- Nótese que utilizamos la primera y la segunda barra. Esto permite un retraso de una hora. 
    //---- es decir, la señal aparecerá más tarde, pero protege contra la repetición de apertura/cierre
    //---- posiciones dentro de una barra
    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();
    //---- búsqueda en todas las posiciones abiertas
    for ( int z = _OrdersTotal - 1; z >= 0; z -- )
    {
        //---- si ocurre un error al buscar una posición, saltamos a la siguiente
        if ( !OrderSelect( z, SELECT_BY_POS ) )
        {
            _GetLastError = GetLastError();
            Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - Error #", _GetLastError );
            continue;
        }
 
        //---- si una posición está cerrada para un símbolo diferente al actual, saltamos
        if ( OrderSymbol() != Symbol() ) continue;
 
        //---- si MagicNumber no es igual a Expert_ID, saltamos esta posición
        if ( OrderMagicNumber() != Expert_ID ) continue;
 
        //---- si una posición BUY está abierta,
        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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose # ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una nueva posición
            else
            { return(0); }
        }
        //---- si se ha abierto 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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una nueva posición
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| si la ejecución alcanza este punto no hay posición abierta       |
//| comprobamos si todavía 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", 
              Expert_ID, 0, Green ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Error OrderSend # ", _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", 
              Expert_ID, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Error OrderSend # ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

Ahora, el usuario puede abrir posiciones manualmente mientras el asesor experto trabaja. El experto no las tocará.


4. Varios expertos idénticos en gráficos diferentes de un símbolo

Hay casos donde el mismo EA debe hacer trading en gráficos del mismo símbolo, pero en diferentes marcos temporales, por ejemplo. Si intentamos adjuntar nuestro experto al gráfico EURUSD H1 y a EURUSD M30 simultáneamente, interferirán el uno con el otro. Los asesores entenderán que la posición abierta es la suya y la modificarán a su juicio.

Este problema se puede solucionar asignando otro Expert_ID al otro experto. Aunque esto no es lo más conveniente. Si se utilizan muchos expertos a la vez uno de ellos se puede hacer un lío con los IDs.

Podemos encontrarnos con este problema al utilizar el periodo del gráfico con MagicNumber. ¿Cómo vamos a hacerlo? Si añadimos el periodo del gráfico al Expert_ID es posible que dos expertos distintos, trabajando en dos gráficos diferentes, generen el mismo MagicNumber.

Así que es mejor que multipliquemos Expert_ID por 10 (para ser precisos, su código de 1 a 9) y pongamos el periodo del gráfico al final.

Tendrá el siguiente aspecto:

    int Period_ID = 0;
    switch ( Period() )
    {
        case PERIOD_MN1: Period_ID = 9; break;
        case PERIOD_W1:  Period_ID = 8; break;
        case PERIOD_D1:  Period_ID = 7; break;
        case PERIOD_H4:  Period_ID = 6; break;
        case PERIOD_H1:  Period_ID = 5; break;
        case PERIOD_M30: Period_ID = 4; break;
        case PERIOD_M15: Period_ID = 3; break;
        case PERIOD_M5:  Period_ID = 2; break;
        case PERIOD_M1:  Period_ID = 1; break;
    }
    _MagicNumber = Expert_ID * 10 + Period_ID;

Hay que añadir este código a la función init() del experto y sustituir en todas partes Expert_ID por _MagicNumber.

Esta es la versión final del EA:

extern int Expert_ID = 1234;
int _MagicNumber = 0;
 
int init()
{
    int Period_ID = 0;
    switch ( Period() )
    {
        case PERIOD_MN1: Period_ID = 9; break;
        case PERIOD_W1:  Period_ID = 8; break;
        case PERIOD_D1:  Period_ID = 7; break;
        case PERIOD_H4:  Period_ID = 6; break;
        case PERIOD_H1:  Period_ID = 5; break;
        case PERIOD_M30: Period_ID = 4; break;
        case PERIOD_M15: Period_ID = 3; break;
        case PERIOD_M5:  Period_ID = 2; break;
        case PERIOD_M1:  Period_ID = 1; break;
    }
    _MagicNumber = Expert_ID * 10 + Period_ID;

    return(0);
}
 
int start()
{
    //---- Recordamos los valores del indicador para futuros análisis
    //---- Nótese que utilizamos la primera y la segunda barra. Esto permite un retraso de una hora. 
    //---- es decir, la señal aparecerá más tarde, pero protege contra la repetición de apertura/cierre
    //---- posiciones dentro de una barra
    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();
    //---- búsqueda en todas las posiciones abiertas
    for ( int z = _OrdersTotal - 1; z >= 0; z -- )
    {
        //---- si ocurre un error al buscar una posición, saltamos a la siguiente
        if ( !OrderSelect( z, SELECT_BY_POS ) )
        {
            _GetLastError = GetLastError();
            Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - Error #", _GetLastError );
            continue;
        }
 
        //---- si una posición está abierta para un símbolo diferente al actual, saltamos
        if ( OrderSymbol() != Symbol() ) continue;
 
        //---- si MagicNumber no es igual a _MagicNumber, saltamos esta posición
        if ( OrderMagicNumber() != _MagicNumber ) continue;
 
        //---- si una posición BUY está abierta,
        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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose # ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una posición nueva
            else return(0);
        }
        //---- si se ha abierto 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    )
            {
                //---- cerrar la posición
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Error OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- si la alerta no ha cambiado, salimos; es demasiado pronto para abrir una posición nueva
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| si la ejecución alcanza este punto no hay posición abierta       |
//| comprobamos si todavía 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 OrderSend # ", _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 OrderSend # ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

El experto se puede utilizar en varios gráficos con diferentes periodos.

El valor de la variable Expert_ID se cambiará solo si existe la necesidad de lanzar dos expertos en gráficos del mismo símbolo y periodo (por ejemplo, EURUSD H1 y EURUSD H4), aunque esto sucede en raras ocasiones.

Del mismo modo, el usuario puede mejorar sus EAs con el código anterior, y enseñarles a distinguir sus posiciones de aquellas que no les pertenecen.