Формальные параметры функций
Функция 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() работу не завершила, то она прерывается принудительно.
Пишем простейшего эксперта
На мой взгляд, наиболее простым способом для новичка изучить процесс написания экспертов будет подробный разбор специально написанных мной для этой цели экспертов. На данный момент (да и далее в своих статьях) я не буду ставить перед собой цель написать супер гениального эксперта, который способен будет зарабатывать десятки процентов в месяц и делать это стабильно. Основная цель - показать все аспекты программирования экспертов, а не написать "грааль".
Итак, внимательно изучите код эксперта ниже, но не волнуйтесь, что на данный момент Вам пока еще ничего не понятно. В следующих выпусках я подробно разберу каждую строчку этого эксперта.
//+------------------------------------------------------------------+
//| 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<AccountFreeMargin()) return(true);
else return(false);
}
//+-------------------------------------------------------------------+
//| Вычисление необходимой маржи на один лот|
//+-------------------------------------------------------------------+
double GetOneLotMargin(string s)
{
double p;
if ((StringSubstr(s, 0, 3) == "EUR")||(StringSubstr(s, 0, 3) == "GBP")||
(StringSubstr(s, 0, 3) == "AUD")||(StringSubstr(s, 0, 3) == "NZD"))
{
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());
}
}
if (StringSubstr(s, 0, 3) == "USD")
return(MarketInfo(s, MODE_LOTSIZE)/AccountLeverage());
if (s == "CHFJPY")
{
p = iClose("USDCHF", Period(), iBarShift("USDCHF", Period(), CurTime(), true));
return(MarketInfo(s, MODE_LOTSIZE)/(AccountLeverage()*p));
}
return(77777777777777777777777777.0);
}
//+------------------------------------------------------------------+
Благодарю Вас за то, что Вы мужественно дочитали до этой строчки. Как я уже упомянул ранее, в следующих выпусках я очень подробно остановлюсь на каждой строчке этого эксперта, и проясню Вам все непонятные моменты.
Использование констант в эксперте
В прошлом выпуске я выложил код нашего первого эксперта.
Вначале разберем, что означают следующие строчки:
#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 - см. описание типов в выпуске "Настраиваем параметры нового эксперта".
Хранение данных в переменных
Любой эксперт оперирует с некоторыми данными - ценами, значениями индикаторов, объемами, количеством открытых позиций и пр. Те, места, где эти данные хранятся в эксперте, называются переменными. В качестве имен переменных можно использовать цифры (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. Параметры эксперта
Если эксперт уже "прикреплен" к графику (в правом верхнем углу графика Вы видите улыбающуюся или грустную рожицу), то для изменения его параметров - значений внешних переменных - нажмите на рожице правую кнопку мыши и выберите в в появившемся контекстном меню пункт "Советники - Свойства ...". Более быстрый способ - использовать горячую клавишу F7. Появится окно с параметрами эксперта (см. рис. 1), в котором Вы сможете изменить значения внешних переменных.
В следующем выпуске я расскажу о глобальных переменных.
Глобальные переменные
В прошлом выпуске я рассказал о внешних переменных. В этом выпуске я остановлюсь на глобальных переменных.
Глобальные переменные
Для человека, имеющего уже опыт написания экспертов, выражение "глобальные переменные" может внести небольшую путанницу. Дело в том, что на самом деле существует два вида "глобальных переменных":
1. переменные, которые видны из любой функции ОДНОГО эксперта (т.е. являются глобальными в пределах этого эксперта); и
2. переменные, которые являются общими для ВСЕХ экспертов.
В этом выпуске речь пойдет именно о глобальных переменных первого типа - тех, которые являются общими для всех функций ОДНОГО эксперта.
Второй же тип глобальных переменных я рассмотрю в следующих выпусках, когда буду рассказывать о функциях, с помощью которых к таким переменным получают доступ, - GlobalVariable...().
Если Вы хотите, чтобы к значению переменной можно было бы обращаться из разных функций одного эксперта, сделайте ее глобальной переменной, т.е. определите ее на том же уровне, что и функции:
//---- глобальные переменные
int CurrentState;
int MyOrderTicket;
//---- определения функций
int init()
{
...
}
Начальное значение глобальной переменной может быть только константой (если не задано никакого значения, то она инициализируется нулем). Это значение присваивается глобальной переменной только один раз, до выполнения функции init().
В следующем выпуске я расскажу о формальных параметрах функций.
Параметры функций
В выпуске "Создание собственных функций" я рассказал достаточно подробно о том, как описывать собственные функции. В этом выпуске я продолжу рассмотрение функций и детально разберу, как в функцию можно передать значения и получить результат функции.
Операции отношения
Операция | Результат |
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 значащих цифр), нельзя производить сравнение таких чисел на равенство (==) или неравенство (!=) без предварительной нормализации (о нормализации вещественных чисел я расскажу в следующих выпусках).
В следующем выпуске я расскажу о логических операциях.
Логические операции
В прошлом выпуске я рассказал об операциях отношения, с помощью которых можно сравнить две переменные. Результатом выступало значение типа bool.
Напомню, что значение ЛОЖЬ (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
Побитовые операции
В прошлом выпуске я рассказал о логических операциях, а этот выпуск будет посвящен побитовым операциям.
Для начала необходимо рассказать о формате представления чисел в компьютере.
Мы в нашей жизни привыкли к десятичному представлению чисел: 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
Объединение арифметических и побитовых операций с операцией присваивания
В прошлых выпусках мы рассмотрели арифметические и побитовые операции. Ранее я рассказал об операторе присваивания.
В синтаксисе MetaQuotes Language 4 - языка написания советников - есть возможность объединить арифметические и побитовые операции с операцией присваивания.
Табл. 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 );
}
В следующем выпуске я расскажу о функции OrderSend(). С помощью этой функции можно открывать позиции и размещать отложенные ордера.
OrderSend() - открытие позиции или размещение отложенного ордера
В прошлых выпусках я рассказал о большом количестве функций, с помощью которых можно получить информация об ордере, выделенном с помощью функции OrderSelect(). Большинство из этих функций очень важны и полезны. Однако я уверен, что Вам уже давно хочется экшина, т.е. Вы давно уже ждете от меня описания тех торговых функций, с помощью которых можно открывать и закрывать позиции, выставлять, модифировать и удалять ордера.
Сегодня я расскажу о первой такой функции - функции 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, то открывающая стрелка не отображается на графике.
В следующем выпуске я рассмотрю параметры функции OrderSend() более подробно.
Параметры функции OrderSend()
В прошлом выпуске я рассказал о функции OrderSend(). В этом выпуске я хотел бы остановиться на параметрах этой функции более подробно.
В параметре cmd мы передаем тип приказа:
Константа | Значение | Описание |
OP_BUY | Открыть позицию на покупку | |
OP_SELL | Открыть позицию на продажу | |
OP_BUYLIMIT | Разместить отложенный ордер BUY LIMIT | |
OP_SELLLIMIT | Разместить отложенный ордер SELL LIMIT | |
OP_BUYSTOP | Разместить отложенный ордер BUY STOP | |
OP_SELLSTOP | Разместить отложенный ордер SELL STOP |
Таблица 1. Возможные значения параметра cmd функции OrderSend()
Таким образом, для того, чтобы открыть позицию на продажу в качестве параметра 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.
В следующем выпуске я расскажу об оставшихся параметрах функции OrderSend().
Параметры функции OrderSend() - продолжение
В прошлых выпусках я рассказал о функции OrderSend() и начал рассмотрение ее параметров. В прошлом выпуске мною были рассмотрены параметры cmd и price.
В этом выпуске я расскажу о параметрах StopLoss, TakeProfit и expiration.
Когда Вы открываете позициию (выставляете отложенный ордер), то ордера 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;
}
В следующем выпуске я расскажу о примере использования функции OrderSend().
Пример использования функции OrderSend()
Сегодня, разбирая почту, я наткнулся на очень давнее письмо, в которым один из читателей моего блога попросил мне помочь в написании такого эксперта:
Советник должен в определенное время (параметры 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);
}
//+------------------------------------------------------------------+
В следующем выпуске я разберу этот эксперт по "косточкам".
Пример использования функции OrderSend() - продолжение
В прошлом выпуске я написал по просьбе одного из читателей моего блога эксперт, в котором воплощена следующая логика:
Советник должен в определенное время (параметры MyHour и MyMinute) на заранее заданном расстоянии от текущей цены (параметр MyPendingLevel) выставлять два ордера - Sell Stop и Buy Stop. При этом должны выставляться ордера Stop Loss на расстоянии MySL пипсов от цены ордера и Take Profit на расстоянии MyTP пипсов. |
В этом выпуске я хотел бы разобрать подробно логику работы этого эксперта.
Прежде всего этот эксперт имеет несколько параметров:
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()), - значит мы уже сегодня ордера выставляли,