Торговые советники (Expert Advisors)

  • Читать Закрыть 1

    Введение.

    В сети Интернет вы можете найти бесплатно или купить за небольшие деньги великое множество механических систем, которые, со слов авторов, принесут вам десятки, а то и сотни процентов дохода в месяц. Большинство таких «граалей» показывают очень хорошие результаты при тестировании на исторических данных, однако на демо-счете и реальном счете такие системы терпят неудачу. Дело в том, что написание советников (Expert Advisors) — механических торговых систем для MetaTrader 4 — очень легкое занятие, которое по силам любому трейдеру. Поэтому в сети Интернет появилось огромное количество любительских «поделок», в которых допущен ряд ошибок.

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

    Вначале несколько слов о том, что же такое советник (Expert Advisor):

    Советник — это запрограммированный на языке MetaQuotes Language 4 трейдером алгоритм совершения сделок и управления ордерами.

    Конечно, можно торговать на своем счете вручную, но, например, в Альпари предлагается несколько десятков валютных пар плюс десятки других инструментов: CFD на американские акции, на товарные фьючерсы и многое другое. Представьте себе, сколько времени вы будете тратить на анализ каждого инструмента. Очень часто возникает ситуация, когда по какому-либо инструменту уже сформировался сигнал к открытию позиции, однако вы упускаете благоприятный момент для сделки, т. к. в тот момент вы были заняты анализом другого инструмента. К тому же вы не можете не спать и не есть. Но пока вы спите, вы можете проспать сигнал к совершению сделки.

    Можно привести еще десятки доводов в пользу того, чтобы передать управление вашим счетом в руки того, кто будет делать это 24 часа в сутки, 5 дней в неделю, не испытывая при этом никаких эмоций (которые сильно мешают успешной торговле). Тому, кто будет в состоянии отслеживать в режиме реального времени ВСЕ инструменты. Тому, кто будет действовать четко в соответствии с запрограммированным вами алгоритмом. Этот неоценимый помощник — советник, или иными словами, торговый алгоритм, запрограммированный на языке MetaQuotes Language 4.

    Все вышесказанное звучит очень обнадеживающе и оптимистично. Однако, не все так просто. Советник делает ровно то, что вы ему приказали. Если ваша торговая тактика изначально убыточна, то ваш советник будет методично, в режиме реального времени, не пропуская ни одной возможности совершить сделку, торговать по вашему алгоритму и может довести ваш торговый счет до плачевного состояния.

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

  • Читать Закрыть 2

    Создаем нового советника .

    Естественно, что первым делом Вы должны иметь представление, а в чем же заключается Ваша торговая тактика, и что Вы хотите запрограммировать. Принципы создания любой торговой тактики подробно описаны в разделе «Торговые тактики», поэтому примем за аксиому, что Вы знаете, что делаете, т.е. в Вашей голове уже сформировался торговый алгоритм, который Вы желаете запрограммировать в виде советника на языке MetaQuotes Language 4. Итак, начнем…

    Запустите терминал MetaTrader 4

    Запустите терминал MetaTrader 4

    Окно «Навигатор» позволяет быстро получить доступ к различным возможностям программы. Список возможностей представлен в виде дерева и содержит 5 групп:

    • «Счета» — список счетов.
    • «Индикаторы» — список доступных индикаторов технического анализа.
    • «Советники» — список доступных советников.
    • «Пользовательские индикаторы» — список всех доступных пользовательских индикаторов, т.е. программ, написанных на языке MetaQuotes Language 4 и реализующих функцию индикатора.
    • «Скрипты» — библиотеки пользовательских функций, предназначенные для хранения часто используемых блоков пользовательских программ.

    Щелкните правой кнопкой мыши на любом из доступных советников. Появится контекстное меню:

    • Attach to a chart — «прикрепить» советник к активному графику. Это означает, что с каждый тиком по данной валютной паре советник будет совершать запрограммированные трейдером действия.
    • Modify — вызвать MetaQuotes Language Editor и начать редактирование кода советника.
    • Delete — удалить советника.
    • Create — создать нового советника.
    • Add to favorites — добавить советника в закладки (вкладка «Favorites» окна «Навигатор») для последующего быстрого доступа к советнику.
    • Set hotkey — задать «горячую» клавишу для выделенного советника.

    Как Вы уже, наверняка, догадались, Вам нужно выбрать пункт «Create». В результате откроется окно MetaQuotes Language Editor и Вы сможете приступить к созданию своего первого советника.

  • Читать Закрыть 3

    Настраиваем параметры.

    Итак, в прошлом выпуске мы вызвали контекстное меню окна «Навигатор» группы «Expert Advisors» и выбрали пункт меню «Create». В результате появилось окно, в котором нам предстоит выбрать тип объекта, который мы хотим создать (см. рис. 1).

    Рис. 1. Выберем тип объекта, который мы хотим написать на языке MetaQuotes Language 4

    Рис. 1. Выберем тип объекта, который мы хотим написать на языке MetaQuotes Language 4

    Нас интересует создание эксперта, поэтому выбираем пункт «Expert Advisor». Нажимаем «Далее».

    В следующем окне (рис. 2.) установим параметры нового эксперта:

    • Name — имя советника (файлу будет присвоено такое же имя с расширением *.mq4;
    • Author — автор советника;
    • Link — адрес веб-сайта автора советника;
    • Parameters — параметры советника, которые можно впоследствии менять при подключении советника к графику;

    Рис. 2. Настройка параметров советника

    Рис. 2. Настройка параметров советника

    Зададим для тренировки какой-нибудь параметр. Например, MAPeriod. Для этого нажмите кнопку «Add» и настройте каждое поле параметра:

    • Name — имя параметра (в нашем примере MAPeriod);
    • Type — тип параметра;
    • Initial value — начальное значение.

    Параметры могут быть следующих типов:

    • int — целое число (от -2147483648 до 2147483647);
    • bool — логический тип (или ложь — false, или истина — true);
    • datetime — дата и время (в формате D’YYYY.MM.DD HH:MM:SS’);
    • color — цвет (о формате я расскажу в следующих выпусках);
    • double — вещественное число (-1.7 × 10-308 до 1.7 × 10308, точность — 15 значащих цифр);
    • string — заключенная в двойные кавычки строка (например, «это строка»).

    Нажмем кнопку «Готово». В результате мы увидим заготовку для кода нашего первого эксперта:

    //+------------------------------------------------------------------+
    //| My First Expert.mq4 |
    //| Copyright c 2006, Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2006, Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"

    //---- input parameters
    extern int MAPeriod=0;
    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+


  • Читать Закрыть 4

    Структура советника. Специальные функции.

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

    //+------------------------------------------------------------------+
    //| My First Expert.mq4 |
    //| Copyright c 2006, Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2006, Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"

    //---- input parameters
    extern int MAPeriod=0;
    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+


    Первые несколько строчек — комментарии. Комментарий — это неисполняемый кусок кода, т.е., иными словами, это любая полезная читателю кода информация. Комментарии бывают однострочными — в этом случае они начинаются с двойного слэша: //, — и весь остаток строчки после // будет считаться комментарием. Также комментарии бывают многострочными. Такие комментарии должны начинаться с /* и заканчиваться */.

    #property copyright "Copyright © 2006, Andrey Vedikhin"


    C помощью #property copyright «строка» мы можем задать имя/наименование создателя эксперта.

    #property link "http://www.vedikhin.ru"


    C помощью #property link «веб-сайт» зададим веб-сайт создателя.

    extern int       MAPeriod=0;


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

    Слово extern перед определением переменной показывает, что это внешняя переменная или параметр, которая будет видна в настройках эксперта. int — тип переменной (в нашем примере этот параметр — целое число от -2147483648 до 2147483647 — см. предыдущий выпуск). А MAPeriod — имя параметра. Мы хотим, чтобы по умолчанию значение параметра было равным нулю, поэтому добавим =0 сразу же после имени параметра. Точка с запятой показывает, что мы завершили описание параметра эксперта.

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

  • Читать Закрыть 5

    Создаем собственные функции.

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

    • Если вдруг Вам понадобится внести изменения в этот кусок кода, то это придется делать везде, где он используется. Существует большая вероятность, что где-то Вы забудете внести изменения, и советник будет торговать с ошибками.
    • Гораздо более читабельным будет код, когда выполнение повторяющихся действий вынесено в отдельную функцию, а в соответствующих местах эта функция вызывается. В этом случае изменения (в случае необходимости) надо делать всего в одном месте и читаемость такого кода будет гораздо лучше.

    Таким образом, в любой момент Вы можете описать свою функцию, которая будет выполнять определенные действия:

    тип     имя ( параметры )
    {
    действия
    }


    Функция может возвращать какое значение любого из стандартных типов (int, bool, datetime, double, color, string — см. описание типов в выпуске «Настраиваем параметры нового эксперта»), либо не возвращать никакого значения (тип void).

    Имя функции — это любое понятное Вам имя, которое помогает сразу же вспомнить, что делает эта функция. Имя может состоять из цифр (0-9), латинских прописных и строчных букв (а — z и А — Z, следует помнить, что буквы ’a’ и ’A’ — совершенно разные символы), символа подчеркивания (_). Первый символ не может быть цифрой. Также имя функции не должно совпадать с зарезервированным словом (т.е. словом, которое имеет особый смысл в языке MetaQuotes Language 4 — но об этом позже).

    Пример имени функции: GetConnectionStatus.

    Каждой функции могут передаваться определенные параметры. Они указываются через через запятую в круглых скобках после имени функции. Для каждого параметра указывается тип.

    Приведем пример описания функции, которая вычисляет наименьшее из двух целых чисел:

    int GetMinValue(int a1, int a2)
    {
    if (a1

    Эта функция возвращает значение типа int (целое число), ее имя — GetMinValue и она имеет два входящих целочисленных параметра: a1 и a2.

    Для возврата значения в вызывающую программу используется команда return(значение).

    Если в каком-то месте нашего советника нам потребуется вычислить минимальное число из двух, то мы можем вызвать нашу функцию следующим образом: имя(параметры через запятую). Например: GetMinValue(10, 15).

    Функции init(), start() и deinit()

    Функция init()

    • Эта функция вызывается в следующих случаях:
    • после присоединения советника к графику;
    • после старта MetaTrader 4 и подгрузки исторических данных;
    • после смены инструмента или периода графика;
    • после перекомпиляции программы в редакторе MetaEditor;
    • после изменения настроек эксперта;
    • после смены торгового счета.

    Функция start()

    Функция start() запускается на каждом новом тике. Собственно это основная функция эксперта, т.к. она вызывается на каждом тике и выполняет основную работу.


    Важно: Если функция start() не успела завершить свою работу до прихода следующего тика, следующий тик пропускается и функция для него не вызывается.

    Функция deinit()

    С помощью UninitializeReason() можно узнать причину вызова функции deinit().

    • Функция deinit() вызвается в следующих случаях:
    • при завершении работы MetaTrader 4 или при закрытии графика (функция UninitializeReason() возвращает REASON_CHARTCLOSE);
    • при удалении эксперта с графика (REASON_REMOVE);
    • перед изменением инструмента графика или периода графика (REASON_CHARTCHANGE);
    • при удачной перекомпиляции программы в MetaEdito (REASON_RECOMPILE)r;
    • при изменении параметров эксперта (REASON_PARAMETERS);
    • при переключении на другой счет (REASON_ACCOUNT).

    Функция UninitializeReason() возвращает 0, если скрипт самостоятельно завершил работу.

    Если в течение 2.5 секунд фукция deinit() работу не завершила, то она прерывается принудительно.

  • Читать Закрыть 6

    Исходный код нашего первого советника.

    Наиболее простым способом для новичка изучить процесс написания экспертов будет подробный разбор специально написанных мной для этой цели экспертов. На данный момент (да и далее в своих статьях) я не буду ставить перед собой цель написать супер гениального эксперта, который способен будет зарабатывать десятки процентов в месяц и делать это стабильно. Основная цель — показать все аспекты программирования экспертов, а не написать «грааль».

    Итак, внимательно изучите код эксперта ниже, но не волнуйтесь, что на данный момент Вам пока еще ничего не понятно. В следующих выпусках будет подробно разберана каждая строчку этого эксперта.

    //+------------------------------------------------------------------+
    //| My First Expert.mq4 |
    //| Copyright © 2006, Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2006, Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"

    #define STATE_SQUARE 0
    #define STATE_LONG 1
    #define STATE_SHORT 2

    //---- input parameters
    extern int MAPeriod=13;
    extern double LotsNumber=1.0;


    //---- глобальные переменные
    int CurrentState;
    int MyOrderTicket;


    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----
    if (iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0) > Close[0])
    CurrentState = STATE_SHORT;
    else CurrentState = STATE_LONG;

    MyOrderTicket = 0;
    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----
    int err;

    double MA;
    MA = iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);

    if ( CurrentState == STATE_LONG)
    {
    if (MA > Close[1]) //скользящая средняя выше цены закрытия
    {
    CurrentState = STATE_SHORT;

    //переворачиваемся в продажу

    //---закрыть позицию, если была открыта
    if ( MyOrderTicket != 0)
    {
    if (!OrderClose(MyOrderTicket, LotsNumber, Bid, 3, CLR_NONE))
    {
    err = GetLastError();
    Print("Ошибка при закрытии позиции: ", err);
    return(0);
    }
    MyOrderTicket = 0;
    }
    RefreshRates();

    //--- длинная позиция была закрыта успешно

    //--- теперь откроем позицию в продажу

    //--- проверим на наличие свободных средств
    if (!CheckForEnoughMargin()) return(0);

    MyOrderTicket = OrderSend(Symbol(), OP_SELL, LotsNumber, Bid, 3, 0, 0,
    NULL, 0, 0, CLR_NONE);
    if (MyOrderTicket<0)
    {
    err = GetLastError();
    Print("Ошибка при открытии позиции: ", err);
    MyOrderTicket = 0;
    }
    }
    }
    else
    {
    if (MA < Close[1]) //скользящая средняя ниже цены закрытия
    {
    CurrentState = STATE_LONG;

    //переворачиваемся в покупку

    //---закрыть позицию, если была открыта
    if ( MyOrderTicket != 0)
    {
    if (!OrderClose(MyOrderTicket, LotsNumber, Ask, 3, CLR_NONE))
    {
    err = GetLastError();
    Print("Ошибка при закрытии позиции: ", err);
    return(0);
    }
    MyOrderTicket = 0;
    }
    RefreshRates();

    //--- короткая позиция была закрыта успешно

    //--- теперь откроем позицию в покупку

    //--- проверим на наличие свободных средств
    if (!CheckForEnoughMargin()) return(0);

    MyOrderTicket = OrderSend(Symbol(), OP_BUY, LotsNumber, Ask, 3, 0, 0,
    NULL, 0, 0, CLR_NONE);
    if (MyOrderTicket<0)
    {
    err = GetLastError();
    Print("Ошибка при открытии позиции: ", err);
    MyOrderTicket = 0;
    }
    }
    }
    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| Проверка наличия свободной маржи |
    //+------------------------------------------------------------------+
    bool CheckForEnoughMargin()
    {
    if (GetOneLotMargin(Symbol())*LotsNumber

    Вначале разберем, что означают следующие строчки:

     

       #define STATE_SQUARE   0
    #define STATE_LONG 1
    #define STATE_SHORT 2


     

    Эти строчки дают возможность вместо написания малоинформтивных чисел 0, 1 или 2 использовать более понятные имена STATE_SQUARE, STATE_LONG или STATE_SHORT. Результат будет абсолютно таким же — если программа встретит в тексте STATE_SQUARE, STATE_LONG или STATE_SHORT, она заменит их на 0, 1 и 2 соответственно. Такая программа будет более читаемой.

    Также константы используются в тех случаях, когда какое-то значение использует в нескольких местах программы и существует вероятность, что трейдеру может понадобиться изменить это значение в будущем. Если использовать константу, достаточно будет это сделать только в одном месте — в директиве #define.

    Для объявления константы используйте следующую конструкцию:

     

       #define      имя   значение


     

    Примеры констант:

     

       #define      AUTHOR         "Vedikhin Andrey"
    #define Lots 1.1
    #define ItemsNumber 77


     

    Следующие две строчки абсолютно идентичны, но первая — более читаема:

     

       for(x=1;x<=ItemsNumber;x++) Print(Lots*x);
    for(x=1;x<=77;x++) Print(1.1*x);


     

    Константа может быть любого типа: int, bool, datetime, double, color, string

  • Читать Закрыть 7

    Хранение данных в переменных.

    Любой эксперт оперирует с некоторыми данными — ценами, значениями индикаторов, объемами, количеством открытых позиций и пр. Те, места, где эти данные хранятся в эксперте, называются переменными. В качестве имен переменных можно использовать цифры (0-9), латинские прописные и строчные буквы (а — z и А — Z, следует помнить, что буквы ’a’ и ’A’ — совершенно разные символы), символ подчеркивания (_). Первый символ не может быть цифрой. Также имя переменной не должно совпадать с зарезервированным словом (т.е. словом, которое имеет особый смысл в языке MetaQuotes Language 4 — но об этом позже).

    Желательно в качестве имени переменной использовать такое, из которого сразу становится понятным предназначение переменной. Например, переменная, содержащая количество открытых позиций, может иметь такое имя: OpenPositionsNumber.

    • Можно создавать переменные разных типов:
    • int — целое число (от -2147483648 до 2147483647);
    • bool — логический тип (или ложь — false, или истина — true);
    • datetime — дата и время (в формате D’YYYY.MM.DD HH:MM:SS’);
    • color — цвет (о формате я расскажу в следующих выпусках);
    • double — вещественное число (-1.7 × 10-308 до 1.7 × 10308, точность — 15 значащих цифр);
    • string — заключенная в двойные кавычки строка (например, «это строка»).

    Перед использованием переменной ее надо объявить. Это можно сделать несколькими способами:

    тип     имя;
    или
    тип имя = начальное_значение;


    Примеры объявлений переменных:

    int Count;
    datetime InitialDate = D'2006.07.12 00:00';
    string ip_address = "127.0.0.1";


    В дальнейшем для того, чтобы обратиться к значению переменной, надо будет просто указать ее имя:

    i = 5 + Count;


    Иногда требуется сохранить не одно значение, а определенное количество взаимосвязанных значений. Например, значение индикатора на текущем баре, на предыдущем баре, … , N-баров назад. Для таких целей существуют массивы. Массив — это индексированная совокупность однотипных данных.

    Предположим, что у нас в эксперте есть такое описание массива Prices:

    double Prices[50];


    Это означает, что массив-переменная Prices предоставляет доступ к 50 элементам типа double. Для доступа к i-му элементу надо указать Prices[i]. Нумерация элементов начинается с нуля и заканчивается 49-м элементов (в нашем случае).

    Если Вы попробуете обратиться к элементу вне этого диапазона, то будет зафиксирована ошибка ERR_ARRAY_INDEX_OUT_OF_RANGE (4002), которую можно получить при помощи функции GetLastError().

    В случае необходимости можно использовать не только одномерные массивы (у которых только один индекс), но и многомерные массивы.

    Пример описания двухмерного массива, состоящего из шести массивов, каждый из которых состоит из 50 элементов:

    int mas[6] [50];


    При описании массива можно в фигурных скобках задать начальные значения для каждого элемента:

    int    a[4][4] = { 1, 1, 1, 1,  2, 2, 2, 2,  3, 3, 3, 3,  4, 4, 4, 4 };


    Начальные значения элементов массивов могут быть только константами. Если Вы в фигурных скобках при инициализации массива указали меньшее количество начальных значений, чем размерность массива, то недостающие элементы инициализируются нулями.

    Локальные и статические переменные

    • Единственным нераскрытом моментом остались области видимости переменных. Дело в том, что переменные бывают нескольких типов:

       

    • локальные переменные,
    • статические переменные,
    • глобальные переменные,
    • внешние переменные,
    • параметры функций.

    Локальные переменные

    Если переменная объявлена в пределах какой-нибудь функции, то она является локальной переменной и видна только в пределах этой функции. За пределами этой функции к этой переменной обращаться нельзя.

    Инициализация (присвоение начального значения) локальной переменной происходит каждый раз при вызове функции. Присваиваемое начальное значение может быть как константой, так и выражением.

    Пример объявления и инициализации локальной переменной:

     

    int CalcFactorial(int n)
    {
    int i = 0;
    ...
    }


     

    Cтатические переменные

    Статические переменные объявляются директивой static. Они инициализируются только один раз — при первом вызове функции и сохраняют свое значение даже после выхода из функции. В следующий раз при новом вызове функции статические переменные будут иметь то же значение, которое они имели перед выходом из функции в послений раз.

    Статические переменные объявляются в пределах описания функции и поэтому доступны для использования только в пределах той функции, в которой они объявлены.

    Пример объявления статической переменной:

     

    int GetOpenPositionsNumber()
    {
    static int Count = 0;
    ...
    }


     

    Внешние переменные

    Мы уже сталкивались с внешними переменными, когда описывали параметры эксперта MAPeriod и LotsNumber в коде нашего первого эксперта.

    Внешние переменные описываются с помощью зарезервированного слова extern:

       extern int       MAPeriod=13;
    extern double LotsNumber=1.0;


    Внешние переменные не могут быть массивами.

    Внешние переменные — это параметры эксперта, которые можно изменять.

    «Прикрепите» эксперт к графику. Для этого нажмите правую кнопку мыши на названии эксперта в окне «Навигатор» и в появившемся контекстном меню выберите «Присоединить к графику». Появится окно свойств эксперта. Во вкладке «Входные параметры» Вы можете изменить значения внешних переменных, описанных в эксперте (см. рис. 1).

    Рис. 1. Параметры эксперта

    Рис. 1. Параметры эксперта

    Если эксперт уже «прикреплен» к графику (в правом верхнем углу графика Вы видите улыбающуюся или грустную рожицу), то для изменения его параметров — значений внешних переменных — нажмите на рожице правую кнопку мыши и выберите в в появившемся контекстном меню пункт «Советники — Свойства …». Более быстрый способ — использовать горячую клавишу F7. Появится окно с параметрами эксперта (см. рис. 1), в котором Вы сможете изменить значения внешних переменных.

    Глобальные переменные

    Для человека, имеющего уже опыт написания экспертов, выражение «глобальные переменные» может внести небольшую путанницу. Дело в том, что на самом деле существует два вида «глобальных переменных»:

    • переменные, которые видны из любой функции ОДНОГО эксперта (т.е. являются глобальными в пределах этого эксперта); и
    • переменные, которые являются общими для ВСЕХ экспертов.

    В этом выпуске речь пойдет именно о глобальных переменных первого типа — тех, которые являются общими для всех функций ОДНОГО эксперта.

    Второй же тип глобальных переменных я рассмотрю в следующих выпусках, когда буду рассказывать о функциях, с помощью которых к таким переменным получают доступ, — GlobalVariable…().

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

       //---- глобальные переменные
    int CurrentState;
    int MyOrderTicket;

    //---- определения функций
    int init()
    {
    ...
    }



    Начальное значение глобальной переменной может быть только константой (если не задано никакого значения, то она инициализируется нулем). Это значение присваивается глобальной переменной только один раз, до выполнения функции init().

  • Читать Закрыть 8

    Параметры функций.

    Формальные параметры функций

    Как уже говорилось в разделе «Создание собственных функций» функцию можно описать следующим образом:

     

       тип    имя ( формальные параметры )
    {
    действия
    }



     

    Формальные параметры функции — это значения, которые передаются в функцию. Они указываются через через запятую в круглых скобках после имени функции. Для каждого параметра указывается тип (любой из стандартных: массивы, int, bool, datetime, double, color, string).

    В функцию не может быть передано более 64 параметров.

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

    В описании функции параметрам можно присваивать значения по умолчанию. При этом следует иметь в виду, что все параметры, следующие за первым параметром со значением по умолчанию, должны также иметь значения по умолчанию:

     

       int GetSomethingUseful ( int a, int b = 0, bool f = true, double r = 5.9)
    {
    ...
    }



     

    В этом случае все или часть параметро по умолчанию можно упускать при вызове функции, а вместо них автоматически будут подставлены значения по умолчанию:

     

    • Вызов GetSomethingUseful(3, 5, false) будет эквивалентен вызову GetSomethingUseful(3, 5, false, 5.9).
    • Вызов GetSomethingUseful(3, 5) будет эквивалентен вызову GetSomethingUseful(3, 5, true, 5.9).
    • Вызов GetSomethingUseful(3) будет эквивалентен вызову GetSomethingUseful(3, 0, true, 5.9).

     

    При этом следует помнить, что если мы пропускаем какой-то параметр, имеющий значение по умолчанию, то и все последующие параметры также должны не указываться.

    Если мы импортируем функцию из другого модуля, то такие параметры таких функций не могут иметь значения по умолчанию.

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

     

      void  MyFirstFunction ()
    {
    int a = 10;
    MySecondFunction ( a );
    }

    void MySecondFunction ( int b )
    {
    b = b + 1;
    Print ( b );
    }



     

    В этом примере мы вызываем MySecondFunction, передавая в качестве параметра значение переменной «a» (в нашем случае — 10). Таким образом, при вызове MySecondFunction переменной «b» — формальному параметры функции — будет присвоено значение 10, которое будет следующей строчкой увеличено на единицу и выведено в лог. Однако при этом значение переменной a в MyFirstFunction не изменится и останется равным 10.

    Существует и другой способ передачи параметров в функцию — по ссылке (используя после описания типа знак амперсенда — &). Рассмотрим новый пример:

     

      void  MyFirstFunction ()
    {
    int a = 10;
    MySecondFunction ( a );
    }

    void MySecondFunction ( int& b )
    {
    b = b + 1;
    Print ( b );
    }



     

    В этом примере мы вызываем MySecondFunction, передавая в качестве параметра ссылка на переменную «a». Таким образом, в функции MySecondFunction всегда, когда будет идти обращение к переменной b, реально будет идти обращение к переменной a. Т.е. следующей строчкой значение переменной «a» будет увеличено на единицу и выведено в лог. После завершения функции MySecondFunction переменная «а» будет равна 11.

    В качестве параметров по ссылке можно передавать как переменные стандартных типов (но только в пределах одного модуля), так и массивы (как в пределах одного модуля, так и в другие модули).

  • Читать Закрыть 9

    Операторы. Арифметические операции.

    Составной оператор

    В выпуске «Использование констант в эксперте» мы научились описывать константы и разобрались, что же в коде нашего первого эксперта означают следующие строчки:

     

       #define STATE_SQUARE   0
    #define STATE_LONG 1
    #define STATE_SHORT 2



     

    В выпуске Внешние переменные мы научились задавать параметры эксперта, и нам стали понятны следующие строчки:

     

       extern int           MAPeriod     = 13;
    extern double LotsNumber = 1.0;


     

    Кроме локальных и статических переменных мы рассмотрели и глобальные (на уровне одного модуля) переменные, в результате приоткрылась завеса над еще двумя строчками нашего эксперта:

     

       int CurrentState;
    int MyOrderTicket;



     

    Теперь рассмотрим более подробно, что же в нашем эксперте делает функция init().

     

       int init()
    {
    //----
    if (iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0) > Close[0])
    CurrentState = STATE_SHORT;
    else CurrentState = STATE_LONG;

    MyOrderTicket = 0;
    //----
    return(0);
    }



     

     

    Как мы знаем из выпуска «Функции init(), start() и deinit()» эта функция запускается в следующих случаях:

    • после присоединения советника к графику;
    • после старта MetaTrader 4 и подгрузки исторических данных;
    • после смены инструмента или периода графика;
    • после перекомпиляции программы в редакторе MetaEditor;
    • после изменения настроек эксперта;
    • после смены торгового счета.

     

    Если отбросить комментарии, которые начинаюся с двойного флэша (//), то мы увидим, что эта функция состоит из нескольких команд (назовем их операторами), разделенных точкой запятой (;). Когда нам нужно объединить несколько операторов в один связный блок (например, в функцию), мы используем конструкцию, которая называется составной оператор. Иными словами мы эти операторы заключаем в фигурные скобки { и }. После закрывающей фигурной скобки { не должно стоять точки с запятой.

    Пример составного оператора:

     

      if (b==3)
    {
    b = b + 1;
    Print (b);
    }



     

     

    Оператор присваивания. Арифметические операции

     

    Формат оператора присваивания:

       переменная = выражение;


    В качестве левой части оператора присваивания может выступать также элемент массива. Вначале вычисляется выражение, которое потом присваивается переменной.

    Пример оператора присваивания:

     

       b  = b  + 1;


     

    Допустим, что перед выполнением этого оператора переменная «b» была равна 7. Тогда вначале вычисляется выражение в правой части ( 7+1 = 8 ), а затем значение этого выражение (8) присваивается переменной «b».

    В выражении может использоваться несколько типов операций. Рассмотрим самый простой тип — арифметические операции:

    Операция Пример
    Сложение двух аргументов j + k
    Вычитание второго аргумента из первого j — k
    Умножение аргументов j * k
    Получить частное от деления первого аргумента на второй j / k
    Получить остаток от деления первого аргумента на второй j % k
    Изменить знак аргумента -k

     

    Вне выражений (в виде отдельного оператора) можно применять операции:

    • Увеличение аргумента на единицу: j++
    • Уменьшение аргумента на единицу: j-

     


    Пример:

     

       j++;                // верное выражение
    k = (j++) + 3; // неверное выражение


     

  • Читать Закрыть 10

    Операции отношения и логические операции.

    Операции отношения

    Как мы уже знаем, один из стандартных типов переменных в языке MetaQuotes Language 4 — тип bool. Переменная типа bool может принимать только два значения: ИСТИНА (true) и ЛОЖЬ (false). Значение ЛОЖЬ представляется в виде нулевого значения, а ИСТИНА — ненулевого.

    Значение операции отношения или логической операции будет иметь тип bool.

    Операции отношения

    Операция Результат
    a == b Истина, если a равно b
    Ложь, если a не равно b
    a != b Истина, если a не равно b
    Ложь, если a равно b
    a < b Истина, если a меньше b
    Ложь, если a больше или равно b
    a <= b Истина, если a меньше или равно b
    Ложь, если a больше b
    a > b Истина, если a больше b
    Ложь, если a меньше или равно b
    a >= b Истина, если a больше или равно b
    Ложь, если a меньше b


    Примечание: в связи с тем, что числа с плавающей точкой (тип double) не могут быть представлены точно в связи с ограниченным количеством значащих цифр после запятой (в MetaQuotes Language 4 точность — 15 значащих цифр), нельзя производить сравнение таких чисел на равенство (==) или неравенство (!=) без предварительной нормализации (о нормализации вещественных чисел я расскажу в следующих выпусках).

    Логические операции

    Напомню, что значение ЛОЖЬ (false) представляется в виде нулевого значения, а ИСТИНА (true) — ненулевого.

    В этом выпуске я рассмотрю логические операции НЕ (!), ИЛИ (||) и И (&&).


    Логическое отрицание НЕ (!)

    Операнд A Значение выражения !A
    ИСТИНА (true) ЛОЖЬ (false)
    ЛОЖЬ (false) ИСТИНА (true)



    Пример:

     

       bool b;
    b = false; // переменная b равна false (ЛОЖЬ)
    b = !b; // переменная b равна true (ИСТИНА)
    b = !b; // переменная b равна false (ЛОЖЬ)



     


    Логическая операция ИЛИ (||)

    Результат логической операции ИЛИ равен true, если хотя бы один из операндов равен true. Если оба операнда равны false, то и результат логического ИЛИ также будет равен false.

    Операнд A Операнд B Значение выражения A || B
    ЛОЖЬ (false) ЛОЖЬ (false) ЛОЖЬ (false)
    ЛОЖЬ (false) ИСТИНА (true) ИСТИНА (true)
    ИСТИНА (true) ЛОЖЬ (false) ИСТИНА (true)
    ИСТИНА (true) ИСТИНА (true) ИСТИНА (true)


    Пример:

     

       bool a = true;   // a равно true
    bool b = false; // b равно false
    b = b || a; // b равно true


     


    Логическая операция И (&&)

    Результат операции логического И будет равен true только в случае, если оба операнда равны true. Во всех иных случаях результат операции будет равен false.

    Операнд A Операнд B Значение выражения A && B
    ЛОЖЬ (false) ЛОЖЬ (false) ЛОЖЬ (false)
    ЛОЖЬ (false) ИСТИНА (true) ЛОЖЬ (false)
    ИСТИНА (true) ЛОЖЬ (false) ЛОЖЬ (false)
    ИСТИНА (true) ИСТИНА (true) ИСТИНА (true)


    Пример:

     

       bool a = true;    // a равно true
    bool b = false; // b равно false
    b = b && a; // b равно false


     

  • Читать Закрыть 11

    Побитовые операции.

    Для начала необходимо рассказать о формате представления чисел в компьютере.

    Мы в нашей жизни привыкли к десятичному представлению чисел: 56, 777, 10957 и т.д. Десятичное представление числа 10957 означает, что 10957 = 1×104 +0×103+9×102+5×101+7×100. Иными словами десятичное число — это a0*100+a1*101+a2*102+…+an*10n. Т.е. сумма произведений соответствующей цифры (ai) на соответствующую степень числа 10 (10i).

    В компьютере внутренний формат представления чисел — двоичный. В двоичном формате все числа состоят из нулей и единиц, а в качестве множителя используется не число 10, возведенное в степень, а соответствующая степень числа 2. Например, двоичное число 10001101 равно десятичному (обычному) числу 1×27+0×26+0×25+0×24+1×23+1×22+0×21+1×20 = 128+0+0+0+8+4+0+1 = 141.

    Каждая такая двоичная цифра (1 или 0) называется битом.

    Теперь мы знаем достаточно, чтобы рассмотрить побитовые операции.

    Побитовые операции

    Дополнение до единицы. В каждом разряде единица заменяется нулем, а нуль — единицей. Пример:

       int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    b = ~b; // переменная b стала равна 01110010, т.е. 114

     

    Сдвиг вправо. Двоичное представление первого операнда сдвигается вправо на количество разрядов, равное значению второго операнда. Освобождающиеся «левые» разряды будут заполняться нулями. Пример:

       int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    b = b >> 2; // переменная b стала равна 00100011, т.е. 67

     

    Сдвиг влево. Двоичное представление первого операнда сдвигается влево на количество разрядов, равное значению второго операнда. Появившиеся «правые» разряды будут заполняться нулями. Пример:

       int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    b = b << 2; // переменная b стала равна 1000110100, т.е. 564

     

    Побитовая операция И. Результат будет содержать единицу в тех разрядах, где соответствующие разряды первого и второго операнда содержат единицу. В других случаях соответствующий бит результата будет равен 0. Пример:

       int a = 25;  // начальное значение переменной a равно 25,
    // что в двоичном представлении равно 00011001
    int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    a = a & b; // переменная a стала равна 00001001, т.е. 9

     

    Побитовая операция ИЛИ. Результат будет содержать ноль в тех разрядах, где соответствующие разряды первого и второго операнда содержат ноль. В других случаях соответствующий бит результата будет равен 1. Пример:

       int a = 25;  // начальное значение переменной a равно 25,
    // что в двоичном представлении равно 00011001
    int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    a = a | b; // переменная a стала равна 10011101, т.е. 157

     

    Побитовая операция исключающее ИЛИ. Значение результирующего выражения будет содержать 1 в тех разрядах, в которых x и y имеют разные двоичные значения, и 0 — во всех остальных разрядах. Пример:

       int a = 25;  // начальное значение переменной a равно 25,
    // что в двоичном представлении равно 00011001
    int b = 141; // начальное значение переменной b равно 141,
    // что в двоичном представлении равно 10001101
    a = a ^ b; // переменная a стала равна 10010100, т.е. 148

     

  • Читать Закрыть 12

    Условный оператор.

    Вернемся к рассмотрению нашего первого советника и расскажу об условном операторе if-else, который я использовал в функции init().

     

       int init()
    {
    //----
    if (iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0) > Close[0])
    CurrentState = STATE_SHORT;
    else CurrentState = STATE_LONG;

    MyOrderTicket = 0;
    //----
    return(0);
    }



     

    Формат условного оператора if-else:

     

       if (выражение)
    первый_оператор
    else
    второй_оператор


     

    Принцип работы оператора: вычисляется значение выражения в скобках; если оно равно true, то выполняется «первый_оператор», в противном случае — «второй_оператор».

    В нашем первом эксперте в функции init() есть такая строчка:

     

          if (iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0) > Close[0])
    CurrentState = STATE_SHORT;
    else CurrentState = STATE_LONG;


     

    В этой строчке вычисляется выражение iMA(NULL, 0, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0) > Close[0]. Забегая вперед, скажу, что в этом выражении сравнивается значение экспоненциальной скользящей средней по цене закрытия с периодом, определенным внешней переменной MAPeriod, и цена закрытия текущего бара ( Close[0] ).

    Если значение скользяйшей средней больше цены закрытия, то выполняется первый оператор:

     

       CurrentState = STATE_SHORT;


     

    Если — меньше, то второй:

     

       CurrentState = STATE_LONG;


     

    Если в качестве оператора есть необходимость выполнить сразу несколько операторов, то можно использовать составной оператор, т.е. заключить в фигурные скобки необходимые нам операторы. При этом после закрывающей фигурной скобки точка с запятой уже не ставится.

    Пример использования составного оператора можно найти и в нашем эксперте:

     

       if (!IsTesting()) 
    return(MarketInfo(s, MODE_LOTSIZE)*MarketInfo(StringSubstr(s, 0, 3)+"USD",
    MODE_BID)/AccountLeverage());
    else
    {
    p = iClose(StringSubstr(s, 0, 3)+"USD", Period(),
    iBarShift(StringSubstr(s, 0, 3)+"USD", Period(), CurTime(), true));
    return(MarketInfo(s, MODE_LOTSIZE)*p/AccountLeverage());
    }



     

    Если у нас нет необходимости в выполнении второго оператора (в случае, если выражение в круглых скобках не равно true), то часть else можно опустить:

     

       if (выражение)
    первый_оператор


     

    Пример из нашего эксперта:

     

       if (s == "CHFJPY")
    {
    p = iClose("USDCHF", Period(), iBarShift("USDCHF", Period(), CurTime(), true));
    return(MarketInfo(s, MODE_LOTSIZE)/(AccountLeverage()*p));
    }



     

    Если несколько операторов if-else вложены друг в друга, а часть операторов имеет пропущенную часть else, то else всегда связывается с ближайшим предыдущим оператором if в том же блоке, не имеющим части else.

    Обычно операторы в коде эксперта выполняются последовательно — один за другим. Однако часто требуется изменить последовательность их выполнения в зависимости от каких-то условий. В этом выпуске я рассмотрел один из способов изменения этой последовательности: условный оператор if-else.

  • Читать Закрыть 13

    Цикл while.

    Обычно операторы в коде эксперта выполняются последовательно — один за другим. Однако часто требуется изменить последовательность их выполнения в зависимости от каких-то условий. В прошлом выпуске я рассмотрел один из способов изменения этой последовательности: условный оператор if-else. Применение этого оператора оправдано, когда есть необходимость выполнить две разные последовательности действий в зависимости от какого-то условия.

    В этом выпуске я планирую рассказать об операторе цикла while. Уже само название говорит о том, что этот оператор применяется в тех случаях, когда нужно повторить какое-то количество раз одну и ту же последовательность операторов.

    Формат оператора цикла while:

     

       while (выражение)
    оператор;


     

    Каждый раз перед выполнением «оператора» будет проверяться значение «выражения». Если «выражение» истинно, то «оператор» будет выполнен. Далее «выражение» проверяется еще раз. Если оно опять истинно, то «оператор» будет выполнен еще раз. И так будет продолжаться до тех пор, пока «выражение» будет равно true. Как только выражение станет ложным, произойдет выход из цикла и управление будет передано оператору, следующему за циклом while.

    Цикл while может быть не выполнен ни разу, если «выражение» изначально (при первой проверке) было ложным.

    Если требуется, чтобы в теле цикла выполнялся не один оператор, а несколько, нужно использовать составной оператор, т.е. заключить несколько операторов в фигурные скобки {}. После закрывающей фигурной скобки } не должно стоять точки с запятой.

    К сожалению, в нашем первом эксперте я не использую цикл while, поэтому пример его использования мне придется просто придумать:

     

       int i = 0;
    while ( i < 9 )
    {
    Print( i );
    i++;
    }
    Print ("Done");



     

    При первой проверке «выражение» (i<9) будет истинным (т.к. 0<9) и будет выполнен составной оператор, который состоит из функции Print (вывода информации в журнал экспертов), которая выведет число 0 в журнал, и из оператора i++, который увеличивает переменную i на единицу.

    Далее выражение i<9 будет вычислено еще раз. На этот раз оно опять будет истинным. В результате в лог файл будет выведено текущее значение переменной i (т.е. 1), а после этого значение переменной будет опять увеличено на единицу и станет равным 2. Цикл будет повторяться до тех пор, пока значение переменной i не станет равным 9. Тогда выражение i<9 перестанет быть истинным и цикл прервется, а управление будет передано следующему оператору:

     

       Print ("Done");


     

  • Читать Закрыть 14

    Цикл for.

    Операторе цикла while используется, когда необходимо повторить некоторую последовательность действий несколько раз (или ни одного). Подобную задачу можно решить также с помощью другого оператора цикла — оператора for.

    Формат оператора цикла for:

     

       for ( выражение1; выражение2; выражение3 )
    оператор;


     

    Выполнение оператора цикла for начинается с вычисления «выражения1». Это своего рода инициализация цикла, которая выполняется только один раз и предшествует последующим действиям.

    После этого вычисляется «выражение2». Если оно истинно, то выполняется «оператор». Далее вычисляется «выражение3». Первая итерация цикла закончена. Вторая итерация начинается опять с вычисления «выражения2». Если оно истинно, то выполняется «оператор». Далее опять вычисляется «выражение3» и так продолжается до тех пор, пока «выражение2» будет истинным. Если же уже на первой итерации «выражение2» будет ложным, то цикл не исполнится ни разу.

    Если вникнуть в логику работы оператора цикла for, то можно заметить, что она полностью соответствует следующему фрагменту кода с использование оператора цикла while:

     

       выражение1;
    while ( выражение2 )
    {
    оператор;
    выражение3;
    }


     

    В прошлом выпуске мы рассмотрели пример использования оператора цикла while:

     

       int i = 0;
    while ( i < 9 )
    {
    Print( i );
    i++;
    }
    Print ("Done");


     

    Этот пример можно переписать с использованием оператора цикла for:

     

       int i;
    for (i = 0; i < 9; i++)
    Print( i );
    Print ("Done");


     

    Не будет ошибкой, если любое из трех или все три выражения в операторе цикла for будут отсутствовать, однако разделяющие их точки с запятыми (;) опускать нельзя. Если отсутствует «выражение2», то принимается, что оно всегда равно true.

    «Выражение1» и «выражение3» могут состоять из нескольких выражений, объединенных запятой. В этом случае вычисляется каждое из выражение, причем порядок вычисления будет слева направо:

     

       for (i = 0, j = 0; i < 9; i++)
    Print ( "i = ", i, " j= ", j );


     

  • Читать Закрыть 15

    Операторы Break и Сontinue.

    Досрочный выход из цикла с помощью оператора break

    Достаточно часто возникает потребность досрочно выйти из цикла — для этого используется оператор break.

    Оператор досрочного завершения цикла break

    Оператор break используется для досрочного выхода из ближайшего вложенного внешнего оператора цикла while или for. Также оператор break используется для выхода из оператора switch.

    Пример использования этого оператора:

     

       int i;
    for ( i = 0; i<10; i++ )
    {
    ...
    if ( i == 5 ) break;
    }

     

    В нашем примере цикл исполнится не 10 раз, а только 6, т.к. в тот момент, когда переменная i будет равна 5, в операторе if-else условие i=5 станет истинным и будет выполнен оператор break, т.к. произойдет досрочный выход из цикла.

    Досрочное завершение очередной итерации цикла с помощью оператора continue

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

    Оператор continue прерывает выполнение текущей итерации и передает управление на начало ближайшего внешнего оператора цикла for или while, запуская на исполнение следующую итерацию.

    Пример использования оператора continue:

     

       int i;
    for ( i = 0; i < 10; i++)
    {
    ...
    continue;
    Print ("Изучаем оператор continue"); // этот оператор никогда не будет выполнен
    }

     

  • Читать Закрыть 16

    Переключатель Switch.

    Достаточно часто возникают ситуации, когда нам надо заложить в эксперте не два, а больше варианта развития событий. Например, переменная i может принимать целочисленные значения от 1 до 5 и в зависимости от того, какое значение имеет эта переменная, возможны 5 различных последовательностей операторов. Такую задачу легко можно решить с использованием множества вложенных условных операторов if-else.

     

       if (i == 1)
    оператор1;
    else
    if (i == 2)
    оператор2;
    else
    if (i == 3)
    оператор3;
    else
    if (i == 4)
    оператор4;
    else
    if (i == 5)
    оператор5;


     

    Такой код будет работать, но его очень тяжело читать и воспринимать. Для большей наглядности в таких случаях лучше использовать оператор переключения switch.

    Формат оператора switch:

     

       switch(выражение)
    {
    case константа1: операторы1
    case константа2: операторы2
    ...
    default: операторы_default
    }


     

    Принцип работы этого оператора: сначала вычисляется значение «выражения», потом среди констант «константа1», «константа2» и др. ищется константа, которая равна результату «выражения». Если такая константа найдена, то оправление передается на соответствующую группу операторов. После этого последовательно выполняются все операторы этой группы и всех последующих, пока не будет выполнен последний оператор в операторе switсh или не будет осуществлен досрочный выход из оператора switсh с помощью оператора break. Если же не было найдено подходящей константы, то управление передается на группу операторов «операторы_default».

    Метка default и группа операторов «операторы_default» могут отсутствовать. В этом случае просто не будет выполняться никаких действий, если ни одна из констант не соответствует результату «выражения». Default не обязательно должен быть последним.

    Каждый вариант case может быть помечен целой константой, символьной константой или константным выражением.

    Теперь в качестве примере перепишем наш пример с переменной i и 5 вариантами действий с помощью оператора switch:

     

       switch (i)
    {
    case 1: оператор1; break;
    case 2: оператор2; break;
    case 3: оператор3; break;
    case 4: оператор4; break;
    case 5: оператор5; break;
    }


     

  • Читать Закрыть 17

    Учим эксперта торговать.

    Уверены, что Вам уже не терпиться научить Вашего эксперта торговать. Язык MetaQuotes Language 4 позволяет осуществлять ПОЛНЫЙ контроль над торговым счетом — открывать/закрывать позиции, размещать/модифицировать и удалять ордера, получать информацию об открытых позициях и уже закрытых сделках и многое-многое другое.

    В этом выпуске хотим привести полный список торговых функций, которые можно вызывать в эксперте, а в следующих выпусках подробно остановлюсь на каждой функции.

    Функция Краткое описание
    HistoryTotal Количество закрытых позиций и удаленных ордеров
    OrderClose Закрывает позицию
    OrderCloseBy Закрывает позицию другой позицией (локированной)
    OrderClosePrice Цена закрытия выбранного функцией OrderSelect() ордера/позиции
    OrderCloseTime Дата и время закрытия выбранного функцией OrderSelect() ордера/позиции
    OrderComment Комментарий выбранного функцией OrderSelect() ордера/позиции
    OrderCommission Комиссия выбранного функцией OrderSelect() ордера/позиции
    OrderDelete Удаляет отложенный ордер
    OrderExpiration Дата истечения выбранного функцией OrderSelect() ордера/позиции
    OrderLots Количество лотов в выбранном функцией OrderSelect() ордере/позиции
    OrderMagicNumber «Магическое» число выбранного функцией OrderSelect() ордера/позиции
    OrderModify Изменяет уровень и дату эксперации отложенного ордера, уровни Stop Loss и Take Profit и «магическое» число открытой позиции или отложенного ордера
    OrderOpenPrice Цена открытия выбранного функцией OrderSelect() ордера/позиции
    OrderOpenTime Дата и время размещения/открытия выбранного функцией OrderSelect() ордера/позиции
    OrderPrint Выводит параметры ордера в журнал
    OrderProfit Прибыль/убыток выбранного функцией OrderSelect() ордера/позиции
    OrderSelect Выбрать ордер для получения информации по нему
    OrderSend Открытие позиции или установка отложенного ордера
    OrderStopLoss Stop Loss выбранного функцией OrderSelect() ордера/позиции
    OrdersTotal Количество открытых позиций и отложенных ордеров
    OrderSwap Величина сториджа выбранного функцией OrderSelect() ордера/позиции
    OrderSymbol Аббревиатура инструмента выбранного функцией OrderSelect() ордера/позиции
    OrderTakeProfit Take Profit выбранного функцией OrderSelect() ордера/позиции
    OrderTicket Тикет выбранного функцией OrderSelect() ордера/позиции
    OrderType Тип выбранного функцией OrderSelect() ордера/позиции
  • Читать Закрыть 18

    функция OrderSelect.

    Формат функции OrderSelect():

     

       bool    OrderSelect ( int index, int select, int pool=MODE_TRADES )


     

    Как мы видим, функция возвращает значение логического типа: true — когда ордер или позиция были успешно выбраны, false — в случае какой-то ошибки. Код этой ошибки можно получить впоследствии с помощью функции GetLastError().

    Ордер/позицию можно выбрать двумя способами:

    • по номеру тикера;
    • по порядковому номеру ордера/позиции в списке.

    Если Вы знаете тикер ордера или позиции, то в качестве параметра select надо передать значение SELECT_BY_TICKET, а в качестве параметра index — номер тикера ордера или позиции. Третий параметр в этом случае не используется и его можно опустить.

    Пример выбора по тикеру:

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    Print("Ордер #77777 выбран успешно");
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     

    Если Вы не знаете тикера, то существует другой способ выбрать ордер или позицию. Для этого Вам надо выбрать ордер или позицию из списка открытых позиций или неудаленных отложенных ордеров (значение параметра pool равно MODE_TRADES) или из списка закрытых позиций или удаленных ордеров (параметр pool равен MODE_HISTORY). В обоих случаях параметр select должен быть равен SELECT_BY_POS. В параметре index Вы должны указать порядковый номер этой позиции или ордера в списке открытых позиций или неудаленных отложенных ордеров (pool равен MODE_TRADES) или в списке закрытых позиций и удаленных отложенных ордеров (pool равен MODE_HISTORY).

    Этот способ очень полезен, когда Вам нужно «пробежаться», например, по всем открытым позициям:

     

       int pos;
    int total = OrdersTotal();
    for ( pos = 0; pos

     

  • Читать Закрыть 19

    функция HistoryTotal.

    Зачастую, чтобы найти нужную нам позицию, приходится «пробежаться» по всему списку. Для этого нам нужно знать общее количество позиций и ордеров в списке закрытых позиций и удаленных отложенных ордеров. В этом нам поможет функция HistoryTotal().

    Формат функции HistoryTotal():

     

       int HistoryTotal ()


     

    Эта функция возвращает количество закрытых позиций или удаленных отложенных ордеров, которые загруженны в клиентском терминале MetaTrader 4 в соответствии с текущими настройками вкладки «История счета».

    Пример использования HistoryTotal():

     

       int pos;
    int total = HistoryTotal();
    for ( pos = 0; pos

     

  • Читать Закрыть 20

    функция OrderClosePrice.

    Формат функции OrderClosePrice():

     

       double OrderClosePrice()



     

    Функция OrderClosePrice() возвращает цену закрытия ордера или позиции, выделенной с помощью функции OrderSelect().

    Рассмотрим пример использования функции OrderClosePrice(). Предположим, что мы знаем, что позиция закрылась по ордеру и нам надо определить по какому: по Stop Loss или Take Profit:

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    // позиция выбрана успешно
    if ( OrderClosePrice() == OrderStopLoss() )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    // позиция закрылась по Take Profit
    // ...
    }
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     

    К сожалению, наш пример будет работать только в тех случаях, когда Stop Loss сработал без проскальзывания. В следующих выпусках мы усложним пример и сделаем так, чтобы даже если ордера были сработаны с проскальзыванием, наш кусочек кода правильно это определял.

  • Читать Закрыть 21

    функция OrderStopLoss.

    В этом примере была использована функция OrderStopLoss(). Из названия абсолютно понятно ее предназначение, но лучше все-таки остановиться на ней поподробнее.

    Для того, чтобы изучение функции OrderStopLoss() стало не просто скучным теоретическим описанием, попробуем слегка изменить прошлый пример, чтобы получить максимум практической пользы.

    Прошлый наш пример работал только в тех случаях, когда Stop Loss был сработан без проскальзывания. Изменим немного наш код:

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    // позиция выбрана успешно
    if ( ((OrderClosePrice() <= OrderStopLoss()) && (OrderType() == OP_BUY)) ||
    ((OrderClosePrice() >= OrderStopLoss()) && (OrderType() == OP_SELL)) )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    // позиция закрылась по Take Profit
    // ...
    }
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());

     


    Этот пример базируется на том, что:

    • Если позиция была на покупку (условие OrderType() == OP_BUY истинно), то Stop Loss сработает или по цене ордера, или хуже, т.е. ниже: (OrderClosePrice() <= OrderStopLoss()).
    • Если позиция была на продажу (условие OrderType() == OP_SELL истинно), то Stop Loss сработает или по цене ордера, или хуже, т.е. выше: (OrderClosePrice() >= OrderStopLoss()).
  • Читать Закрыть 22

    функция OrderType.

    Функция OrderType() возвращает значение, которое определяет тип операции ордера, который предварительно выбран с помощью OrderSelect():

    • OP_BUY — позиция на покупку,
    • OP_SELL — позиция на продажу,
    • OP_BUYLIMIT — отложенный ордер Buy Limit,
    • OP_BUYSTOP — отложенный ордер Buy Stop,
    • OP_SELLLIMIT — отложенный ордер Sell Limit,
    • OP_SELLSTOP — отложенный ордер Sell Stop.

    В предыдущем выпуске я поместил пример, которые определял, как была закрыта позиция — по Stop Loss ордеру или по Take Profit. Вместе с тем в этом примере есть небольшая ошибка: если выделенный ордер является отложенным ордером, то этот кусочек кода будет ошибочно считать, что позиция закрылась по Take Profit. Изменим наш пример, чтобы исключить эту ошибку:

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    if ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))
    {
    // позиция выбрана успешно
    if ( ((OrderClosePrice() <= OrderStopLoss()) && (OrderType() == OP_BUY)) ||
    ((OrderClosePrice() >= OrderStopLoss()) && (OrderType() == OP_SELL)) )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    // позиция закрылась по Take Profit
    // ...
    }
    }
    else
    Print("Ошибка: это не закрытая позиция, а отложенный ордер");
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     


    Путем добавления дополнительной проверки, нам удастся отделить закрытые позиции от отложенных ордеров:

     

       if ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))

     

  • Читать Закрыть 23

    функция OrderCloseTime.

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

     

       datetime OrderCloseTime()



     

    Функцию можно вызвать после того, как ордер или позиция были предварительно выбраны с помощью функции OrderSelect().

    Возвращает дату и время закрытия позиции или ордера:

    • Если позиция открыта, то возвращает 0 (ноль).
    • Если позиция закрыта, то возвращает дату и время закрытия позиции.
    • Если отложенный ордер удален, то возвращает дату и время удаления ордера.
    • Если отложенный ордер еще не удален, то возвращает 0 (ноль).

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

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    // это позиция, а не отложеный ордер?
    if ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))
    {
    // это закрытая позиция, а не открытая?
    if ( OrderCloseTime() != 0 )
    {
    if ( ((OrderClosePrice() <= OrderStopLoss()) && (OrderType() == OP_BUY)) ||
    ((OrderClosePrice() >= OrderStopLoss()) && (OrderType() == OP_SELL)) )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    // позиция закрылась по Take Profit
    // ...
    }
    }
    else
    Print("Ошибка: это не закрытая позиция, а открытая");
    }
    else
    Print("Ошибка: это не закрытая позиция, а отложенный ордер");
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     

    То, что это не может быть отложенный ордер, мы уже проверили (см. прошлый выпуск).

    Теперь наш пример по определению типа ордера, по которому была закрыта позиция, стал содержать на одну ошибку меньше, но по-прежнему остается небезгрешным. Мы почему-то с первых строчек кода безусловно верим в тот факт, что позиция закрыта именно по Stop Loss или Take Profit ордера. А если нет?

  • Читать Закрыть 24

    функция OrderTakeProfit.

    Изучим функцию OrderTakeProfit() и доработаем наш пример. Мы почему-то с первых строчек кода безусловно верим в тот факт, что позиция была закрыта именно по Stop Loss или Take Profit ордера. А если нет? Это легко проверить.

     

    • Если для позиции на покупку цена закрытия находится не ниже уровня Take Profit, то позиция закрыта по Take Profit.
    • Если для позиции на продажу цена закрытия находится не выше уровня Take Profit, то позиция закрыта по Take Profit.

     

    Мы знаем, что уровень Stop Loss ордера выделенной позиции или ордера можно определить с помощью функции OrderStopLoss(). По аналогии уровень Take Profit определяется с помощью функции OrderTakeProfit():

     

       double OrderTakeProfit()


     

    Ордер должен быть предварительно выбран с помощью функции OrderSelect().

    Изменим наш пример, чтобы убедиться, что позиция закрыта по ордеру, а не с рынка:

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    // это позиция, а не отложеный ордер?
    if ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))
    {
    // это закрытая позиция, а не открытая?
    if ( OrderCloseTime() != 0 )
    {
    if ( ((OrderClosePrice() <= OrderStopLoss()) && (OrderType() == OP_BUY)) ||
    ((OrderClosePrice() >= OrderStopLoss()) && (OrderType() == OP_SELL)) )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    if ( ((OrderClosePrice() >= OrderTakeProfit()) && (OrderType() == OP_BUY)) ||
    ((OrderClosePrice() <= OrderTakeProfit()) && (OrderType() == OP_SELL)) )
    {
    // позиция закрылась по Take Profit
    // ...
    }
    else
    Print("Ошибка: позиция закрыта не по ордеру");
    }
    }
    else
    Print("Ошибка: это не закрытая позиция, а открытая");
    }
    else
    Print("Ошибка: это не закрытая позиция, а отложенный ордер");
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     

  • Читать Закрыть 25

    функция OrderSymbol.

    Очень часто перед трейдером стоит задача перебрать все открытые позиции или отложенные ордера и что-то сделать с позициями или ордерами, которые выставлены по инструменту, к которому прикреплен советник. Предположим, что нас интересуют только отложенные ордера.

    Используя функцию OrderSymbol(), мы можем получить информация о выделенном с помощью функции OrderSelect() ордере или позиции.

     

       string OrderSymbol()


     

    Напишем кусочек кода, который просматривает все наши неудаленные отложенные ордера и производит какие-то действия с ордерами, которые выставлены по инструменту, к которому прикреплен советник.

     

       int pos;
    for (pos=0; pos=OP_BUYLIMIT)
    {
    // ордер оказался отложенным; проверим инструмент
    if (OrderSymbol()==Symbol())
    {
    // делаем что-то с ордером
    // ...
    }
    }
    }
    else
    Print("Ошибка ", GetLastError(), " при выборе ордера номер ", pos);


     

    В этом коде Вам могут быть непонятны два момента.

    Во-первых, я как-то странно на первый взгляд провожу определение типа ордера: if (OrderType()>=OP_BUYLIMIT). На самом деле условие OrderType()>=OP_BUYLIMIT будет истинно в тех случаях, когда выбран ордер, и ложно в тех случаях, когда выбрана позиция. Дело в том, что константы, определяющие тип ордера, имеют следующие числовые значения:

    Константа Значение Описание
    OP_BUY 0 Позиция на покупку
    OP_SELL 1 Продажа
    OP_BUYLIMIT 2 Отложенный ордер BUY LIMIT
    OP_SELLLIMIT 3 Отложенный ордер SELL LIMIT
    OP_BUYSTOP 4 Отложенный ордер BUY STOP
    OP_SELLSTOP 5 Отложенный ордер SELL STOP


    Как мы видим, во всех случаях, когда выделен отложенный ордер, значение функции OrderType() будет больше или равно OP_BUYLIMIT (т.е. больше или равно 2).

    Второй непонятный момент в коде — функция Symbol(). Эта функция возвращает текстовую строку с именем финансового инструмента, к которому прикреплен советник:

     

       string Symbol()


     

  • Читать Закрыть 26

    функция OrderComment.

    Существует еще один способ узнать была ли закрыта позиция по Stop Loss или Take Profit ордеру. При этом данный способ будет более простым, чем рассмотренные ранее.

    В этом нам поможет функция OrderComment().

     

       string OrderComment()


     

    После того как ордер или позиция выбраны функцией OrderSelect() вызов функции OrderComment() вернет комментарии этого ордера/позиции в виде строки.

    Очень часто сервер пишет в комментарий ордера или позиции полезные вещи:

     

    • [sl] — если сработал Stop Loss;
    • [tp] — если сработал Take Profit;
    • cancelled — если отложенный ордер был отменен клиентом;
    • deleted [no money] — если отложенный ордер должен был сработать, но был удален сервером из-за недостатка свободных средств на счете;
    • partial close — если позиция была закрыта частично;
    • close hedge by #тикер — если локированная позиция была закрыта полностью другой локированной позицией;
    • [число%] — позиция была закрыта по Stop Out (т.е. сервер автоматически закрыл позицию, когда equity стало меньше 20% необходимой маржи);
    • deleted [good for day] — удаленный отложенный ордер по CFD, который имел статус Day Order;
    • expiration [YYYY.MM.DD HH:MM] — отложенный ордер, который был удален сервером при достижении даты эксперации YYYY.MM.DD HH:MM;
    • split from #тикер — при закрытии одной позицией другой с разными объемами появляется в рынке новая позиция с таким комментарием; #тикер — номер тикера позиции с бОльшим объемом.

     

    Может быть этот список неполон, но в данный момент нас это не должно волновать. Нас интересуют те позиции и ордера, в комментарии которых встречается подстрока [sl] (позиция закрыта по Stop Loss ордеру) или подстрока [tp] (позиция закрыта по Take Profit ордеру):

     

       //---- выбираем ордер/позицию с тикером 77777
    if ( OrderSelect (77777, SELECT_BY_TICKET) == true)
    {
    // это позиция, а не отложеный ордер?
    if ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))
    {
    // это закрытая позиция, а не открытая?
    if ( OrderCloseTime() != 0 )
    {
    if ( StringFind(OrderComment(), "[sl]") != -1 )
    {
    // позиция закрылась по Stop Loss
    // ...
    }
    else
    {
    if ( StringFind(OrderComment(), "[tp]") != -1 )
    {
    // позиция закрылась по Take Profit
    // ...
    }
    else
    Print("Ошибка: позиция закрыта не по ордеру");
    }
    }
    else
    Print("Ошибка: это не закрытая позиция, а открытая");
    }
    else
    Print("Ошибка: это не закрытая позиция, а отложенный ордер");
    }
    else
    Print("OrderSelect() вернул ошибку - ",GetLastError());


     

    В этом примере есть неизвестная пока нам функция — StringFind():

     

       int StringFind ( string text, string matched_text, int start=0 )


     

    Эта функция ищет в строке text подстроку matched_text начиная с позиции start (если третий параметр не указан, то поиск идет с нулевой позиции, т.е. с первого элемента). Если подстрока не найдена, то функция возвращает -1; если найдена — номер позиции в строке, с которой начинается искомая подстрока.

  • Читать Закрыть 27

    функция OrderSwap.

    В этом выпуске расскажем о функции OrderSwap(). Честно говоря, совершенно не понятно практического применения этой функции в экспертах, но раз она присутствует, то лучше о ней рассказать.

     

       double OrderSwap()


     


    Функция OrderSwap() возвращает начисленный или списанный сторидж (т.е. плату за перенос позиции на следующий день) по ордеру или позиции, выделенных с помощью функции OrderSelect().

  • Читать Закрыть 28

    функция OrderCommission.

    К сожалению, при всей моей достаточно буйной фантазии я так и не смог придумать практического применения этих функций. Но раз эти функции присутствуют, то лучше о них расcказать. Вдруг вам они все-таки пригодятся.

       double OrderCommission()


    Функция OrderCommission() возвращает удержанную комиссию по ордеру, выделенному функцией OrderSelect().

  • Читать Закрыть 29

    функция OrderOpenPrice.

    В этом выпуске я рассмотрю функцию OrderOpenPrice(). В качестве практического примера применения этой функции рассмотрим установку и управление трейлинг стопом.

    Напоминаем, что Trailing Stop (трейлинг стоп) — это алгоритм управления уровнем Stop Loss ордера. После выставления трейлинг-стопа (например, на Х пипсов) происходит следующее:

    • MetaTrader не предпринимает никаких действий до того момента, пока по открытой позиции не образуется прибыль в Х пипсов. После этого MetaTrader выставляет Stop Loss ордер на расстоянии Х пипсов от текущей цены (в данном случае — на уровне безубыточности).
    • После выполнения первого шага MetaTrader посылает команду на изменение уровня Stop Loss ордера на расстояние Х пипсов от текущей котировки каждый раз, когда расстояние между ней и старым уровнем ордера превысит Х пипсов. В результате этого Stop Loss ордер «подтягивается» к текущей цене.

    Реализуем этот принцип на языке MetaQuotes Language 4. Будем полагать, что открытая позиция уже выбрана и мы точно знаем, что эта позиция открыта по инструменту, к которому прикреплен эксперт. Также предположим, что значение трейлинг стопа в пипсах содержится в переменной TrailingStop.

     

       int err;
    if (OrderType() == OP_BUY)
    {
    // позиция на покупку
    if ((Bid-OrderOpenPrice())>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Bid-TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Bid-TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }
    else
    {
    // позиция на продажу
    if ((OrderOpenPrice()-Ask)>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Ask+TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Ask+TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }


     

    Прежде всего нас интересует новая функция OrderOpenPrice():

     

       double OrderOpenPrice()


     

    Эта функция возвращает цену открытия для выбранного функцией OrderSelect() ордера или позиции.

    В этом коде Вам могут быть непонятны еще несколько функций:

    • OrderTicket() — возвращает тикер выделенного функцией OrderSelect() ордера или позиции;
    • Bid — Bid последней котировки по инструменту, к которому прикреплен эксперт;
    • Ask — Ask последней котировки по инструменту, к которому прикреплен советник;
    • Point — размер пункта текущего инструмента в валюте котировки (для EURUSD это 0.0001, для USDJPY- 0.01 и т.д.).
  • Читать Закрыть 30

    функция OrderTicket.

    Эта функция возвращает тикер выделенного с помощью функции OrderSelect() ордера или позиции.

     

       int err;
    if (OrderType() == OP_BUY)
    {
    // позиция на покупку
    if ((Bid-OrderOpenPrice())>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Bid-TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Bid-TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }
    else
    {
    // позиция на продажу
    if ((OrderOpenPrice()-Ask)>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Ask+TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Ask+TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }


     

    В этом примере нами была использована функция OrderTicket():

     

       int OrderTicket()


     

  • Читать Закрыть 31

    функция OrderProfit.

    Функция OrderProfit() возвращает прибыль для ордера, выделенного с помощью функции OrderSelect().

    Предположим, что мы хотим подсчитать прибыль, которую мы получили по закрытым позициям.

     

       int profit = 0;
    int pos;
    for ( pos = 0; pos<= OP_SELL) profit += OrderProfit();
    }
    else
    Print("Ошибка ", GetLastError(), " при выделении ордера ", OrderTicket());
    }
    Print("Суммарный профит по закрытым позициям = ", profit);


     

  • Читать Закрыть 32

    функция OrderOpenTime.

    С помощью функции OrderOpenTime() мы можем получить дату и время открытия ордера или позиции, выделенного предварительно с помощью функции OrderSelect().

       datetime OrderOpenTime()



    • В случае отложенного ордера функция OrderOpenTime() возвращает дату и время выставления ордера.
    • В случае открытой или закрытой позиции функция возвращает дату и время открытия позиции.

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

    В этом нам помогут четыре новых функции:

    • datetime CurTime() — время прихода последней котировки в виде количества секунд, прошедших после 00:00 1 января 1970 года;
    • int TimeHour(datetime time) — возвращает порядковый номер часа для времени time;
    • int TimeMinute(datetime time) — возвращает порядковый номер минуты для времени time;
    • int TimeSeconds(datetime time) — возвращает количество секунд с начала минуты для времени time.

    Например, для 02:16:05 функции TimeHour, TimeMinute и TimeSeconds вернут соответственно 2, 16 и 5.

    Мы помним, что значение типа datetime представляет собой количество секунд, прошедшее с 00:00 1 января 1970 года. Также мы помним, что в часе 60×60 секунд, а в минуте 60 секунд. Поэтому, зная текущее время (мы можем получить его с помощью CutTime), мы можем получить время текущего дня на 00:00:00 часов:

     

       int c_time = CurTime();   
    datetime day_start;
    day_start=c_time-TimeHour(c_time)*60*60-TimeMinute(c_time)*60-TimeSeconds(c_time);


     

    А полный код, который подсчитывает прибыль по всем закрытым позициям, которые открыты сегодня, будет таким:

     

       //---- вычислим начало дня - переменная day_start
    int c_time = CurTime();
    datetime day_start;
    day_start=c_time-TimeHour(c_time)*60*60-TimeMinute(c_time)*60-TimeSeconds(c_time);
    //---- подсчитаем прибыль
    int profit = 0;
    int pos;
    for ( pos = 0; pos<= OP_SELL)
    {
    // ордер был открыт сегодня?
    if (OrderOpenTime()>=day_start) profit += OrderProfit();
    }
    }
    else
    Print("Ошибка ", GetLastError(), " при выделении ордера ", OrderTicket());
    }
    Print("Суммарный профит по закрытым позициям = ", profit);


     

  • Читать Закрыть 33

    функция OrderMagicNumber.

    Очень часто трейдеру бывает нужно как-то идентифицировать ордера или позиции. Например, полезно знать, открыт ли данный ордер именно этим экспертом или на каком периоде графика сработал торговый сигнал. Разработчики MetaTrader 4 предусмотрели для таких случаев очень полезную торговую функцию — OrderMagicNumber().

     

       int OrderMagicNumber()



     

    Функция OrderMagicNumber() возвращает целое число, которое было установлено для этого ордера функцией OrderSend() в параметре magic.

    Немного забегая вперед, расскажу о формате функции OrderSend():

     

       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)


     

    Функция OrderSend() предназначена для размещения отложенного ордера или открытия позиции. В следующем выпуске я расскажу о ней подробнее.

    Как я уже говорил ранее, магическое число для ордера или позиции передается в функцию через параметр magic.

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

    Для этих целей нам подойдет следующий формат магического числа: последние 2 цифры числа — ID эсперта, первые цифры — период графика.

    В языке MetaQuotes Language существуют следующие константы, которые обозначают периоды графиков:

    Константа Значение Описание
    PERIOD_M1 1 1 минута
    PERIOD_M5 5 5 минут
    PERIOD_M15 15 15 минут
    PERIOD_M30 30 30 минут
    PERIOD_H1 60 1 час
    PERIOD_H4 240 4 часа
    PERIOD_D1 1440 1 день
    PERIOD_W1 10080 1 неделя
    PERIOD_MN1 43200 1 месяц
    0 (ноль) 0 Период текущего графика

    Табл. 1. Периоды графиков

    Исходя из этого мы можем описать 3 функции:

     

       // получить магическое число из периода графика и ID эксперта
    int GetMagicNumber( int chart_period, int expertID )
    {
    return( chart_period * 100 + expertID );
    }

    // получить период графика из магического числа
    int GetPeriod( int magic )
    {
    return( magic / 100 );
    }

    // получить ID эксперта из магического числа
    int GetExpertID( int magic )
    {
    return( magic % 100 );
    }


     

  • Читать Закрыть 34

    функция OrderSend.

    Формат функции:

     

       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)


     

    Функция OrderSend() предназначена для размещения отложенного ордера или открытия позиции. Функция вовзращает номер тикера открытой позиции или размещенного отложенного ордера. В случае неудачи функция OrderSend() возвращает число -1.

    При этом:

    • symbol — инструмент, по которому будет открыта позиция или размещен отложенный ордер;
    • cmd — тип ордера (см. таблицу 1);
    • volume — объем в лотах;
    • price — цена открытия позиции или уровень отложенного ордера;
    • slippage — максимально допустимое отклонение между price и ценой сервера, при которым позиция будет открыта (для установки отложенных ордеров величина параметра slippage значения не имеет);
    • stoploss — уровень Stop Loss;
    • takeprofit — уровень Take Profit;
    • comment — комментарий к ордеру или позиции (впоследствии это поле может быть изменено сервером — см. выпуск «OrderComment() — комментарий выделенного ордера»);
    • magic — магическое число ордера (может быть впоследствии получено функцией OrderMagicNumber());
    • expiration — дата и время истечения отложенного ордера (если к этой дате и времени отложенный ордер не сработает, то он будет удален — см. выпуск «OrderExpiration() — дата истечения отложенного ордера»);
    • arrow_color — цвет открывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то открывающая стрелка не отображается на графике.

    В параметре cmd мы передаем тип приказа:

    Таблица 1. Возможные значения параметра cmd функции OrderSend()

    Константа Значение Описание
    OP_BUY 0 Открыть позицию на покупку
    OP_SELL 1 Открыть позицию на продажу
    OP_BUYLIMIT 2 Разместить отложенный ордер BUY LIMIT
    OP_SELLLIMIT 3 Разместить отложенный ордер SELL LIMIT
    OP_BUYSTOP 4 Разместить отложенный ордер BUY STOP
    OP_SELLSTOP 5 Разместить отложенный ордер SELL STOP

    Таким образом, для того, чтобы открыть позицию на продажу в качестве параметра cmd надо указать OP_SELL. Для открытия позиции на покупку — OP_BUY. Для установки отложенного ордера надо использовать значения OP_BUYLIMIT, OP_SELLLIMIT, OP_BUYSTOP или OP_SELLSTOP в зависимости от типа размещаемого отложенного ордера.

    При открытии позиции в качестве параметра price надо использовать текущий Bid (если cmd равен OP_SELL) или текущий Ask (если cmd равен OP_BUY):

    • функция Bid возвращает текущий Bid по инструменту, к которому «прикреплен» эксперт;
    • функция Ask возвращает текущий Ask по инструменту, к которому «прикреплен» эксперт;
    • функция MarketInfo(string symbol, int type) c параметром type, равным MODE_BID или MODE_ASK, возвращает текущий Bid или Ask по инструменту, который передан ей в качестве параметра symbol.

     


    Важно:
    Ни в коем случае нельзя использовать цену, которую Вы рассчитали по какой-то формуле, или цену, которую Вы не привели (нормализовали) к тому количеству знаков после запятой, сколько должно быть у данного инструмента.

     

    Для того, чтобы «нормализовать» цену, надо использовать функцию NormalizeDouble():

     

       double NormalizeDouble ( double value, int digits )


     

    Эта функция округляет вещественное число value с точностью до digits знаков после запятой. Число цифр после десятичной точки должно быть в диапазоне 0 .. 8.

     

    В случае использования «неправильной» цены в параметре price будут выданы следующие коды ошибок:

    • ERR_INVALID_PRICE (129) — если цена не была «нормализована» или такой цены вообще не было в потоке;
    • ERR_REQUOTE (138) — если цена сильно устарела (независимо от значения параметра slippage);

     

    Если же цена устарела, но еще присутствует в потоке, то будет совершена сделка по текущей цене, если текущая цена находится в диапазоне price +/- slippage.

    Когда Вы открываете позициию (выставляете отложенный ордер), то ордера Stop Loss и Take Profit должны находиться по отношению к текущей цене (цене отложенного ордера) не ближе, чем на расстоянии определенного количества пунктов. Например, если Вы имеет счет в Компании Альпари, то Вы не можете выставлять Stop Loss и Take Profit ордера на FOREX ближе одного спрэда к текущей цене для открытой позиции или к цене отложенного ордера.

    Если Вы попытаетесь разместить Stop Loss или Take Profit ближе, чем это дозволено, то функция OrderSend() вернет ошибку 130 (ERR_INVALID_STOPS).

    Если Вы не знаете этого минимально допустимого значения, то Вы всегда можете его получить с помощью функции MarketInfo(). Функция MarketInfo(string symbol, int type) c параметром type, равным MODE_STOPLEVEL, возвращает это значение по инструменту, который передан ей в качестве параметра symbol.

    Инструмент графика, к которому прикреплен эксперт, можно получить с помощью функции Symbol():

     

       string Symbol()


     

    Если функция OrderSend() возратила ошибку 147 (ERR_TRADE_EXPIRATION_DENIED), это означает, что на торговом сервере запрещены отложенные ордера с установленной датой эксперации. В этом случае следует в дальнейшем использовать функцию OrderSend() с параметром expiration, равным нулю. Компания Альпари разрешает своим клиентам устанавливать любую дату и время, когда неисполненный отложенный ордер будет удален автоматически.

    Если функция OrderSend() вернула ошибку 148 (ERR_TRADE_TOO_MANY_ORDERS), это означает, что на торговом сервере установлено ограничение на максимально возможное число открытых позиций и выставленных отложенных ордеров по одному счету. Пытаясь открыть еще одну позицию или выставить еще один отложенный ордер, Вы превышаете допустимый лимит, поэтому Вам в этом будет отказано.

    Пример использования функции OrderSend() можно найти в нашем первом эксперте:

     

       MyOrderTicket = OrderSend(Symbol(), OP_SELL, LotsNumber, Bid, 3, 0, 0, 
    NULL, 0, 0, CLR_NONE);
    if (MyOrderTicket<0)
    {
    err = GetLastError();
    Print("Ошибка при открытии позиции: ", err);
    MyOrderTicket = 0;
    }


     

    Пример советника

    Задача.

    Советник должен в определенное время (параметры MyHour и MyMinute) на заранее заданном расстоянии от текущей цены (параметр MyPendingLevel) выставлять два ордера — Sell Stop и Buy Stop. При этом должны выставляться ордера Stop Loss на расстоянии MySL пипсов от цены ордера и Take Profit на расстоянии MyTP пипсов.

    Решение.

     

       //+------------------------------------------------------------------+
    //| 2nd Expert.mq4 |
    //| Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"

    //---- input parameters
    extern int MyPendingLevel=15;
    extern int MySL=30;
    extern int MyTP=15;
    extern int MyHour=19;
    extern int MyMinute=40;
    extern int MyLots=1.0;

    datetime LastTradeTime; // время последней торговой операции

    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----
    // установим время последней торговой операции вчерашним днем
    LastTradeTime = CurTime()-24*60*60;
    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----
    // проверим, не выставляли ли мы ордер уже сегодня
    // если выставляли - выходим
    if (TimeDayOfYear(CurTime())==TimeDayOfYear(LastTradeTime))
    return(0);

    // проверим, не наступило ли время выставить ордер
    if ((TimeHour(CurTime())==MyHour)&&(TimeMinute(CurTime())==MyMinute))
    {
    // выставим Buy Stop
    if (OrderSend(Symbol(), OP_BUYSTOP, MyLots,
    Ask+Point*MyPendingLevel, 0, Ask+Point*MyPendingLevel-Point*MySL,
    Ask+Point*MyPendingLevel+Point*MyTP)!=-1)
    LastTradeTime = CurTime();
    // ордер не выставлен
    else
    return(0);

    // обязательная пауза в 10 секунд
    Sleep(10000);

    // обновим текущий Bid и Ask
    RefreshRates();


    // выставим Sell Stop
    if (OrderSend(Symbol(), OP_SELLSTOP, MyLots,
    Bid-Point*MyPendingLevel, 0, Bid-Point*MyPendingLevel+Point*MySL,
    Bid-Point*MyPendingLevel-Point*MyTP)!=-1)
    LastTradeTime = CurTime();
    // ордер не выставлен
    else
    return(0);
    }
    //----
    return(0);
    }
    //+------------------------------------------------------------------+


     

    Прежде всего этот эксперт имеет несколько параметров:

     

       extern int       MyPendingLevel=15;
    extern int MySL=30;
    extern int MyTP=15;
    extern int MyHour=19;
    extern int MyMinute=40;
    extern int MyLots=1.0;


     

    Эти параметры имеют следующих смысл:

    • MyPendingLevel — расстояние в пипсах от текущей цены, на котором выставляется отложенный ордер;
    • MySL и MyTP — Stop Loss и Tale Profit в пипсах от цены отложенного ордера;
    • MyHour и MyMinute — час и минута, когда выставляет отложенный ордер;
    • MyLots — размер лота отложенного ордера.

    Напомню, что о том, как описать в коде параметры эксперта, я рассказывал в выпуске «Внешние переменные».

    В реальности может возникнуть ситуация, когда на баре MyHour:MyMinute может быть несколько тиков, поэтому чтобы избежать выставления отложенных ордеров на каждом тике, мы завели глобальную переменную LastTradeTime:

     

      datetime LastTradeTime; // время последней торговой операции


     

    Этой переменной мы присваиваем в качестве начального значения вчерашнюю дату при инициализации эксперта — в функции init():

     

      //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----
    // установим время последней торговой операции вчерашним днем
    LastTradeTime = CurTime()-24*60*60;
    //----
    return(0);
    }


     

    На каждом тике вызывается функция start(), в которой мы сначала проверяем, не выставляли ли мы уже сегодня отложенные ордера:

     

       // проверим, не выставляли ли мы ордер уже сегодня
    // если выставляли - выходим
    if (TimeDayOfYear(CurTime())==TimeDayOfYear(LastTradeTime))
    return(0);


     

    Здесь используется неизвестная нам пока функция TimeDayOfYear():

     

       int TimeDayOfYear( datetime date )


     

    Эта функция возвращает порядковый номер дня (с начала года): 1 — 1 января, … , 365 или 366 — 31 декабря.

    Если же порядковый номер дня последней торговой операции — TimeDayOfYear(LastTradeTime) — равен порядковому дню текущего времени — TimeDayOfYear(CurTime()), — значит мы уже сегодня ордера выставляли, поэтому выходим из эксперта: return(0).

    Теперь проверим, не наступило ли время выставить отложенный ордер:

     

       // проверим, не наступило ли время выставить ордер
    if ((TimeHour(CurTime())==MyHour)&&(TimeMinute(CurTime())==MyMinute))
    {
    ...
    }



     

    Расскажу о двух незнакомых функциях в этом участке кода:

    • int TimeHour(datetime time) — возвращает час для времени time: 0..23;
    • int TimeMinute(datetime time) — возвращает минуту для времени time: 0..59.

    Если уже пора выставить отложенный ордер, то сначала с помощью функции OrderSend() размещаем ордер Buy Stop.

    Потом выжидаем паузу в 10 секунд с помощью функции Sleep():

     

       void Sleep(int milliseconds)


     

    Эта функция делает паузу в работе эксперта на milliseconds милисекунд (1 секунда = 1000 милисекунд).

    За эти 10 секунд текущий бид и аск могли измениться, поэтому мы обновляем их с помощью функции RefreshRates().

    Далее мы выставляем отложенный ордер Sell Stop.

    Вот мы и дошли до конца нашего эксперта. Хочу заметить, что этот эксперт может пропустить момент, когда надо было выставить отложенный ордер, если в этом баре не было котировок и функция start() не вызывалась. Думаю, что у моего читателя уже достаточно собственных знаний, чтобы самостоятельно подправить этот недостаток эксперта.

  • Читать Закрыть 35

    функция OrderModify.

    Функция OrderModify().

       bool OrderModify(int ticket, double price, double stoploss, double takeprofit, 
    datetime expiration, color arrow_color=CLR_NONE)


    Эта функция позволяет изменять параметры отложенных ордеров и открытых позиций. В случае успеха она возвращает true, а в случае неудачи — false. Код ошибки можно будет получить с помощью функции GetLastError().

    Параметры функции:

    • ticket — тикер отложенного ордера или открытой позиции;
    • price — новый уровень отложенного ордера (для открытых позиций не может быть изменен);
    • stoploss — новый уровень Stop Loss;
    • takeprofit — новый уровень Take Profit;
    • expiration — новые дата и время истечения отложенного ордера (если к этой дате и времени отложенный ордер не сработает, то он будет удален — см. выпуск «OrderExpiration() — дата истечения отложенного ордера»);
    • arrow_color — цвет открывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то открывающая стрелка не отображается на графике.

    В случае неправильных параметров функция возвращает следующие коды ошибок:

    • 1 (ERR_NO_RESULT) — если ни один из параметров не был изменен;
    • 147 (ERR_TRADE_EXPIRATION_DENIED) — если в настройках торгового сервера запрещено выставлять дату эксперации ордера. В этом случае параметр expiration всегда должен быть равен нулю).

    В качестве практического примера применения этой функции рассмотрим установку и управление трейлинг стопом.

    Напомню, что Trailing Stop (трейлинг стоп) — это алгоритм управления уровнем Stop Loss ордера. После выставления трейлинг-стопа (например, на Х пипсов) происходит следующее:

    • MetaTrader не предпринимает никаких действий до того момента, пока по открытой позиции не образуется прибыль в Х пипсов. После этого MetaTrader выставляет Stop Loss ордер на расстоянии Х пипсов от текущей цены (в данном случае — на уровне безубыточности).
    • После выполнения первого шага MetaTrader посылает команду на изменение уровня Stop Loss ордера на расстояние Х пипсов от текущей котировки каждый раз, когда расстояние между ней и старым уровнем ордера превысит Х пипсов. В результате этого Stop Loss ордер «подтягивается» к текущей цене.

    Реализуем этот принцип на языке MetaQuotes Language 4. Будем полагать, что открытая позиция уже выбрана и мы точно знаем, что эта позиция открыта по инструменту, к которому прикреплен эксперт. Также предположим, что значение трейлинг стопа в пипсах содержится в переменной TrailingStop.

     

       int err;
    if (OrderType() == OP_BUY)
    {
    // позиция на покупку
    if ((Bid-OrderOpenPrice())>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Bid-TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Bid-TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }
    else
    {
    // позиция на продажу
    if ((OrderOpenPrice()-Ask)>=(TrailingStop*Point))
    {
    // выставляем Stop Loss
    if (OrderModify(OrderTicket(), OrderOpenPrice(), Ask+TrailingStop*Point,
    OrderTakeProfit(), 0))
    Print("#", OrderTicket(),": trailing stop ", Ask+TrailingStop*Point);
    else
    {
    err = GetLastError();
    Print("#", OrderTicket(),": trailing stop error ", err);
    }
    }
    }

     

  • Читать Закрыть 36

    функция OrderDelete.

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

    В этом выпуске расскажем о том, как можно удалить отложенный ордер. Это можно сделать с помощью функции OrderDelete().

     

       bool OrderDelete(int ticket)

     


    Эта функция удаляет отложенный ордер с тикером ticket. Функция возвращает true в случае успешного удаления и false — в случае возникновения ошибки. Код ошибки можно получить, вызвав функцию GetLastError().

    Применение функции OrderDelete() рассмотрим на следующем примере:

    Предположим, что наш эксперт по какой-то логике (не будем здесь вдаваться в подробности) выставляет по одному инструменту сразу два отложенных ордера — Buy Limit и Sell Limit. Наша задача — если один из ордеров сработал, то удалить оставшийся отложенный ордер.

    Предположим, что по данному инструменту работает только наш эксперт.

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

     

       // просмотрим все открытые позиции и выставленные ордера
    int pos;
    for(pos=0; pos<=OP_SELL))
    {
    // найти второй отложенный ордер
    int i;
    for(i=0; iOP_SELL))
    {
    // ордер найден - удалим его
    if (OrderDelete(OrderTicket()))
    {
    Print("Ордер удален");
    }
    else
    {
    Print("Ошибка ", GetLastError(), " при удалении ордера");
    }
    }
    }
    }
    // выйти из цикла
    break;
    }
    }
    }

     

  • Читать Закрыть 37

    функция OrderClose.

    Пришло время рассказать о том, как закрыть открытую позицию с помощью функции OrderClose().

     

       bool OrderClose(int ticket, double lots, double price, int slippage, 
    color Color=CLR_NONE)



     

    Параметры функции:

    • ticket — тикер открытой позиции;
    • lots — объем в лотах;
    • price — цена закрытия позиции;
    • slippage — максимально допустимое отклонение между price и ценой сервера, при котором позиция будет закрыта;
    • arrow_color — цвет закрывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка не отображается на графике.

    Функция OrderClose() вернет true, если позиция закрыта успешно. Если же произошла какая-то ошибка, то функция вернет false и код ошибки можно будет получить с помощью функции GetLastError().

    Практических примеров применения этой функции можно привести бесчисленное количество, т.к. в большинстве экспертов существует потребность закрывать позиции не только по Stop Loss или Take Profit ордеру, но и по текущей цене.

    Мы же в качестве примера применения функции OrderClose() рассмотрим эксперт, который закрывает все открытые позиции и удаляет все отложенные ордера в пятницу после 22:00 (по времени торговой платформы).

     

       //+------------------------------------------------------------------+
    //| Close everything on Friday.mq4 |
    //| Copyright © 2006, Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2006, Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"

    //---- input parameters
    extern int MyHour=22;
    extern int MyMinute=00;


    // 5 - Friday
    #define MyDay 5

    datetime LastTradeTime;

    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----
    // установим время последней торговой операции вчерашним днем
    LastTradeTime = CurTime()-24*60*60;
    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----

    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----
    // проверим, не закрывали ли мы все уже сегодня
    // если да - выходим
    if (TimeDayOfYear(CurTime())==TimeDayOfYear(LastTradeTime)) return(0);

    // если не пятница - выходим
    if (DayOfWeek()!=MyDay) return(0);

    // проверим, не наступило ли время закрывать позиции
    if (((TimeHour(CurTime())==MyHour)&&(TimeMinute(CurTime())>=MyMinute))
    ||(TimeHour(CurTime())>MyHour))
    {
    // закроем все позиции и удалим ордера
    while (OrdersTotal()>0)
    {
    // выделим первую позицию или ордер в списке
    if (!OrderSelect(0, SELECT_BY_POS)) break; // в случае неудачи выйти из цикла


    // если отложенный ордер, то удалить
    if (OrderType()>OP_SELL)
    {
    if (!OrderDelete(OrderTicket()))
    {
    Print("Ошибка ", GetLastError()," при удалении отложенного ордера ",
    OrderTicket());
    break;
    }
    }
    // если открытая позиция, то закрыть
    else
    {
    double price;
    if (OrderType()==OP_SELL)
    price = MarketInfo(OrderSymbol(), MODE_ASK);
    else
    price = MarketInfo(OrderSymbol(), MODE_BID);


    if (!OrderClose(OrderTicket(), OrderLots(), price, 3))
    {
    Print("Ошибка ", GetLastError()," при закрытии позиции ", OrderTicket());
    break;
    }
    }

    // пауза 10 секунд
    Sleep(10000);
    }
    if (OrdersTotal()==0) LastTradeTime = CurTime();
    }

    //----
    return(0);
    }
    //+------------------------------------------------------------------+


     

    Разоберем каждую строчку кода этого советника.

    Прежде всего этот эксперт имеет несколько параметров:

     

         extern int       MyHour=22;
    extern int MyMinute=00;


     

    Эти параметры имеют следующих смысл:

    MyHour и MyMinute — час и минута, когда эксперт закрывает открытые позиции и удаляет отложенные ордера.

    Напомню, что о том, как описать в коде параметры эксперта, я рассказывал в выпуске «Внешние переменные».

    Мы объявили глобальную переменную LastTradeTime:

     

       datetime LastTradeTime;


     

    С ее помощью мы сможем избежать «сработки» эксперта на каждом тике после 22:00, если в этот день мы уже закрыли все открытые позиции и удалили отложенные ордера.

    При инициализации эксперта в функции init() мы присваиваем этой переменной значение вчерашнего дня:

     

       int init()
    {
    //----
    // установим время последней торговой операции вчерашним днем
    LastTradeTime = CurTime()-24*60*60;
    //----
    return(0);
    }


     

    Напомню, что функция CurTime() возвращает текущее время в формате datetime.

    На каждом тике вызывается функция start(), в которой мы сначала проверяем, не закрывали ли мы уже сегодня открытые позиции и не удаляли ли мы уже отложенные ордера:

     

       if (TimeDayOfYear(CurTime())==TimeDayOfYear(LastTradeTime)) return(0);


     

    Здесь используется функция TimeDayOfYear():

     

       int TimeDayOfYear( datetime date )


     

    Эта функция возвращает порядковый номер дня (с начала года): 1 — 1 января, … , 365 или 366 — 31 декабря.

    Если же порядковый номер дня последней торговой операции — TimeDayOfYear(LastTradeTime) — равен порядковому дню текущего времени — TimeDayOfYear(CurTime()), — значит мы уже сегодня ордера удаляли и позиции закрывали, поэтому выходим из эксперта: return(0).

    Проверим, не пятница ли сегодня:

     

       if (DayOfWeek()!=MyDay) return(0);


     

    Функция DayOfWeek() возвращает порядковый номер дня недели (воскресенье — 0, 1 — понедельник, …, 6 — суббота) последнего известного времени сервера.

    Теперь проверим, не наступило ли время закрывать позиции:

     

         if (((TimeHour(CurTime())==MyHour)&&(TimeMinute(CurTime())>=MyMinute))
    ||(TimeHour(CurTime())>MyHour))
    {
    ...
    }


     

    В этом кусочке кода я использовал две новые функции:

    • int TimeHour(datetime time) — возвращает час для времени time: 0..23;
    • int TimeMinute(datetime time) — возвращает минуту для времени time: 0..59.

    Далее с помощью функции OrderSelect() мы начинаем выделять первую позицию/ордер в списке, пытаться ее закрыть или удалить ордер, а потом с помощью цикла while этот процесс повторяется вновь и вновь, пока есть открытые позиции или отложенные ордера. Также цикл прерывается в случае ошибки.

    Обязательно между торговыми операциями мы выдерживаем паузу в 10 секунд:

     

       Sleep(10000);


     

    Функция Sleep(int milliseconds) делает паузу в работе эксперта на milliseconds милисекунд (1 секунда = 1000 милисекунд).

    После того, как все позиции закрыты и все отложенные ордера удалены, мы задаем время последней «сработки» советника текущим временем:

     

       if (OrdersTotal()==0) LastTradeTime = CurTime(); 


     

    В коде эксперта Вы видите еще одну незнакомую нам функцию — MarketInfo():

     

       double MarketInfo(string symbol, int type)


     

    С помощью этой функции можно получить различную информацию об инструменте symbol. Вид получаемой информации зависит от значения параметра type:

    Константа Значение Описание
    MODE_LOW 1 Минимальная цена за день
    MODE_HIGH 2 Максимальная цена за день
    MODE_TIME 5 Время поступления последней котировки
    MODE_BID 9 Последний Bid
    MODE_ASK 10 Последний Ask
    MODE_POINT 11 Размер пункта в валюте котировки
    MODE_DIGITS 12 Количество цифр после десятичного точки в цене инструмента
    MODE_SPREAD 13 Спрэд в пунктах
    MODE_STOPLEVEL 14 Минимально допустимый уровень стоп-лосса/тейк-профита в пунктах
    MODE_LOTSIZE 15 Размер контракта в базовой валюте инструмента
    MODE_TICKVALUE 16 Размер минимального изменения цены инструмента в валюте котировки
    MODE_TICKSIZE 17 Минимальный шаг изменения цены инструмента в пунктах
    MODE_SWAPLONG 18 Сторидж для длинных позиций
    MODE_SWAPSHORT 19 Сторидж для коротких позиций
    MODE_STARTING 20 Дата начала торгов по инструменту (для фьючерсов)
    MODE_EXPIRATION 21 Дата окончания торгов по инструменту (для фьючерсов)
    MODE_TRADEALLOWED 22 Флаг разрешения торгов по данному инструменту
    MODE_MINLOT 23 Минимальный размер лота
    MODE_LOTSTEP 24 Шаг изменения размера лота
    MODE_MAXLOT 25 Максимальный размер лота
  • Читать Закрыть 38

    функция OrderCloseBy.

    Давайте перепишем нашего эксперта с учетом этого совета. В этом нам поможет функция OrderCloseBy():

     

       //+------------------------------------------------------------------+
    //| Close everything on Friday.mq4 |
    //| Copyright © 2006, Andrey Vedikhin |
    //| http://www.vedikhin.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright © 2006, Andrey Vedikhin"
    #property link "http://www.vedikhin.ru"


    //---- input parameters
    extern int MyHour=22;
    extern int MyMinute=00;

    // 5 - Friday
    #define MyDay 5

    datetime LastTradeTime;

    //+------------------------------------------------------------------+
    //| expert initialization function |
    //+------------------------------------------------------------------+
    int init()
    {
    //----
    // установим время последней торговой операции вчерашним днем
    LastTradeTime = CurTime()-24*60*60;
    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert deinitialization function |
    //+------------------------------------------------------------------+
    int deinit()
    {
    //----


    //----
    return(0);
    }
    //+------------------------------------------------------------------+
    //| expert start function |
    //+------------------------------------------------------------------+
    int start()
    {
    //----
    // проверим, не закрывали ли мы все уже сегодня
    // если да - выходим
    if (TimeDayOfYear(CurTime())==TimeDayOfYear(LastTradeTime)) return(0);

    // если не пятница - выходим
    if (DayOfWeek()!=MyDay) return(0);


    // проверим, не наступило ли время закрывать позиции
    if (((TimeHour(CurTime())==MyHour)&&(TimeMinute(CurTime())>=MyMinute))
    ||(TimeHour(CurTime())>MyHour))
    {
    // закроем все позиции и удалим ордера
    while (OrdersTotal()>0)
    {
    // выделим первую позицию или ордер в списке
    if (!OrderSelect(0, SELECT_BY_POS))
    // в случае неудачи выйти из цикла
    break;

    // если отложенный ордер, то удалить
    if (OrderType()>OP_SELL)
    {
    if (!OrderDelete(OrderTicket()))
    {
    Print("Ошибка ", GetLastError()," при удалении отложенного ордера ",
    OrderTicket());
    break;
    }
    }
    // если открытая позиция, то закрыть
    else
    {
    // попробуем найти локированную позицию по этому же инструменту
    int ticket0;
    ticket0 = OrderTicket();

    string symbol0;
    symbol0 = OrderSymbol();

    int ordertype0;
    ordertype0 = OrderType();

    int i;
    for (i = 1; i

    // заново выделим нулевую позицию
    if (!OrderSelect(ticket0, SELECT_BY_TICKET))
    // в случае неудачи выйти из цикла
    break;

    double price;
    if (OrderType()==OP_SELL)
    price = MarketInfo(OrderSymbol(), MODE_ASK);
    else
    price = MarketInfo(OrderSymbol(), MODE_BID);


    if (!OrderClose(OrderTicket(), OrderLots(), price, 3))
    {
    Print("Ошибка ", GetLastError()," при закрытии позиции ",
    OrderTicket());
    break;
    }
    }

    // пауза 10 секунд
    Sleep(10000);
    }
    if (OrdersTotal()==0) LastTradeTime = CurTime();
    }

    //----
    return(0);
    }
    //+------------------------------------------------------------------+


     

    Вначале я расскажем о самой функции, потом разберем каждую новую строчку нашего эксперта.

     

       bool OrderCloseBy(int ticket, int opposite, color сolor=CLR_NONE)


     

    Функция OrderCloseBy() закрывает одну позицию другой позицией по тому же самому инструменту. В случае успеха она возвращает true, а в случае ошибки — false. Код ошибки можно в дальнейшем узнать с помощью функции GetLastError().

    Параметры функции OrderCloseBy():

    • ticket — тикер первой позиции;
    • opposite — тикер второй позиции;
    • сolor — цвет закрывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то закрывающая стрелка не отображается на графике.

    Разберем каждую новую строчку нашего эксперта:

     

       // попробуем найти локированную позицию по этому же инструменту
    int ticket0;
    ticket0 = OrderTicket();

    string symbol0;
    symbol0 = OrderSymbol();

    int ordertype0;
    ordertype0 = OrderType();

    int i;
    for (i = 1; i


    // заново выделим нулевую позицию
    if (!OrderSelect(ticket0, SELECT_BY_TICKET))
    // в случае неудачи выйти из цикла
    break;


     

    Вначале запомним номер тикера, инструмент и тип операции выделенной позиции (первой в списке открытых позиций и отложенных ордеров) в переменных ticket0, symbol0 и ordertype0 соответственно:

     

       int ticket0;
    ticket0 = OrderTicket();


    string symbol0;
    symbol0 = OrderSymbol();

    int ordertype0;
    ordertype0 = OrderType();


     

    После этого в цикле переберем все оставшиеся позиции в поиске позиции, чтобы найти позицию по тому же инструменту, но открытую в другом направлении (OrderType()==((ordertype0+1)%2)):

     

       int i;
    for (i = 1; i

     

    Если такую позицию мы нашли, то закроем ее и нашу первоначально выделенную позицию с помощью функции OrderCloseBy() и в случае успеха присвоим переменной i значение -2.

    Далее мы проверяем были ли закрыты локированные позиции. Если «да», то делаем паузу и переходим к следующей итерации цикла while, т.е. смотрим следующую позицию/ордер — кандидатов на закрытие/удаление:

     

       if (i==-2)
    {
    Sleep(10000);
    continue;
    }


     

    Если же локированные позиции были не найдены, то вновь выделяем первую позицию в списке открытых позиций и отложенных ордеров:

     

       if (!OrderSelect(ticket0, SELECT_BY_TICKET)) 
    // в случае неудачи выйти из цикла
    break;


     

    Далее пытаемся закрыть эту позицию с помощью функции OrderClose().

  • Читать Закрыть 39

    Обработка ошибок.

    Как я уже неоднократно упоминал в предыдущих выпусках, если какая-то функция вернула ошибку, то код этой ошибки можно получить с помощью функции GetLastError():

       int GetLastError()


    После вызова этой функции код последней ошибки обнуляется, поэтому если в промежутке между двумя вызовами функции GetLastError() не произошло новой ошибки, то второй раз функция вернет 0.

    Функция GetLastError() возвращает числовой код ошибки. Если же Вы хотите получить словесное описание ошибки, то используйте функцию ErrorDescription(), описанную в файле stdlib.mqh:

       string ErrorDescription(int error_code)
    {
    string error_string;
    //----
    switch(error_code)
    {
    //---- codes returned from trade server
    case 0:
    case 1: error_string="no error"; break;
    case 2: error_string="common error"; break;
    ...
    }
    //----
    return(error_string);
    }


    Коды ошибок, возвращаемые сервером:

    Константа Значение Описание
    ERR_NO_ERROR 0 Нет ошибки
    ERR_NO_RESULT 1 Нет ошибки, но результат неизвестен
    ERR_COMMON_ERROR 2 Общая ошибка
    ERR_INVALID_TRADE_PARAMETERS 3 Неправильные параметры
    ERR_SERVER_BUSY 4 Торговый сервер занят
    ERR_OLD_VERSION 5 Старая версия клиентского терминала
    ERR_NO_CONNECTION 6 Нет связи с торговым сервером
    ERR_NOT_ENOUGH_RIGHTS 7 Недостаточно прав
    ERR_TOO_FREQUENT_REQUESTS 8 Слишком частые запросы
    ERR_MALFUNCTIONAL_TRADE 9 Недопустимая операция нарушающая функционирование сервера
    ERR_ACCOUNT_DISABLED 64 Счет заблокирован
    ERR_INVALID_ACCOUNT 65 Неправильный номер счета
    ERR_TRADE_TIMEOUT 128 Истек срок ожидания совершения сделки
    ERR_INVALID_PRICE 129 Неправильная цена
    ERR_INVALID_STOPS 130 Неправильные стопы
    ERR_INVALID_TRADE_VOLUME 131 Неправильный объем
    ERR_MARKET_CLOSED 132 Рынок закрыт
    ERR_TRADE_DISABLED 133 Торговля запрещена
    ERR_NOT_ENOUGH_MONEY 134 Недостаточно денег для совершения операции
    ERR_PRICE_CHANGED 135 Цена изменилась
    ERR_OFF_QUOTES 136 Нет цен
    ERR_BROKER_BUSY 137 Брокер занят
    ERR_REQUOTE 138 Новые цены
    ERR_ORDER_LOCKED 139 Ордер заблокирован и уже обрабатывается
    ERR_LONG_POSITIONS_ONLY_ALLOWED 140 Разрешена только покупка
    ERR_TOO_MANY_REQUESTS 141 Слишком много запросов
    ERR_TRADE_MODIFY_DENIED 145 Модификация запрещена, так как ордер слишком близок к рынку
    ERR_TRADE_CONTEXT_BUSY 146 Подсистема торговли занята
    ERR_TRADE_EXPIRATION_DENIED 147 Использование даты истечения ордера запрещено брокером
    ERR_TRADE_TOO_MANY_ORDERS 148 Количество открытых и отложенных ордеров достигло предела, установленного брокером


    Коды ошибок выполнения, генерируемые клиентским терминалом при выполнении советника:

    Константа Значение Описание
    ERR_NO_MQLERROR 4000 Нет ошибки
    ERR_WRONG_FUNCTION_POINTER 4001 Неправильный указатель функции
    ERR_ARRAY_INDEX_OUT_OF_RANGE 4002 Индекс массива — вне диапазона
    ERR_NO_MEMORY_FOR_FUNCTION_CALL_STACK 4003 Нет памяти для стека функций
    ERR_RECURSIVE_STACK_OVERFLOW 4004 Переполнение стека после рекурсивного вызова
    ERR_NOT_ENOUGH_STACK_FOR_PARAMETER 4005 На стеке нет памяти для передачи параметров
    ERR_NO_MEMORY_FOR_PARAMETER_STRING 4006 Нет памяти для строкового параметра
    ERR_NO_MEMORY_FOR_TEMP_STRING 4007 Нет памяти для временной строки
    ERR_NOT_INITIALIZED_STRING 4008 Неинициализированная строка
    ERR_NOT_INITIALIZED_ARRAYSTRING 4009 Неинициализированная строка в массиве
    ERR_NO_MEMORY_FOR_ARRAYSTRING 4010 Нет памяти для строкового массива
    ERR_TOO_LONG_STRING 4011 Слишком длинная строка
    ERR_REMAINDER_FROM_ZERO_DIVIDE 4012 Остаток от деления на ноль
    ERR_ZERO_DIVIDE 4013 Деление на ноль
    ERR_UNKNOWN_COMMAND 4014 Неизвестная команда
    ERR_WRONG_JUMP 4015 Неправильный переход
    ERR_NOT_INITIALIZED_ARRAY 4016 Неинициализированный массив
    ERR_DLL_CALLS_NOT_ALLOWED 4017 Вызовы DLL не разрешены
    ERR_CANNOT_LOAD_LIBRARY 4018 Невозможно загрузить библиотеку
    ERR_CANNOT_CALL_FUNCTION 4019 Невозможно вызвать функцию
    ERR_EXTERNAL_EXPERT_CALLS_NOT_ALLOWED 4020 Вызовы внешних библиотечных функций не разрешены
    ERR_NOT_ENOUGH_MEMORY_FOR_RETURNED_STRING 4021 Недостаточно памяти для строки, возвращаемой из функции
    ERR_SYSTEM_BUSY 4022 Система занята
    ERR_INVALID_FUNCTION_PARAMETERS_COUNT 4050 Неправильное количество параметров функции
    ERR_INVALID_FUNCTION_PARAMETER_VALUE 4051 Недопустимое значение параметра функции
    ERR_STRING_FUNCTION_INTERNAL_ERROR 4052 Внутренняя ошибка строковой функции
    ERR_SOME_ARRAY_ERROR 4053 Ошибка массива
    ERR_INCORRECT_SERIES_ARRAY_USING 4054 Неправильное использование массива-таймсерии
    ERR_CUSTOM_INDICATOR_ERROR 4055 Ошибка пользовательского индикатора
    ERR_INCOMPATIBLE_ARRAYS 4056 Массивы несовместимы
    ERR_GLOBAL_VARIABLES_PROCESSING_ERROR 4057 Ошибка обработки глобальныех переменных
    ERR_GLOBAL_VARIABLE_NOT_FOUND 4058 Глобальная переменная не обнаружена
    ERR_FUNCTION_NOT_ALLOWED_IN_TESTING_MODE 4059 Функция не разрешена в тестовом режиме
    ERR_FUNCTION_NOT_CONFIRMED 4060 Функция не подтверждена
    ERR_SEND_MAIL_ERROR 4061 Ошибка отправки почты
    ERR_STRING_PARAMETER_EXPECTED 4062 Ожидается параметр типа string
    ERR_INTEGER_PARAMETER_EXPECTED 4063 Ожидается параметр типа integer
    ERR_DOUBLE_PARAMETER_EXPECTED 4064 Ожидается параметр типа double
    ERR_ARRAY_AS_PARAMETER_EXPECTED 4065 В качестве параметра ожидается массив
    ERR_HISTORY_WILL_UPDATED 4066 Запрошенные исторические данные в состоянии обновления
    ERR_TRADE_ERROR 4067 Ошибка при выполнении торговой операции
    ERR_END_OF_FILE 4099 Конец файла
    ERR_SOME_FILE_ERROR 4100 Ошибка при работе с файлом
    ERR_WRONG_FILE_NAME 4101 Неправильное имя файла
    ERR_TOO_MANY_OPENED_FILES 4102 Слишком много открытых файлов
    ERR_CANNOT_OPEN_FILE 4103 Невозможно открыть файл
    ERR_INCOMPATIBLE_ACCESS_TO_FILE 4104 Несовместимый режим доступа к файлу
    ERR_NO_ORDER_SELECTED 4105 Ни один ордер не выбран
    ERR_UNKNOWN_SYMBOL 4106 Неизвестный символ
    ERR_INVALID_PRICE_PARAM 4107 Неправильный параметр цены для торговой функции
    ERR_INVALID_TICKET 4108 Неверный номер тикета
    ERR_TRADE_NOT_ALLOWED 4109 Торговля не разрешена
    ERR_LONGS_NOT_ALLOWED 4110 Длинные позиции не разрешены
    ERR_SHORTS_NOT_ALLOWED 4111 Короткие позиции не разрешены
    ERR_OBJECT_ALREADY_EXISTS 4200 Объект уже существует
    ERR_UNKNOWN_OBJECT_PROPERTY 4201 Запрошено неизвестное свойство объекта
    ERR_OBJECT_DOES_NOT_EXIST 4202 Объект не существует
    ERR_UNKNOWN_OBJECT_TYPE 4203 Неизвестный тип объекта
    ERR_NO_OBJECT_NAME 4204 Нет имени объекта
    ERR_OBJECT_COORDINATES_ERROR 4205 Ошибка координат объекта
    ERR_NO_SPECIFIED_SUBWINDOW 4206 Не найдено указанное подокно
    ERR_SOME_OBJECT_ERROR 4207 Ошибка при работе с объектом
  • Читать Закрыть 40

    Секреты правильной торговли.

    Расскажем о трех функциях: IsExpertEnabled(), IsTradeContextBusy() и IsTradeAllowed().

    Функция IsExpertEnabled()

     

       bool IsExpertEnabled()


     

    Функция IsExpertEnabled() возвращает true, если экспертам разрешено совершать сделки и управлять ордерами. Дело в том, что на уровне настроек клиентского терминала можно запретить ВСЕМ экспертам торговать. Это можно сделать, например, с помощью команды главного меню: Сервис -> Настройки. В появившемся окне выбираем вкладку «Советники», в которой ставим или убираем галочку напротив «Включить советники …».

    Более простой способ отключать/включать советников — кнопка.

    Если же на уровне настроек терминала работа экспертов запрещена, то функция IsExpertEnabled() вернет false.

    Функция IsTradeContextBusy()

    В каждый момент времени только один эксперт может торговать. Если в этот момент второй эксперт попытается совершить торговую операцию, то торговая функция вернет ошибку ERR_TRADE_CONTEXT_BUSY (146).

     

       bool IsTradeContextBusy()


     

    Функция IsTradeContextBusy() позволит Вам заблаговременно выявить ситуацию, когда другой эксперт уже пытается торговать (она вернет в этом случае значение true), и избежать нерезультативного вызова торговой функции (которая все равно завершиться с ошибкой). Обычно в таких случаях лучше подождать немного до того момента, когда другой эксперт закончит торговать (тогда функция IsTradeContextBusy() вернет значение false) и только после этого вызывать Вашу торговую функцию.

    Функция IsTradeAllowed()

    Функция IsTradeAllowed() объединяет все обе предыдущие функции. Она вернет true только в том случае, если торговля экспертов разрешена и торговый поток свободен для торговли, т.е. никакой другой эксперт не совершает в данный момент торговых операций.

     

       bool IsTradeAllowed()


     

    Как уже говорилось в каждый момент времени только один эксперт может торговать. Если в этот момент второй эксперт попытается совершить торговую операцию, то торговая функция вернет ошибку ERR_TRADE_CONTEXT_BUSY (146).

    Что же делать, если у Вас торгует одновременно несколько экспертов?

    Напишем функцию, которая возвращает:

    • 0, если торговый поток свободен;
    • 1, если работа эксперта была остановлена (см. причины ниже);
    • 2, если торговля экспертов запрещена на уровне настроек клиентского терминала.

    Если в момент обращения к функции торговый поток был занят, то функция ждет, пока он освободится. После этого она обновляет данные о текущих котировках с помощью функции RefreshRates() и возвращает 0.

    Напомню возможные причины остановки эксперта:

    • завершение работы MetaTrader 4,
    • закрытие графика,
    • удаление эксперта с графика,
    • изменение инструмента или периода графика,
    • удачная перекомпиляции программы в MetaEditor,
    • изменение параметров эксперта,
    • переключение на другой счет.

    Исходный код функции, которая решает поставленную задачу:

     

       //+-------------------------------------------------------------------+
    //| Функция WaitUntilTradingIsAllowed возвращает:|
    //| 0 - если торговый поток свободен |
    //| 1 - если работа эксперта была остановлена |
    //| 2 - если торговля экспертов запрещена на |
    //| уровне настроекклиентского терминала |
    //+-------------------------------------------------------------------+
    int WaitUntilTradingIsAllowed()
    {
    // Если торговля разрешена, то вернем 0 и выйдем
    if (IsTradeAllowed()) return(0);

    // В цикле проверяем, как изменилась ситуация
    while (!IsStopped())
    {
    // Если торговля экспертов запрещена на уровне настроек
    // клиентского терминала, то выйдем и вернем 2
    if (!IsExpertEnabled()) return(2);

    // Если торговый поток освободился, то обновим данные
    // о текущих курсах и вернем 0
    if (!IsTradeContextBusy())
    {
    RefreshRates();
    return(0);
    }

    // Если дошли до момента, то ситуация не изменилась
    // Поэтому делаем паузу в 0.1 секунду
    Sleep(100);
    }

    // Т.к. вышли из цикла, то работа эксперта была остановлена
    // Вернем 1
    return(1);
    }



     

    Функция IsStopped().

     

       bool IsStopped()


     

    Функция возвращает true, если эксперт был «остановлен», и false — в противном случае.

    Эксперт получает команду на завершение в следующих случаях:

    • при завершении работы клиентского терминала,
    • при закрытии графика, к которому прикреплен эксперт,
    • при удалении эксперта с графика,
    • при изменении инструмента или периода графика,
    • после перекомпиляции эксперта в MetaEditor,
    • после изменения параметров советника,
    • при переключении на другой счет.

    Чтобы не пытаться совершить сделку по старым котировкам и не получить сообщение об ошибке, мы обновляем информацию о текущих котировках с помощью функции RefreshRates().

     

       bool RefreshRates()


     

    Функция возвращает true, если информация была обновлена, и false, если данные не изменились и в обновлении нет нужды. Если данные были обновлены, то вызов функции RefreshRates() также обновит время последней котировки, которое возвращается функцией CurTime().

    Считается хорошим тоном вызывать функцию RefreshRates() перед вызовом торговой функции, если перед этим эксперт что-то очень долго или пересчитывал. Или же если эксперт ждал, пока другой эксперт закончит торговать и торговый поток освободиться.

    Поскольку, наверняка, в своей торговле Вы будете использовать сразу несколько экспертов или одного и того же эксперта, но на разных временных промежутках или инструментах, то Ваши эксперты будут периодически ожидать освобождения торгового потока и Вы будете использовать функцию RefreshRates() практически всегда.

  • Читать Закрыть 41

    Выдерживаем паузу между сделками.

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

    Это произойдет или потому, что сервер подумает, что такое количество запросов в секунду может осуществлять только злоумышленник, который ставит перед собой лишь цель «завалить» торговый сервер. Или потому, что сотнями запросами в минуту Вы разъярите дилера — сотрудника дилингового центра, которому приходится обрабатывать Ваши запросы.

    Считается правилом хорошего тона не совершать торговые операции чаще, чем 1 раз в 5-10 секунд. Конечно, в любом правиле могут быть исключения и иногда Вам крайне необходимо совершить операции с меньшим временным лагом, но старайтесь этой возможностью не злоупотреблять.

    Старайтесь выдерживать паузу минимум в 5 секунд. В этом Вам поможет моя функция WaitBeforeTransaction()

     

    //+-------------------------------------------------------------------+
    //| Фунцкия WaitBeforeTransaction выдерживает паузу Secs секунд
    //| между торговыми операциями эксперта (по умолчанию 5 секунд)
    //|
    //| Возвращает:
    //| 1 - если пауза выдержена без ошибок
    //| 0 - если эксперт был остановлен
    //| -1 - если произошла какая-то ошибка
    //+--------------------------------------------------------------------+
    int WaitBeforeTransaction(int Secs = 5)
    {
    // если режим тестирования, то ждать необязательно
    if (IsTesting()) return(1);

    // если глобальная переменная LastTradeTime не существует,
    // то создать ее
    if (!GlobalVariableCheck("LastTradeTime"))
    {
    // Если произошла какя-то ошибка при вызове функции
    // GlobalVariableCheck(), выйдем с ошибкой
    if (GetLastError()!=0)
    {
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при проверке глобальной переменной LastTradeTime");
    return(-1);
    }

    // глобальная переменная не существует, создадим ее
    if (GlobalVariableSet("LastTradeTime", 1)==0)
    {
    // произошла ошибка при создании глобальной переменной
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при создании глобальной переменной LastTradeTime");
    return(-1);
    }

    // глобальная переменная успешно создана
    Print("WaitBeforeTransaction(): глобальная переменная ",
    "LastTradeTime создана");
    }

    // получим время последней операции
    datetime LastTradeTime;
    LastTradeTime = GlobalVariableGet("LastTradeTime");

    // если произошла ошибка (равна нулю), то выходим с ошибкой
    if (LastTradeTime==0)
    {
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при чтении глобальной переменной LastTradeTime");
    return(-1);
    }

    // ждем Secs секунд
    while(true)
    {
    // если эксперт остановлен, выйдем со значением 0
    if (IsStopped())
    {
    Print("WaitBeforeTransaction(): эксперт остановлен. Выходим...");
    return(0);
    }

    // если прошло меньше Secs секунд, то ждем
    if ((LocalTime()-LastTradeTime)

    // т.к. прошло больше Secs секунд, то попробуем изменить значение
    // глобальной переменной LastTradeTime на текущее время

    // используем функцию GlobalVariableSetOnCondition(), чтобы выявить
    // ошибку, если уже за время ожидания другой эксперт успел совершить
    // сделку и изменил значение глобальной переменной
    if (GlobalVariableSetOnCondition("LastTradeTime", LocalTime(), LastTradeTime))
    {
    // за время ожидания глобальная переменная не изменилась, поэтому
    // удалось установить ее новое значение
    // пауза выдержена, обновим котировки и выйдем без ошибки
    RefreshRates();
    return(1);
    }
    else
    {
    // не удалось изменить значение глобальной переменной, т.к. другой
    // эксперт успел совершить сделку раньше и установил новое
    // значение переменной поэтому получим текущее значение глобальное
    // переменной и продолжим ожидание
    LastTradeTime = GlobalVariableGet("LastTradeTime");

    // если произошла ошибка (равна нулю), то выходим с ошибкой
    if (LastTradeTime==0)
    {
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при чтении глобальной переменной LastTradeTime");
    return(-1);
    }
    }
    }
    }



     

    Определение режима тестирования на истории с помощью функции IsTesting()

    Сам исходный код функции обладает достаточным количеством комментариев, и я уверен, что логика реализации этой функции Вам станет абсолютно понятна после изучения следующих функций:

     

    • IsTesting()
    • GlobalVariableCheck()
    • GlobalVariableSet()
    • GlobalVariableGet()
    • LocalTime()

     

    Начнем с рассмотрения функции IsTesting().

     

       bool IsTesting()


     

    Функция IsTesting() возвращает true, если эксперт работает в режиме тестирования на исторических данных, и false — если работа ведется на демо- или реальном счете.

    Дело в том, что необязательно сразу заставлять эксперта торговать на демо- или реальном счете. Гораздо эффективнее вначале оттестировать эксперта на исторических данных, которые есть в клиентском терминале. После этого с определенной точностью станет понятно, на что способен этот эксперт.

    Основное преимущество тестов на истории — быстрота. Вам не надо ждать месяцы или годы, чтобы оттестировать своего эксперта на реальных котировках. Вы просто «прогоняете» советника по истории. На это у Вас уходит лишь несколько минут. Однако если Вы будете каждый раз ждать 10 секунд между торговыми операциями, то Вы потеряете это преимущество. Именно поэтому в коде нашей функции присутствует строка:

     

       if (IsTesting()) return(1);


     

    Т.е. мы выходим из функции, если эксперт находится в режиме тестирования на историчексих данных.

  • Читать Закрыть 42

    Глобальные переменные.

    GlobalVariableCheck(): проверка существования глобальной переменной

    Для человека, имеющего уже опыт написания экспертов, выражение «глобальные переменные» может внести небольшую путанницу. Дело в том, что на самом деле существует два вида «глобальных переменных»:

    • переменные, которые видны из любой функции ОДНОГО эксперта (т.е. являются глобальными в пределах этого эксперта); и
    • переменные, которые являются общими для ВСЕХ экспертов.

    О глобальных переменных первого типа — тех, которые являются общими для всех функций ОДНОГО эксперта, — я рассказывал ранее, в выпуске «Глобальные переменные».

    Сейчас же речь пойдет именно о глобальных переменных второго типа — общих для ВСЕХ экспертов.

    Глобальная переменная второго типа — это переменная, к которой можно обратиться из любого эксперта. Если в течение четырех недель не было сделано попытки прочесть значение глобальной переменной или записать в нее новое значение, то глобальная переменная автоматически удаляется клиентским терминалом.

    Для работы с глобальными переменными используются следующие функции:

    • GlobalVariableCheck()
    • GlobalVariableDel()
    • GlobalVariableGet()
    • GlobalVariableName()
    • GlobalVariableSet()
    • GlobalVariableSetOnCondition()
    • GlobalVariablesDeleteAll()
    • GlobalVariablesTotal()

    Вначале я хотел бы рассказать о функции GlobalVariableCheck().

     

       bool GlobalVariableCheck(string name)


     

    Эта функция возвращает true, если глобальная переменная с именем name существует, и false, если такой переменной нет.

    В нашей функции WaitBeforeTransaction() мы храним время и дату совершения последней торговой операции в глобальной переменной «LastTradeTime».

    Перед тем, как узнать время последней операции, прочитав значение этой переменной, мы проверяем, существует ли она вообще:

     

        // если глобальная переменная LastTradeTime не существует,
    // то создать ее
    if (!GlobalVariableCheck("LastTradeTime"))
    {
    ... создаем переменную ...
    }


     

    Проверять существование глобальной переменной и создавать ее в случае отсутствия можно в двух местах: при инициализации эксперта — в функции init() — или на каждом тике — в функции start().

    Несмотря на то, что на первый взгляд напрашивается осуществлять эти действия в функции init(), я бы советовал Вам делать это все-таки в функции start(). Дело в том, что если пользователь при работающем эксперте удалит глобальную переменную, а проверка на ее существование делается только при инициализации эксперта, то дальнейшее поведение такого советника сложно спрогнозировать. Скорее всего, такой советник просто перестанет торговать и начнет выдавать ошибки при обращении к удаленной глобальной переменной.

    GlobalVariableSet() — установка нового значения глобальной переменной

    Предположим, что ее не существует или же нам надо изменить ее значение. В этом случае надо использовать функцию GlobalVariableSet().

     

       datetime GlobalVariableSet(string name, double value)


     

    Функция GlobalVariableSet() устанавливает новое значение value глобальной переменной с именем name и в случае успеха возвращает время последнего доступа к глобальной переменной. Если произошла какая-то ошибка, то функция возвращает 0. Код ошибки, как обычно, можно получить с помощью функции GetLastError().

    Если глобальная переменная с именем name отсутствовала, то она создается и она принимает значение value.

    Пример использования функции GlobalVariableSet() можно найти в написанной нами функции WaitBeforeTransaction():

     

        // если глобальная переменная LastTradeTime не существует,
    // то создать ее
    if (!GlobalVariableCheck("LastTradeTime"))
    {
    // Если произошла какя-то ошибка при вызове функции
    // GlobalVariableCheck(), выйдем с ошибкой
    if (GetLastError()!=0)
    {
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при проверке глобальной переменной LastTradeTime");
    return(-1);
    }

    // глобальная переменная не существует, создадим ее
    if (GlobalVariableSet("LastTradeTime", 1)==0)
    {
    // произошла ошибка при создании глобальной переменной
    Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
    " при создании глобальной переменной LastTradeTime");
    return(-1);
    }

    // глобальная переменная успешно создана
    Print("WaitBeforeTransaction(): глобальная переменная ",
    "LastTradeTime создана");
    }



     

    GlobalVariableSetOnCondition() — установка нового значения глобальной переменной, если текущее ее значение равно заданному значению

    К счастью, создатели языка MetaQuotes Language 4 являются профессиональными программистами и заранее могут предугадать, что может понадобиться трейдеру при написании экспертов. Я им очень благодарен, что они включили в список функций языка MetaQuotes Language 4 функцию GlobalVariableSetOnCondition().

    Давайте же рассмотрим, чем так полезна эта функция.

     

       bool GlobalVariableSetOnCondition(string name, double value, double check_value)


     

    Прежде всего эта функция проверяет, существует ли глобальная переменная с именем name. В документации по MetaQuotes Language 4 написано, что если такой глобальной переменной нет, то функция возвращает false и генерит ошибку ERR_GLOBAL_VARIABLE_NOT_FOUND (4058), которую можно получить с помощью функции GetLastError().

    Однако при экспериментах с функцией GlobalVariableSetOnCondition() мне не удалось добиться того, чтобы функция вернула ошибку ERR_GLOBAL_VARIABLE_NOT_FOUND (4058). Почему-то даже в случае отсутствия глобальной переменной функция возвращает false и код ошибки ERR_NO_ERROR (т.е. 0). Вот кусок кода, который я использовал для этой цели:

     

       string name = "MyGlobalVariable";
    double value = 1;
    double check_value = 2;

    if (GlobalVariableSetOnCondition(name, value, check_value))
    {
    Print("Глобальная переменная ", name, " существует.",
    " Ее значение изменено с ", check_value, " на ", value);
    }
    else
    {
    int Err=0;
    Err = GetLastError();
    Print(Err);
    switch (Err)
    {
    case 0: Print("Глобальная переменная ", name," изменена не была",
    " т.к. ее значение не равно ", check_value); break;
    case 4058: Print("Глобальной переменной ", name,
    " не существует"); break;
    default: Print("Неизвестная ошибка: ", Err);
    }
    }


     

    Если это баг, то думаю, что в следующих версиях MetaTrader 4 его исправят.

    Вернемся к описанию функции GlobalVariableSetOnCondition(). Если же глобальная переменная name существует, то функция не изменит ее текущего значения и вернет false, если текущее значение этой глобальной переменной не равно check_value. Если потом запросить код последней ошибки, то функция GetLastError()вернет ERR_NO_ERROR (т.е. 0), т.к. реально никакой ошибки не было.

    Если же текущее значение глобальной переменной name равно значению check_value, то функция GlobalVariableSetOnCondition() присвоит этой глобальной переменной новое значение: value.

  • Читать Закрыть 43

    Как дождаться освобождения торгового потока.

    Пример использования функции GlobalVariableSetOnCondition()

    В этом же выпуске я хотел бы рассмотреть пример ее практического использования.

    //+---------------------------------------------------------------------+
    //| int StartTrading() |
    //| |
    //| Функция занимает торговый поток |
    //| Функция возвращает: |
    //| 0 - если можно торговать |
    //| 1 - если работа эксперта была остановлена |
    //| 2 - если торговля экспертов запрещена на уровне настроек|
    //| клиентского терминала |
    //+---------------------------------------------------------------------+
    #define GLOB_VAR_NAME "TradeIsAllowed"


    int StartTrading()
    {
    // если советник работает в режиме тестирования, то просто выйдем
    if (IsTesting()) return(0);

    int LastError;

    while (!IsStopped())
    {
    // Если торговля экспертов запрещена на уровне настроек
    // клиентского терминала, то выйдем и вернем 2
    if (!IsExpertEnabled()) return(2);

    // проверим, существует ли глобальная переменная
    if (GlobalVariableCheck(GLOB_VAR_NAME)) break;

    // если произошла ошибка при проверке глобальной переменной,
    // то сообщим об этом в логах и выждем 0.1 секунды
    LastError = GetLastError();
    if (LastError!=0)
    {
    Print("StartTrading(): ошибка ", LastError,
    " при проверке наличия глобальной переменной ", GLOB_VAR_NAME);
    Sleep(100);
    continue;
    }

    // глобальная переменная не существует - создадим ее
    if (GlobalVariableSet(GLOB_VAR_NAME, 0)>0) break;

    // произошла ошибка при создании переменной - запишем в лог
    LastError = GetLastError();
    Print("StartTrading(): ошибка ", LastError,
    " при создании глобальной переменной ", GLOB_VAR_NAME);
    Sleep(100);
    }

    // глобальная переменная существует или же эксперт был остановлен

    // В цикле проверяем, как изменилась ситуация
    while (!IsStopped())
    {
    // Если торговля экспертов запрещена на уровне настроек
    // клиентского терминала, то выйдем и вернем 2
    if (!IsExpertEnabled()) return(2);

    // Если удалось изменить значение глобальной переменной, то обновим данные
    // о текущих курсах и вернем 0
    if (GlobalVariableSetOnCondition(GLOB_VAR_NAME, 1, 0))
    {
    RefreshRates();
    return(0);
    }

    // Если дошли до момента, то ситуация не изменилась
    // Поэтому делаем паузу в 0.1 секунду
    Sleep(100);
    }

    // Т.к. вышли из цикла, то работа эксперта была остановлена
    // Вернем 1
    return(1);
    }


    //+---------------------------------------------------------------------+
    //| int StopTrading() |
    //| |
    //| Функция разрешает торговать следующему эксперту |
    //| Функция возвращает: |
    //| 0 - если другому эксперту можно торговать |
    //| 1 - если работа эксперта была остановлена |
    //| 2 - если торговля экспертов запрещена на уровне настроек|
    //| клиентского терминала |
    //+---------------------------------------------------------------------+
    #define GLOB_VAR_NAME "TradeIsAllowed"

    int StopTrading()
    {
    // если советник работает в режиме тестирования, то просто выйдем
    if (IsTesting()) return(0);

    int LastError;

    while (!IsStopped())
    {
    // Если торговля экспертов запрещена на уровне настроек
    // клиентского терминала, то выйдем и вернем 2
    if (!IsExpertEnabled()) return(2);

    // Если удалось изменить значение глобальной переменной, то обновим данные
    // о текущих курсах и вернем 0
    if (GlobalVariableSet(GLOB_VAR_NAME, 0)>0)
    {
    return(0);
    }

    // при сбросе значения глобальной переменной произошла какая-то ошибка
    LastError = GetLastError();
    Print("StopTrading(): ошибка ", LastError,
    " при сбросе значения глобальной переменной ", GLOB_VAR_NAME);

    // делаем паузу в 0.1 секунду
    Sleep(100);
    }

    // Т.к. вышли из цикла, то работа эксперта была остановлена
    // Вернем 1
    return(1);
    }


  • Читать Закрыть 44

    Критическая секция: разграничение доступа к ресурсу.

    Расскажем о способе организации «критической секции» в советнике.

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

    Для того, чтобы избежать этого, мы создадим объект «критическая секция», который может находиться в двух состояниях:

    • «зеленый свет» (-1); и
    • «красный свет» (1).

    В каждый конкретный момент только один советник может получить доступ к данным. Пока советник получает данные, будет гореть «красный свет» и другие советники будут ждать «зеленового света». Если горит «зеленый свет», то дорога свободна, т.к. никто в данный момент к данным не обращается.

    Напишем две функции:

    • Lock() — вызываем ее перед началом работы с ресурсом. Функция ждет «зеленового света» и меняет его на «красный».
    • Unlock() — обязательно вызываем ее после окончания работы с ресурсом, чтобы снова зажечь «зеленый свет».

    Вот исходный код этих функций:

     

    //+------------------------------------------------------------------+
    //| Lock() |
    //| |
    //| Возвращает: |
    //| 0 - если "критическая секция" успешно |
    //| заблокирована |
    //| 1 - в случае ошибки |
    //| 2 - эксперт остановлен |
    //| 3 - по таймауту (слишком долго ждали) |
    //+------------------------------------------------------------------+
    int Lock(string GlobVarName, int timeout = 0)
    {
    string critical_section = GlobVarName+"Lock";

    // проверим, существует ли переменная critical_section
    if (!GlobalVariableCheck(critical_section))
    {
    if (GetLastError()!=0) return(1);

    // переменная не существует, создадим ее
    if (GlobalVariableSet(critical_section, -1.0)==0) return(1);
    // переменная создана
    }

    int StartTime = GetTickCount();

    // ждем "зеленового света"
    while (true)
    {
    // проверить, не загорелся ли "зеленый свет"
    if (GlobalVariableGet(critical_section)==-1.0)
    {
    // "зеленый свет" загорелся, зажигаем "красный свет"
    if (GlobalVariableSetOnCondition(critical_section, 1.0, -1.0)) return(0);

    // нас опередили, поэтому ждем "зеленового света"
    }

    // проверим, не остановлен ли эксперт
    if (IsStopped()) return(2);


    // таймаут не истек?
    if (timeout!=0)
    {
    if ((GetTickCount()-StartTime)>timeout*1000) return(3);
    }

    // спим 0.1 секунды
    Sleep(100);
    }
    }

    //+------------------------------------------------------------------+
    //| Unlock() |
    //| |
    //| Возвращает: |
    //| 0 - если "критическая секция" успешно |
    //| разблокирована |
    //| 1 - в случае ошибки |
    //| 2 - эксперт остановлен |
    //| 3 - по таймауту (слишком долго ждали) |
    //+------------------------------------------------------------------+
    int Unlock(string GlobVarName, int timeout = 0)
    {
    string critical_section = GlobVarName+"Lock";

    // проверим, существует ли переменная critical_section
    if (!GlobalVariableCheck(critical_section))
    {
    if (GetLastError()!=0) return(1);

    // переменная не существует, создадим ее
    if (GlobalVariableSet(critical_section, -1.0)==0) return(1);

    // переменная создана, поэтому выходим
    return(-1.0);
    }

    int StartTime = GetTickCount();

    // бесконечный цикл
    while (true)
    {
    // пытаемся установить "зеленый свет"
    if (GlobalVariableSetOnCondition(critical_section, -1.0, 1.0)) return(0);


    // проверим, не остановлен ли эксперт
    if (IsStopped()) return(2);

    // таймаут не истек?
    if (timeout!=0)
    {
    if ((GetTickCount()-StartTime)>timeout*1000) return(3);
    }

    // спим 0.1 секунды
    Sleep(100);
    }
    }


     

  • Читать Закрыть 45

    функция OrderLots().

    Очень часто необходимо указать количество лотов открытой позиции или отложенного ордера. Самой простой вариант получить это значение — это выделить позицию или ордер с помощью функции OrderSelect(), а потом вызвать функцию OrderLots().

     

       double OrderLots()



     

    Функция возвращает количество лотов в выделенном ордере или позиции.

    Как уже сказано, основное применение функции — передать количество лотов в качестве параметра в функции OrderClose() и OrderModify().

    Забегая вперед, расскажем о формате функции OrderClose():

     

    bool OrderClose(int ticket, double lots, double price, int slippage, color Color=CLR_NONE)


     

    Где:

    • ticket — тикер ордера или позиции;
    • lots — количество лотов для закрытия позиции;
    • price — цена закрытия;
    • slippage — значение максимального проскальзывания в пунктах;
    • color — цвет стрелки закрытия на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображается.

    Как Вы видите, в качестве второго параметра нам нужно передать в функцию количество лотов. Самый простой способ сделать это — получить количество лотов с помощью функции OrderLots():

     

       // закрыть позицию с тикером 77777 по текущей цене
    // предположим, что позиция уже выделена с помощью OrderSelect
    // и мы точно знаем, что это открытая позиция
    if (OrderType() == OP_SELL)
    OrderClose(OrderTicket(), OrderLots(), Ask, 3);
    else
    OrderClose(OrderTicket(), OrderLots(), Bid, 3);


     

    В этом примере нам встретились две предопределенные переменные типа double:

    • Bid — бид последней котировки по текущему инструменту (к которому прикреплен советник);
    • Ask — аск последней котировки по текущему инструменту.
  • Читать Закрыть 46

    функция OrderExpiration().

    Расскажем о торговой функции OrderExpiration().

       datetime OrderExpiration()


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

    Если дата истечения не установлена (т.е. ордер может существовать бесконечное время, пока не исполнится или не будет удален), то OrderExpiration() вернет 0.

    Для чего используется дата истечения отложенного ордера? Причин может быть множество. Например, довольно разумно удалять отложенные ордера в пятницу поздно вечером — перед закрытием недели.

    В качестве примера использования функции OrderExpiration() напишем кусок кода, которых пробегает по всем неудаленным отложенным ордерам и если дата истечения не установлена, то устанавливает ее на 23:59:59 ближайшей пятницы.

     

       // вычислим 23:59:59 ближайшей пятницы
    datetime friday;
    datetime c_time;
    c_time = CurTime();
    // в friday - дата и время на 00:00:00 текущего дня
    friday = c_time-TimeHour(c_time)*60*60-TimeMinute(c_time)*60-TimeSeconds(c_time);
    // в friday - дата и время на 00:00:00 ближайшей пятницы
    friday = friday + (5-TimeDayOfWeek(c_time))*60*60*24;
    // в friday - дата и время на 23:59:59 ближайшей пятницы
    friday = friday + 23*60*60 + 59*60 + 59;
    // пробежимся по отложенным ордерам и изменим эксперацию
    int pos;
    for(pos=0; posOP_SELL)
    {
    // дата эксперации отсутствует?
    if (OrderExpiration()==0)
    // изменить дату эксперации
    OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(),
    OrderTakeProfit(), friday);
    }
    }
    else
    Print("Ошибка ", GetLastError(), " при выборе ордера номер ", pos);
    }


     

    В этом коде есть несколько неизвестных нам функций:

    • int TimeDayOfWeek(datetime date) — возвращает день недели (0-Воскресенье,1,2,3,4,5,6) для указанной даты;
    • datetime CurTime() — время прихода последней котировки в виде количества секунд, прошедших после 00:00 1 января 1970 года;
    • int TimeHour(datetime time) — возвращает порядковый номер часа для времени time;
    • int TimeMinute(datetime time) — возвращает порядковый номер минуты для времени time;
    • int TimeSeconds(datetime time) — возвращает количество секунд с начала минуты для времени time.

    Например, для 02:16:05 среды функции TimeHour, TimeMinute и TimeSeconds вернут соответственно 2, 16 и 5. А функция TimeDayOfWeek вернет 3.

    Мы помним, что значение типа datetime представляет собой количество секунд, прошедшее с 00:00 1 января 1970 года. Также мы помним, что в часе 60?60 секунд, а в минуте 60 секунд. Исходя из этого становится понятной наша логика получения в переменной friday даты и времени, которые соответствуют 23:59:59 ближайшей пятницы:

     

       // вычислим 23:59:59 ближайшей пятницы
    datetime friday;
    datetime c_time;
    c_time = CurTime();
    // в friday - дата и время на 00:00:00 текущего дня
    friday = c_time-TimeHour(c_time)*60*60-TimeMinute(c_time)*60-TimeSeconds(c_time);
    // в friday - дата и время на 00:00:00 ближайшей пятницы
    friday = friday + (5-TimeDayOfWeek(c_time))*60*60*24;
    // в friday - дата и время на 23:59:59 ближайшей пятницы
    friday = friday + 23*60*60 + 59*60 + 59;


     

Alpari. Professional Services in Financial Markets. © 1998 – 2009 Alpari (RU)
ru