Доброго времени суток.
В этом уроке мы продолжаем тему создания индикатора для анализа открытых ордеров в терминале. Мы немного модифицируем ту версию, что разобрали на прошлом занятии, чтобы получше познакомиться с внешними переменными и фильтрами для отбора ордеров. Поэтому если вы не читали прошлый пост, перейдите сейчас на него по этой ссылке. Хочу заметить, что вы можете продолжить работать с уже созданным кодом из того занятия, либо же, для закрепления материала, написать его снова вместе со мной.
Ознакомились? Тогда начнем модификацию пройденного кода.
Доработка индикатора анализа ордеров
При анализе открытых ордеров в терминале порой необходимо отфильтровать информацию по разным параметрам, чтобы исключить лишние варианты. Этим мы и займемся.
Для вашей же пользы я рекомендую не бездумно копировать выложенный мною код, а перепечатывать вручную строчку за строчкой. Такой способ поможет вам заполнить куда больше информации.
Для начала на демо счете откройте по двум рандомным валютным парам любое количество ордеров на покупку и продажу: рыночных и отложенных. Откройте график одной из этих пар, на него будем устанавливать индикатор и проверять в процессе написания код на работоспособность.
Создаем новый индикатор (либо открываем уже написанный). Функцию обработки событий OnInit оставляем пустой, в функции OnDeinit пишем только одну строчку — очистку комментария:
0 1 2 3 |
void OnDeinit(const int reason) { Comment(""); } |
Разобьем блок с отложенными ордерами в функции OnCalculate отдельно для покупок и продаж. Для этого изменим переменные счетчиков и подсчета лота. У нас добавится четыре переменные со словом pending:
0 1 2 |
int cnt_buy = 0, cnt_sell = 0, cnt_pendings_B = 0, cnt_pendings_S = 0; double lot_buy = 0, lot_sell = 0, lot_pendings_B = 0, lot_pendings_S = 0; double profit_buy = 0, profit_sell = 0; |
Переходим к циклу перебора. Так как теперь отложенные ордера будут учитываться отдельно для покупок и продаж, то нужно изменить условие анализа ордеров. Для отложек на покупки существует два типа ордеров (OrderType): OP_BUYLIMIT — то есть лимитные и OP_BUYSTOP — то есть стоповые. То же самое и для продаж (OP_SELLLIMIT и OP_SELLSTOP). К каждому условию добавляем соответствующие переменные для сохранения их значений в памяти. Не забываем через клавишу Tab делать отступы для корректного оформления кода.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
int AllOrders = OrdersTotal(); for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderType() == OP_BUY) { cnt_buy++; lot_buy += OrderLots(); profit_buy += OrderProfit()+OrderSwap()+OrderCommission(); } else if(OrderType() == OP_SELL) { cnt_sell++; lot_sell += OrderLots(); profit_sell += OrderProfit()+OrderSwap()+OrderCommission(); } if(OrderType() == OP_BUYLIMIT || OrderType() == OP_BUYSTOP) { cnt_pendings_B++; lot_pendings_B += OrderLots(); } else if(OrderType() == OP_SELLLIMIT || OrderType() == OP_SELLSTOP) { cnt_pendings_S++; lot_pendings_S += OrderLots(); } } |
Нужно заметить, что всего существует 6 типов ордеров — три для покупок и три для продаж, начало нумерации с нуля:
Константа | Значение | Описание |
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, либо же OrderType() == 2, это будут равнозначные выражения, но для удобства лучше использовать текстовые константы, чтобы потом было проще читать такой код.
Интересно заметить, что пополнение баланса, как и снятие средств во вкладке «История» терминал учитывает тоже как ордер. Такая операция имеет порядковый номер шесть: OrderType() == 6.
Оболочка для перебора и сохранения данных всех ордеров, которые есть на рынке готова. Теперь нужно подкорректировать вывод информации на график с помощью Comment. В текущей реализации мы будем отображать данные на графике только, если счетчик по типу ордера будет больше нуля. Ниже тела цикла for пишем код для покупок:
0 1 2 3 4 5 6 7 8 |
string q = "\n Информация по ордерам символа " + Symbol(); if(cnt_buy > 0) { //если есть рыночные ордера на покупки q += "\n\n ПОКУПКИ" + "\n Кол-во ордеров: " + IntegerToString(cnt_buy) + "\n Суммарный лот: " + DoubleToString(lot_buy,2) + (profit_buy >= 0 ? "\n Суммарная прибыль: " : "\n Суммарный убыток: ") + DoubleToString(profit_buy,2) + " " + AccountCurrency(); } |
Соответственно, если cnt_buy > 0, значит в рынке есть Buy ордера и вывод информации необходим. Проделываем ту же операцию для продаж, а также отдельно для отложенных Buy и Sell типов ордеров.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
if(cnt_sell > 0) { //если есть рыночные ордера на продажу q += "\n\n ПРОДАЖИ" + "\n Кол-во ордеров: " + IntegerToString(cnt_sell) + "\n Суммарный лот: " + DoubleToString(lot_sell,2) + (profit_sell >= 0 ? "\n Суммарная прибыль: " : "\n Суммарный убыток: ") + DoubleToString(profit_sell,2) + " " + AccountCurrency(); } if(cnt_pendings_B > 0) { //если есть отложенный ордера на покупки q += "\n\n ОТЛОЖЕННЫЕ ОРДЕРА НА ПОКУПКИ" + "\n Кол-во ордеров: " + IntegerToString(cnt_pendings_B) + "\n Суммарный лот: " + DoubleToString(lot_pendings_B,2); } if(cnt_pendings_S > 0) { //если есть отложенный ордера на продажу q += "\n\n ОТЛОЖЕННЫЕ ОРДЕРА НА ПРОДАЖИ" + "\n Кол-во ордеров: " + IntegerToString(cnt_pendings_S) + "\n Суммарный лот: " + DoubleToString(lot_pendings_S,2); } q += "\n\n ИТОГО" + "\n Кол-во ордеров: " + IntegerToString(cnt_buy+cnt_sell+cnt_pendings_B+cnt_pendings_S) + "\n Суммарный лот: " + DoubleToString(lot_buy+lot_sell+lot_pendings_B+lot_pendings_S,2) + (profit_buy+profit_sell >= 0 ? "\n Суммарная прибыль: " : "\n Суммарный убыток: ") + DoubleToString(profit_buy+profit_sell,2) + " " + AccountCurrency() + " (" + DoubleToString((profit_buy+profit_sell)/AccountBalance()*100,2) + "%)"; Comment(q); //выводим информацию на график |
Первая часть корректировки кода завершена. Компилируем и смотрим, чтобы не было ошибок. Просматриваем индикатор на графике, для проверки закрываем все рыночные Buy ордера и убеждаемся, что блок для покупок исчезает.
Все проверили? Все работает? Начнем добавлять разные фильтры.
Добавляем фильтры
Для начала создадим внешнюю переменную Choose Order Symbol в самом начале кода вне теле функций для того, чтобы у пользователя был выбор какие ордера учитывать — все или же только по текущему символу. Тип данных переменной будет ENUM, его мы проходили на уроке 1.10 Переменные в разделе Input переменные. Пользовательский ENUM и внешняя переменная будут иметь вид:
0 1 2 3 4 5 |
enum O_Symbol { Current = 1, //Current Symbol All_S = 2 //All Symbols }; extern O_Symbol OrderSymbols = 1; //Choose Order Symbol |
Как мы помним, для пользователя отображается не наименование самой переменной, а тот текст, который стоит после знака // комментария, если он есть в наличии. Соответственно, в списке выбора значения для Choose Order Symbol, будет два варианта: Current Symbol (текущий символ) или All Symbols (все валютные пары). Теперь нужно этот фильтр добавить в код цикла for:
0 1 2 3 |
for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderSymbols == Current && OrderSymbol() != Symbol()) continue; ... |
В строке #2 получилось условие if из двух выражений: первое проверяет, чтобы OrderSymbols (для пользователя Choose Order Symbol) был равен текущему символу (enum Current), а второе, чтобы символ ордера не был равен текущему символу графика. Получается, что если мы захотим увидеть в итоге только текущий символ в расчете, а пара из списка не будет с ним совпадать — мы пропускаем итерацию с помощью оператора continue. Если же OrderSymbols будет равен All Symbols, то условный оператор if вернет false и остановки данной итерации не будет, работа продолжится. В формуле мы используем только одно из значений enum (Current), второе в данном случае не нужно, если его выбрать в настройках, то условие так и так не выполнится. Надеюсь понятно объяснил.
Следующий фильтр добавим по номеру тикета. Для этого добавим внешнюю переменную Tickets типа string. В настройках через пробел или запятую пользователь сможет ввести необходимые ему тикеты, которые нужно проанализировать. По умолчанию мы оставим кавычки пустыми.
0 |
extern string Tickets = ""; //Tickets List |
Добавляем следующий условный оператор if в #3 строчку кода:
0 1 2 3 4 |
for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderSymbols == Current && OrderSymbol() != Symbol()) continue; if(Tickets != "" && StringFind(Tickets,IntegerToString(OrderTicket()),0) == -1) continue; ... |
Тут также проверяется два условия. Первое, если кавычки не пустые, т.е. если пользователь внес какие-то данные о тикетах в настройки. Если они остаются пустые, оператор вернет false и фильтр игнорируется. Если же нет, то второе условие с помощью строковой функции StringFind производит поиск тикета текущего выбранного ордера среди пользовательских данных. Соответственно, если пользователь внес свои тикеты, но текущий тикет не совпал ни с одним из них (функция вернула значение -1), то итерация пропускается, фильтр сработал.
Конечно, вряд ли вам нужно будет фильтровать ваши рыночные ордера по тикетам, у этого действия мало применений, мы же его добавили только для практики написания кода. Хотя, возможно, вы захотите отслеживать только 2-3 конкретных ордера и смотреть их суммарную плавающую прибыль или убыток.
Идем дальше. Следующий фильтр аналогичен предыдущему, только он фильтрует по magic номерам. Создаем внешнюю переменную MagicNumbers также типа string:
0 |
extern string MagicNumbers = ""; //Magic Numbers List |
В тело цикла for следующей строкой дописываем похожее условие:
0 1 2 3 4 5 |
for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderSymbols == Current && OrderSymbol() != Symbol()) continue; if(Tickets != "" && StringFind(Tickets,IntegerToString(OrderTicket()),0) == -1) continue; if(MagicNumbers != "" && StringFind(MagicNumbers,IntegerToString(OrderMagicNumber()),0) == -1) continue; ... |
Если пользователь внес какие-то магики в настройках, они будут проверяться и фильтроваться. Так как мы для проверки индикатора открыли все ордера вручную, то они будут иметь магик номер равный 0. Для проверки работоспособности этого условия вы можете с помощью нашего скрипта открыть несколько рыночных ордеров с заданным вами магиком и потом отфильтровать их индикатором.
Следующий фильтр будет решать, какой тип ордеров нужно отображать: только покупки, только продажи или все. Для этого создадим второй enum и добавим внешнюю переменную OrderDirection:
0 1 2 3 4 5 6 |
enum O_Direction { _Buy = 0, //BUY _Sell = 1, //SELL _All = 2 //ALL }; extern O_Direction OrderDirection = 2; //Choose Order Direction |
Далее необходимо видоизменить тело цикла for для разных типов ордеров, добавив параметр OrderDirection в условия:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderSymbols == Current && OrderSymbol() != Symbol()) continue; if(Tickets != "" && StringFind(Tickets,IntegerToString(OrderTicket()),0) == -1) continue; if(MagicNumbers != "" && StringFind(MagicNumbers,IntegerToString(OrderMagicNumber()),0) == -1) continue; if(OrderType() == OP_BUY && OrderDirection != _Sell) { cnt_buy++; lot_buy += OrderLots(); profit_buy += OrderProfit()+OrderSwap()+OrderCommission(); } else if(OrderType() == OP_SELL && OrderDirection != _Buy) { cnt_sell++; lot_sell += OrderLots(); profit_sell += OrderProfit()+OrderSwap()+OrderCommission(); } ... |
В строчках #6 и #11 с помощью логической операции && добавилась переменная OrderDirection. Соответственно для того, чтобы было соблюдено условие подсчета покупок, нам нужно, что тип выбранного ордера пользователем в настройках параметра OrderDirection не был равен Sell. Если он не соответствует продажам, значит он либо на покупки, либо для всех типов ордеров, что нас устраивает. Аналогично для ордеров на продажу проверяется условие, чтобы OrderDirection не был равен Buy. То же самое проделываем и с отложками, предварительно вынеся условия с логическим оператором || в отдельные скобки:
0 1 2 3 4 5 6 7 |
if((OrderType() == OP_BUYLIMIT || OrderType() == OP_BUYSTOP) && OrderDirection != _Sell) { cnt_pendings_B++; lot_pendings_B += OrderLots(); } else if((OrderType() == OP_SELLLIMIT || OrderType() == OP_SELLSTOP) && OrderDirection != _Buy) { cnt_pendings_S++; lot_pendings_S += OrderLots(); } |
Готово. Осталось внести последнюю внешнюю переменную ShowPendings типа bool, задача которой будет разрешать учитывать в расчете отложенные ордера или нет.
0 |
extern bool ShowPendings = true; //Show Pendings |
Условие с переменной ShowPendings появится в #17 строчке. Теперь вы сможете сами решить, нужны ли вам данные по отложенным ордерам или нет. В итоге полностью готовый цикл будет иметь следующий вид:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
int AllOrders = OrdersTotal(); for(int i=0; i<AllOrders; i++) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderSymbols == Current && OrderSymbol() != Symbol()) continue; if(Tickets != "" && StringFind(Tickets,IntegerToString(OrderTicket()),0) == -1) continue; if(MagicNumbers != "" && StringFind(MagicNumbers,IntegerToString(OrderMagicNumber()),0) == -1) continue; if(OrderType() == OP_BUY && OrderDirection != _Sell) { cnt_buy++; lot_buy += OrderLots(); profit_buy += OrderProfit()+OrderSwap()+OrderCommission(); } else if(OrderType() == OP_SELL && OrderDirection != _Buy) { cnt_sell++; lot_sell += OrderLots(); profit_sell += OrderProfit()+OrderSwap()+OrderCommission(); } if(ShowPendings) { //оператор проверяет, чтобы было разрешено использовать отложки if((OrderType() == OP_BUYLIMIT || OrderType() == OP_BUYSTOP) && OrderDirection != _Sell) { cnt_pendings_B++; lot_pendings_B += OrderLots(); } else if((OrderType() == OP_SELLLIMIT || OrderType() == OP_SELLSTOP) && OrderDirection != _Buy) { cnt_pendings_S++; lot_pendings_S += OrderLots(); } } } |
Всё, все правки и дополнения заложены в код. Блок комментария менять не нужно, он реагирует на счетчик ордеров, который зависит от прохождения всех циклов. Осталось проверить ваш код, чтобы он корректно скомпилировался.
Я предлагаю вам поэкспериментировать с этими настройками, возможно вы придумаете какие-то свои дополнительные фильтры для открытых ордеров.
Полный код с подробными комментариями моих действий находится, как всегда, по ссылке ниже. Спасибо за внимание.
[download url=»http://www.davinci-fx.com/wp-content/uploads/2021/01/2.3-Open-Orders-Analyze-2.rar» title=»Скачать код урока»]