English 中文 Español Deutsch 日本語 Português
MagicNumber - "магический" идентификатор ордера

MagicNumber - "магический" идентификатор ордера

MetaTrader 4Примеры | 24 мая 2006, 15:29
12 671 5
Andrey Khatimlianskii
Andrey Khatimlianskii


1. История

    В МТ3 контроль над открытыми позициями был достаточно трудоёмким. У трейдера был очень ограниченный набор инструментов для работы со списком открытых и закрытых позиций. Вопрос о различении "своих" и "чужих" позиций решался достаточно неординарными способами. В МТ4 ситуация кардинально изменилась. Теперь пользователю доступно множество функций, позволяющих полноценно управлять любыми открытыми позициями, установленными ордерами, а также дающих информацию о любой закрытой позиции.

    Для идентификации ордеров добавлен специальный параметр – MagicNumber. Именно о нём и пойдет речь в этой статье.

2. Что такое MagicNumber?

    Из справки MetaEditor:

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   -   Магическое число ордера. Может использоваться как определяемый пользователем идентификатор


    Т.е. при установке ордера (открытии позиции), ему можно присвоить уникальный номер, по которому его потом можно отличить от остальных. Эту возможность нет смысла (да и не получится) использовать при ручной торговле, но она просто незаменима при торговле с помощью эксперта.


    Пример первый: в клиентском терминале одновременно торгуют эксперт и человек.
Задача: эксперт должен торговать по своему алгоритму, и не должен ничего делать с позициями, открытыми вручную.
Решение: эксперт при открытии позиции должен присваивать ей уникальный ненулевой MagicNumber. В дальнейшем он должен управлять только позициями, у которых MagicNumber равен заданному при установке.

    Пример второй: в клиентском терминале одновременно торгуют 2 эксперта с разными алгоритмами.
Задача: эксперты должны управлять только "своими" ордерами.
Решение: при открытии позиций, каждый из экспертов должен использовать уникальный ненулевой MagicNumber. В дальнейшем они должны управлять только позициями, у которых MagicNumber равен заданному при установке.

    Пример третий: в клиентском терминале одновременно торгуют несколько экспертов, человек и эксперт-помошник, реализующий нестандартный Трейлинг Стоп.
Задача: торгующие эксперты должны торговать по своему алгоритму и не должны ничего делать с позициями, открытыми вручную. Эксперт-помошник, реализующий Трейлинг Стоп, должен изменять только позиции, открытые вручную, и не трогать позиции других экспертов.
Решение: торгующие эксперты должны использовать уникальные MagicNumber и управлять только "своими" позициями. Эксперт-помошник должен модифицировать только те позиции, у которых MagicNumber равен 0.

    Все три примера вполне реальны, и пользователь возможно уже ставил перед собой подобные задачи. Во всех трех случаях для решения используется MagicNumber. Это не единственный путь, но наиболее простой.
 

3. Реализация

    Теперь решим конкретную задачу: создадим эксперта, который будет работать только со "своими" позициями, не обращая внимания на позиции, открытые вручную и другими экспертами.

    Для начала напишем простого эксперта, сигналом для открытия позиции у которого будет пересечение индикатором MACD нулевой линии. Выглядеть он будет примерно так:

int start()
{
    //---- Запоминаем значения индикатора для дальнейшего анализа
    //---- Обратите внимание - используем 1-й и 2-й бары. Это даёт задержку в 1 бар 
    //---- (т.е. сигнал появится позже), но защищает от многочисленных открытий и закрытий
    //---- позиций в течении бара
    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();
    //---- перебираем все открытые позиции
    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 ( OrderSymbol() != Symbol() ) continue;
 
        //---- если открыта БАЙ-позиция,
        if ( OrderType() == OP_BUY )
        {
            //---- если МАКД пересёк 0-ю линию вниз,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else return(0);
        }
        //---- если открыта СЕЛЛ-позиция,
        if ( OrderType() == OP_SELL )
        {
            //---- если МАКД пересёк 0-ю линию вверх,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| если выполнение дошло до этого места, значит открытой позиции нет
//| проверяем, есть ли возможность открыть позицию
//+------------------------------------------------------------------+
 
    //---- если МАКД пересёк 0-ю линию вверх,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
    {
        //---- открываем БАЙ позицию
        if ( OrderSend( Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0, "MACD_test", 0, 0, Green ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }
    //---- если МАКД пересёк 0-ю линию вниз,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
    {
        //---- открываем СЕЛЛ позицию
        if ( OrderSend( Symbol(), OP_SELL, 0.1, Bid, 5, 0.0, 0.0, "MACD_test", 0, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

   
    Прикрепим его к графику и посмотрим, как он работает:



 

 

    Всё хорошо, но есть одно но. Если мы во время работы советника откроем позицию по тому же инструменту, он посчитает её "своей" и будет распоряжаться ею на своё усмотрение. А это нам не подходит.

    Модифицируем нашего эксперта так, чтобы он управлял только "своими" позициями:

  • Добавим внешнюю переменную Expert_ID, с помощью которой можно менять значение MagicNumber для открываемых экспертом позиций;
  • После выбора позиции функцией OrderSelect() добавим проверку,  соответствует ли MagicNumber выбранного ордера значению нашей переменной Expert_ID;
  • При открытии позиции в поле MagicNumber вместо 0 будем записывать значение переменной Expert_ID.

    Код с учётом изменений  будет выглядеть так:

extern int Expert_ID = 1234;
 
int start()
{
    //---- Запоминаем значения индикатора для дальнейшего анализа
    //---- Обратите внимание - используем 1-й и 2-й бары. Это даёт задержку в 1 бар 
    //---- (т.е. сигнал появится позже), но защищает от многочисленных открытий и закрытий
    //---- позиций в течении бара
    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();
    //---- перебираем все открытые позиции
    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 ( OrderSymbol() != Symbol() ) continue;
 
        //---- если MagicNumber не равен Expert_ID, пропускаем эту позицию
        if ( OrderMagicNumber() != Expert_ID ) continue;
 
        //---- если открыта БАЙ-позиция,
        if ( OrderType() == OP_BUY )
        {
            //---- если МАКД пересёк 0-ю линию вниз,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else
            { return(0); }
        }
        //---- если открыта СЕЛЛ-позиция,
        if ( OrderType() == OP_SELL )
        {
            //---- если МАКД пересёк 0-ю линию вверх,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| если выполнение дошло до этого места, значит открытой позиции нет
//| проверяем, есть ли возможность открыть позицию
//+------------------------------------------------------------------+
 
    //---- если МАКД пересёк 0-ю линию вверх,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
    {
        //---- открываем БАЙ позицию
        if ( OrderSend( Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0, "MACD_test", 
              Expert_ID, 0, Green ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }
    //---- если МАКД пересёк 0-ю линию вниз,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
    {
        //---- открываем СЕЛЛ позицию
        if ( OrderSend( Symbol(), OP_SELL, 0.1, Bid, 5, 0.0, 0.0, "MACD_test", 
              Expert_ID, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

   
    Теперь при работающем эксперте пользователь смело можем открывать позиции вручную. Эксперт их трогать не будет.


4. Несколько одинаковых экспертов на графиках одного инструмента

    Бывают ситуации, когда надо, чтобы один и тот же эксперт торговал на графике одного торгового инструмента, например, на разных таймфреймах. Если  попробовать прикрепить нашего эксперта к графику EURUSD, H1 и этого же эксперта к графику EURUSD, M30, то они будут мешать друг другу – каждый будет считать открытую позицию "своей", и менять её по своему усмотрению.

    Решить это можно, назначив второму эксперту другой Expert_ID. Но это не самый удобный вариант. При большом количестве экспертов можно просто запутаться в их индивидуальных номерах.

    Для решения этой проблемы нам просто надо использовать в значении MagicNumber период графика. Как это сделать? Если просто складывать период графика с Expert_ID, может возникнуть ситуация, в которой 2 разных эксперта на 2-х разных графиках сгенерируют одинаковый MagicNumber.

    Поэтому Expert_ID мы умножим на 10, а период графика (вернее, его код - от 1 до 9) поставим в конец.

    Выглядеть это будет примерно так:

    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;
   
    Теперь добавим этот код в функцию init() эксперта, и везде заменим Expert_ID на _MagicNumber.

    Окончательный вариант эксперта выглядит так:

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()
{
    //---- Запоминаем значения индикатора для дальнейшего анализа
    //---- Обратите внимание - используем 1-й и 2-й бары. Это даёт задержку в 1 бар 
    //---- (т.е. сигнал появится позже), но защищает от многочисленных открытий и закрытий
    //---- позиций в течении бара
    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();
    //---- перебираем все открытые позиции
    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 ( OrderSymbol() != Symbol() ) continue;
 
        //---- если MagicNumber не равен _MagicNumber, пропускаем эту позицию
        if ( OrderMagicNumber() != _MagicNumber ) continue;
 
        //---- если открыта БАЙ-позиция,
        if ( OrderType() == OP_BUY )
        {
            //---- если МАКД пересёк 0-ю линию вниз,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Bid, 5, Green ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else return(0);
        }
        //---- если открыта СЕЛЛ-позиция,
        if ( OrderType() == OP_SELL )
        {
            //---- если МАКД пересёк 0-ю линию вверх,
            if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
                  NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
            {
                //---- закрываем позицию
                if ( !OrderClose( OrderTicket(), OrderLots(), Ask, 5, Red ) )
                {
                    _GetLastError = GetLastError();
                    Alert( "Ошибка OrderClose № ", _GetLastError );
                    return(-1);
                }
            }
            //---- если сигнал не изменился, выходим - пока рано открывать новую позицию
            else return(0);
        }
    }
 
//+------------------------------------------------------------------+
//| если выполнение дошло до этого места, значит открытой позиции нет
//| проверяем, есть ли возможность открыть позицию
//+------------------------------------------------------------------+
 
    //---- если МАКД пересёк 0-ю линию вверх,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) >  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) <= 0.0    )
    {
        //---- открываем БАЙ позицию
        if ( OrderSend( Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0, "MACD_test", 
              _MagicNumber, 0, Green ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }
    //---- если МАКД пересёк 0-ю линию вниз,
    if ( NormalizeDouble( MACD_1, Digits + 1 ) <  0.0 && 
          NormalizeDouble( MACD_2, Digits + 1 ) >= 0.0    )
    {
        //---- открываем СЕЛЛ позицию
        if ( OrderSend( Symbol(), OP_SELL, 0.1, Bid, 5, 0.0, 0.0, "MACD_test", 
              _MagicNumber, 0, Red ) < 0 )
        {
            _GetLastError = GetLastError();
            Alert( "Ошибка OrderSend № ", _GetLastError );
            return(-1);
        }
        return(0);
    }

    return(0);
}

   
    В таком виде эксперт можно использовать на нескольких графиках с разными периодами.

    Значение переменной Expert_ID придется менять только в случае, если есть необходимость запустить два эксперта на графиках с одинаковым символом и периодом (например, EURUSD H1 и EURUSD H4), а это - крайне редкая ситуация.


    По аналогии с представленным выше кодом, пользователь сможет доработать своих экспертов, чтоб они тоже отличали "свои" позиции от "чужих".

Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
[Удален] | 29 мая 2007 в 12:14

Предлагаю для создания уникального MagicNumber использовать функцию TimeLocal(), которая вызывается при инициализации эксперта в функции init(). Значение функции TimeLocal() и присваивается целой переменной magik. Поскольку старт экспертов и инициализация их переменных на одном компьютере может производиться только последовательно, переменной magik будет присваиваться уникальный номер, что и требуется для работы с любым количеством запущенных экспертов...

Andrey Khatimlianskii
Andrey Khatimlianskii | 29 мая 2007 в 12:31
NPV:

Предлагаю для создания уникального MagicNumber использовать функцию TimeLocal(), которая вызывается при инициализации эксперта в функции init(). Значение функции TimeLocal() и присваивается целой переменной magik. Поскольку старт экспертов и инициализация их переменных на одном компьютере может производиться только последовательно, переменной magik будет присваиваться уникальный номер, что и требуется для работы с любым количеством запущенных экспертов...

Во-первых, за одну секунду может инициализироваться хоть 100 экспертов - и у них получится одинаковый меджик.
А во-вторых, как эксперт "узнает" открытую ранее позицию? Если после каждого запуска будет новый меджик, он будет "терять" старые позиции.
[Удален] | 4 июн. 2007 в 16:32
komposter:
NPV:

Предлагаю для создания уникального MagicNumber использовать функцию TimeLocal(), которая вызывается при инициализации эксперта в функции init(). Значение функции TimeLocal() и присваивается целой переменной magik. Поскольку старт экспертов и инициализация их переменных на одном компьютере может производиться только последовательно, переменной magik будет присваиваться уникальный номер, что и требуется для работы с любым количеством запущенных экспертов...

Во-первых, за одну секунду может инициализироваться хоть 100 экспертов - и у них получится одинаковый меджик.
А во-вторых, как эксперт "узнает" открытую ранее позицию? Если после каждого запуска будет новый меджик, он будет "терять" старые позиции.


Как можно за одну секунду запустить 100 экспертов, если их можно запускать только последовательно?!... Ведь они запускаются на разных таймфреймах...
И какую "открытую ранее позицию" должен узнавать эксперт? Эксперт запускается один раз на одном таймфрейме и работает пока его не закроет хозяин. Все это время он открывает и закрывает только свои позиции с уникальным меджиком, остальные же позиции пропускаются при переборе открытых позиций перед их закрытием. Что можно потерять при этом...?
Andrey Khatimlianskii
Andrey Khatimlianskii | 4 июн. 2007 в 17:20
NPV писал(а):
Как можно за одну секунду запустить 100 экспертов, если их можно запускать только последовательно?!...
Например, загрузив терминал.
Если запустить несколько экспертов а потом перезапустить терминал, они начнут работать практически одновременно.

NPV писал(а):
И какую "открытую ранее позицию" должен узнавать эксперт? Эксперт запускается один раз на одном таймфрейме и работает пока его не закроет хозяин. Все это время он открывает и закрывает только свои позиции с уникальным меджиком, остальные же позиции пропускаются при переборе открытых позиций перед их закрытием. Что можно потерять при этом...?

Опять же, если эксперт откроет позицию, а потом терминал перезагрузится (например, электричество отключат), он уже не найдет открытую позицию, т.к. сгенерируется новый мейджик.
[Удален] | 5 июн. 2007 в 10:07
komposter:
NPV писал(а):
Как можно за одну секунду запустить 100 экспертов, если их можно запускать только последовательно?!...
Например, загрузив терминал.
Если запустить несколько экспертов а потом перезапустить терминал, они начнут работать практически одновременно.

NPV писал(а):
И какую "открытую ранее позицию" должен узнавать эксперт? Эксперт запускается один раз на одном таймфрейме и работает пока его не закроет хозяин. Все это время он открывает и закрывает только свои позиции с уникальным меджиком, остальные же позиции пропускаются при переборе открытых позиций перед их закрытием. Что можно потерять при этом...?

Опять же, если эксперт откроет позицию, а потом терминал перезагрузится (например, электричество отключат), он уже не найдет открытую позицию, т.к. сгенерируется новый мейджик.

Да, согласен, в этом случае мое предложение не работает... Вы правы, нужно учитывать возможные сбои...
События в МetaТrader 4 События в МetaТrader 4
Статья посвящена программному отслеживанию событий в терминале МetaТrader 4, таких как открытие, закрытие и модификация ордеров, и рассчитана на пользователя, обладающего базовыми навыками работы с терминалом и программирования на MQL 4.
Пауза между торговыми операциями Пауза между торговыми операциями
Статья посвящена организации паузы между торговыми операциями при работе нескольких экспертов на одном терминале МТ 4 и рассчитана на пользователя, обладающего базовыми навыками работы с терминалом и программирования на MQL 4.
Самостоятельная оценка результатов тестирования эксперта Самостоятельная оценка результатов тестирования эксперта
В статье представлены формулы и порядок расчета данных, отображаемых в отчете тестера.
Требования к статьям для публикации на MQL4.com Требования к статьям для публикации на MQL4.com
Требования к статьям для публикации на сайте MQL 4 Community