Учёт ордеров в большой программе

Сергей Ковалев | 1 июня, 2006


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

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

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

Попробуем разобраться как лучше поставить учёт ордеров в большой программе.


1. Характеристика среды и общие рассуждения.

Остановимся кратко на характеристиках среды, в которой будет работать наша программа.

1. Прежде всего, конечно, мы хотим знать (то есть предполагается, что данные будут востребованы программой) сколько и каких ордеров у нас имеется. Причем недостаточно просто сказать, что у нас есть, скажем, 3 ордера Sell и 4 ордера Buy. Скорее всего сколько-нибудь интеллектуальная торговая система содержит алгоритм управления характеристиками ордеров - положением StopLoss и TakeProfit, а для отложенных ордеров ещё и положением самого ордера - OpenPrice. Кроме того, нам понадобится знание того, сколько стоит каждый ордер, какой у него срок жизни, какой программой он создан.

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

3. К общим характеристикам можно отнести валютный инструмент, в окне которого установлен эксперт.

Учитывая эти простые сведения мы можем определить общие принципы построения программы, содержащей учёт ордеров.

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

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

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


2. Структура программы.

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

// My_expert.mq4
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
#include ...
#include <Peremen.mq4>          // Описание переменных эксперта.   
#include <Terminal.mq4>         // Присоединение функции Terminal.
#include <Sobytiya.mq4>         // Присоединение функции Sobytiya.
#include ...
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
//
// 
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int init()  
   {
   ...                                       // Код функции init()
   return;
   } 
//=================================================================
int start() 
   {
   Terminal();                   // Эта функция стоит первой в ..
                                 // ..последовательности функций
   ...                           // Последующий код функции start()
   Sobytiya();                   // Функция обработки событий
   ...                           // Последующий код функции start()
   return;                                                                 
   }
//=================================================================
int deinit() 
   {   
   ...                                    // Код функции deinit()
   return;
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

Файл Peremen.mq4, описывающий переменные, должен содержать описание массивов, несущих сведения об ордерах.

//  Peremen.mq4
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
//=================================================================
int
...
//=================================================================
double
...
Mas_Ord_Tek[31][8], Mas_Ord_Old[31][8], 
// Массив ордеров текущий и старый
// 1й индекс = порядковый номер ордера в этом массиве
// [][0] не определяется
// [][1] курс откр. ордера   (абсолютное знач. курса)
// [][2] StopLoss ордера     (абсолютное знач. курса)
// [][3] TakeProfit ордера   (абсолютное знач. курса)
// [][4] номер ордера        
// [][5] колич. лотов ордера (абсолютное знач. курса)
// [][6] тип ордера 1=B,2=S,3=BL,4=SL,5=BS,6=SS
// [][7] Магическое число ордера
...
//=================================================================
string 
...
//=================================================================
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

При определении глобальных переменных в небольших программах их обычно прописывают непосредственно перед первой встречающейся функцией. В средних и больших программах имеет смысл собрать все глобальные переменные в один файл и присоединить его к эксперту посредством инструкции #include. С точки зрения работоспособности кода разница в способах не имеет значения, но с точки зрения удобства пользования присоединение файла предпочтительно. В данном примере файл Peremen.mq4 присоединён в эксперт именно таким способом.


3. Массивы.

В файле Peremen.mq4 в качестве глобальных переменных определено два массива:

Mas_Ord_Tek[31][8] - массив ордеров текущий;

Mas_Ord_Old[31][8] - массив ордеров старый.

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

Почему используется двумерный массив? Потому, что мы будем вести учёт одновременно нескольких ордеров, причём о каждом из них будем хранить информацию по нескольким характеристикам. Первый индекс - порядковый номер ордера в массиве, второй - ордерная характеристика по заданному критерию. В данном примере первый индекс допускает присутствие в терминале не более 30 ордеров. Вы можете по своему усмотрению произвольно изменить эту цифру, предполагая учёт одновременно присутствующих в терминале, например, 50-и или 100 ордеров. Разумеется, это имеет смысл делать только в том случае, если Ваша торговая система предполагает работать с таким количеством ордеров. В обычной ситуации используются 1 - 2 ордера, в более редких случаях - 4. 30 по моим представлениям - это уже очень много.

Почему в индексных скобках массивов указано значение 31 и 8, а не 30 и 7? Дело в том, что массивы в MQL4 начинают отсчёт индексов с нуля. Использование нулевых ячеек для рядовых элементов далеко не всегда оправдано. На мой взгляд логично поставить в соответствие порядковый номер ордера порядковому номеру элемента массива, например, третий по счёту ордер должен находиться в строке, индекс которой 3. Фактически по количественному счёту это будет четвёртая строка, но индекс у неё 3, так как у самой первой строки - индекс 0.

Рассмотрим таблицу, в которой наглядно представлено внутреннее наполнение массивов. Предположим, что имеется всего 3 ордера: Buy, Sell и BuyStop разного качества.


Содержательная информация об ордерах располагается в ячейках массива с номерами от [1][1] до [7][30]. Кроме того, в ячейку с индексом [0][0] мы поместим общее количество ордеров, содержащихся в массиве. В данном случае - 3. В дальнейших расчётах эта цифра понадобится для организации циклов, в которых будет производиться анализ текущего состояния.

Таким образом мы можем хранить информацию о 30-и ордерах, каждый из которых отмечен по 7-ми характеристикам.

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

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

Массив, содержащий сведения обо всех ордерах, может быть организован так:


Информация об ордерах по каждому валютному инструменту хранится в одной индексной плоскости подобно рассмотренному ранее примеру. Отличие состоит в том, что в данном случае таких плоскостей несколько ( на рисунке представлено 3 - жёлтая, розовая и зелёная). Общее их количество равно количеству валютных пар, с которыми мы предполагаем работать плюс ещё одна, нулевая. В этой индексной плоскости (серого цвета), в ячейке с индексом [0][0][0] хранится единственное значение - общее количество ордеров.

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

//  Peremen.mq4
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
//============================================================================
int
...
//============================================================================
double
...
Mas_Ord_Tek[26][31][8], Mas_Ord_Old[26][31][8], // Массив ордеров текущий и старый
                                 // 1й индекс = порядковый номер валютного инструмента
                                 // 2й индекс = порядковый номер ордера ..
                                 // .. на плоскости валютного инструмента
                                 // [][][0] не определяется
                                 // [][][1] курс откр. ордера   (абсолютное знач. курса)
                                 // [][][2] StopLoss ордера     (абсолютное знач. курса)
                                 // [][][3] TakeProfit ордера   (абсолютное знач. курса)
                                 // [][][4] номер ордера        
                                 // [][][5] колич. лотов ордера (абсолютное знач. курса)
                                 // [][][6] тип ордера 1=B,2=S,3=BL,4=SL,5=BS,6=SS
                                 // [][][7] Магическое число ордера
//=================================================================
string 
      Instrument[26];          // Массив названий валютных инструментов
...
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
//
//
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int Predopred()
   {
//=============================================== Предопределения =
   Instrument[1] = "EURUSD";
   Instrument[2] = "GBPUSD";
   ...
   Instrument[25]= "AUDCAD";
//==================================================================
   return();
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

В нижней части блока открытия переменных этого файла открыт массив Instrument[26].

В большой программе как правило имеется множество переменных, значения которых по ходу выполнения программы не меняются. В данном случае к ним относятся элементы массива Instrument[]. Для удобства программирования такие переменные имеет смысл собрать в одну функцию, можно также оформить её в виде отдельного файла, присоединяемого к программе с помощью #include.

В данном примере показано предопределение элементов массива Instrument[] в функции Predopred() содержащейся в том же файле Peremen.mq4. Эту функцию достаточно запустить на выполнение всего один раз, поэтому имеет смысл включить её в программу в специальной функции init():

int init()  
   {
   Predopred();            // Предопределение некоторых перем
   return;
   }

Таким образом некоторые целые числа, в данном случае содержащиеся в значениях индексов массива Instrument[], поставлены в соответствие валютным инструментам.


4. Функция учёта ордеров.

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

// Terminal.mq4
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int Terminal()
   {
//============================================== Предопределения ==
   ArrayCopy(Mas_Ord_Old, Mas_Ord_Tek);
// Сохраняем предыдущую историю
   int Kol=0;                         // Обнуление счётчика ордеров
   ArrayInitialize(Mas_Ord_Tek,0);    // Обнуление массива
//=============================================== Анализ ордеров ==
   for (int i=0; i<OrdersTotal(); i++)// По всем ордерам терминала
     {
      if((OrderSelect(i, SELECT_BY_POS)==true) && 
          (OrderSymbol()==Symbol()))
                                //Если есть следующ и наша вал.пара
       {                                                                     
        Kol++;                  // Считаем общее количество ордеров
//-------------------------- Формирование нового массива ордеров --
        Mas_Ord_Tek[Kol][1] = NormalizeDouble(OrderOpenPrice(),
                                              Digits); 
// Курс открытия ордеров
        Mas_Ord_Tek[Kol][2] = NormalizeDouble(OrderStopLoss(),
                                              Digits); 
// Курс SL 
        Mas_Ord_Tek[Kol][3] = NormalizeDouble(OrderTakeProfit(),
                                              Digits); 
// Курс ТР 
        Mas_Ord_Tek[Kol][4] = OrderTicket();     // Номер ордера
        Mas_Ord_Tek[Kol][5] = OrderLots();       // Количество лотов
        Mas_Ord_Tek[Kol][6] = OrderType();       // Тип ордера
        Mas_Ord_Tek[Kol][7] = OrderMagicNumber();// Магическое число 
//------------------------------------------------------------------
       }
     }
   Mas_Ord_Tek[0][0] = Kol; // Сохраняем в нулевую ячеечку
//==================================================================
   return();
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

Первое, что необходимо сделать с момента, когда система передала управление функции Terminal() - это сохранить предыдущее состояние ордеров. Оно содержится в массиве Mas_Ord_Tek[][], но на текущий момент не является актуальным, так как отражает состояние ордеров, сложившееся на предыдущем тике. Текущее состояние на начальный момент ещё не известно.

В строке

   ArrayCopy(Mas_Ord_Old, Mas_Ord_Tek);
// Сохраняем предыдущую историю

последнее известное состояние ордеров передаётся в массив Mas_Ord_Old[][].

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

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

   for (int i=0; i<OrdersTotal(); i++)
// По всем ордерам терминала

Обратите внимание, OrdersTotal() определяет количество ордеров, а исчисление порядковых номеров ордеров в терминале начинается с 0. Поэтому в строке цикла используется знак "<" и изменение внутренней переменной цикла начинается с нуля: i=0.

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

//------------------------- Формирование нового массива ордеров --
       Mas_Ord_Tek[Kol][1] = NormalizeDouble(OrderOpenPrice(),
                                             Digits); 
// Курс открытия ордера
       Mas_Ord_Tek[Kol][2] = NormalizeDouble(OrderStopLoss(),
                                             Digits); 
// Курс SL
       Mas_Ord_Tek[Kol][3] = NormalizeDouble(OrderTakeProfit(),
                                             Digits); 
// Курс ТР
       Mas_Ord_Tek[Kol][4] = OrderTicket(); // Номер ордера
       Mas_Ord_Tek[Kol][5] = OrderLots();   // Количество лотов
       Mas_Ord_Tek[Kol][6] = OrderType();   // Тип ордера
       Mas_Ord_Tek[Kol][7] = OrderMagicNumber();// Магическое число 
//-----------------------------------------------------------------

Попутно наполняемый счётчик по окончании цикла передаёт своё значение нулевому элементу массива.

   Mas_Ord_Tek[0][0] = Kol;    // Сохраняем в нулевую ячеечку

Для случая одновременной торговли по нескольким валютным инструментам функция Tеrminal() может быть составлена так:

// Terminal.mq4
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int Terminal()
   {
//============================================== Предопределения =
   ArrayCopy(Mas_Ord_Old, Mas_Ord_Tek);
// Сохраняем предыдущую историю
   ArrayInitialize(Mas_Ord_Tek,0); 
// Обнуление массива текущих орд.
//=============================================== Анализ ордеров =
   for (int i=0; i<OrdersTotal(); i++)// По всем ордерам терминала
      {
      if(OrderSelect(i, SELECT_BY_POS)==true)
// Если ещё есть живой ордер
         {
//-------------------- Определение индекса валютного инструмента -
         string Symb=OrderSymbol();
// Выясним валюту текущего ордера
         for (int ind=1; ind<=25; ind++)
// Поиск по массиву вал. инстр.
            {
            if (Symb==Instrument[ind])
// Индекс определён, поиск закончен
               break;                                   
// Выходим из цикла по ind
            }
//---------------------- Формирование нового массива ордеров ----
         Mas_Ord_Tek[0][0][0]++;
// Считаем общее количество ордеров
         Mas_Ord_Tek[ind][0][0]++;
// Считаем колич.орд. по валют.паре
         int k=Mas_Ord_Tek[ind][0][0]; // Формальная переменная
         
         Mas_Ord_Tek[ind][k][1] = NormalizeDouble(OrderOpenPrice(),
                                                  Digits); 
// Курс откр. ордеров
         Mas_Ord_Tek[ind][k][2] = NormalizeDouble(OrderStopLoss(),
                                                  Digits); 
// Курс SL
         Mas_Ord_Tek[ind][k][3] = NormalizeDouble(OrderTakeProfit(),
                                                  Digits); 
// Курс ТР
         Mas_Ord_Tek[ind][k][4] = OrderTicket(); // Номер ордера
         Mas_Ord_Tek[ind][k][5] = OrderLots();   // Колич. лотов
         Mas_Ord_Tek[ind][k][6] = OrderType();   // Тип ордера
         Mas_Ord_Tek[ind][k][7] = OrderMagicNumber();
// Магическое число 
         
//-----------------------------------------------------------------
         }
      }
//=================================================================
   return();
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

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

//----------------- Определение индекса валютного инструмента ----
         string Symb=OrderSymbol();
// Выясним валюту текущего ордера
         for (int ind=1; ind<=25; ind++)
// Поиск по массиву вал. инстр.
            {
            if (Symb==Instrument[ind])
// Индекс определён, поиск закончен
               break;                 // Выходим из цикла по ind
            }

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

В представленных примерах реализации функции Terminal() при вычислении некоторых переменных используется нормализация:

Mas_Ord_Tek[ind][k][1] = NormalizeDouble(OrderOpenPrice(),Digits); 
// Курс откр. ордера
Mas_Ord_Tek[ind][k][2] = NormalizeDouble(OrderStopLoss(),Digits); 
// Курс SL
Mas_Ord_Tek[ind][k][3] = NormalizeDouble(OrderTakeProfit(),Digits); 
// Курс ТР

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

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

5. Обработка событий.

Итак, новый тик случился и функция Tеrminal() отработала. Управление будет передано операторам, стоящим непосредственно после функции Terminal(). Заранее нельзя сказать в какой части специальной функции start() должна находиться та или иная пользовательская функция обработки событий - это зависит от идеи, закладываемой в торговую систему. В простом случае обработка по крайней мере некоторых событий может осуществляться сразу же.

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

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

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

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

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

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

Рассмотрим пример реализации функции Sobytiya(), заключённой в одноимённом файле Sobytiya.mq4. Отследим простое событие - удаление или закрытие ордера.

Мы выяснили уже, что номер ордера для этой цели годится не всегда, поэтому будем анализировать MagicNumber.

// Sobytiya.mq4   
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int Sobytiya()
   {
//==================================== Поиск пропавших ордеров ====
   for (int oo=1;oo<=Mas_Ord_Old[0][0];oo++)     
// Возьмём номер ордера из старого массива
      {   
      int Sovpadenie=0;                          
// Для начала обнулим факт признака совпад.
      for (int i=1;i<=Mas_Ord_Tek[0][0];i++)     
// Поищем этот ордерок в текущем массиве 
         {
         if (Mas_Ord_Tek[i][7]==Mas_Ord_Old[oo][7]) 
// Если совпал MagicNumber ордера, то запомним, т.е пока жив
// и выходим из внутреннего цикла
            {
            Sovpadenie=1;                           
            break;                                
            }
         }      
      if (Sovpadenie==1) continue;  // Переходим к следующему старому
      Alert("Закрыт ордер ",Mas_Ord_Old[oo][4]); 
// Сообщим текстом на экран
      PlaySound( "Close_order.wav" );               
// И музычку.
      }
//=================================================================
   return();
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

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

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

// Sobytiya_2.mq4   
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
int Sobytiya_2()
   {
//============================== Поиск ордеров, поменявших тип ====
   for(int oo=1;oo<=Mas_Ord_Old[0][0]; oo++)               
// Пройдёмся по старому массиву
      {
      for (int i=1;i<=Mas_Ord_Tek[0][0];i++)              
// Поищем ордер в текущ. массиве 
         {
         if (Mas_Ord_Tek[i][4] == Mas_Ord_Old[oo][4] &&    
// Если в старом был такой же ордер но другого типа, то:
             Mas_Ord_Tek[i][6] != Mas_Ord_Old[oo][6])      
            {                                                  // Отложенный стал явным
            Alert("Трансформировался ордер",Mas_Ord_Tek[i][4]);
// Сообщим текстом на экран
            PlaySound("Transform.wav" );                       
// И музычку
            }
         }
      }
//=================================================================
   return();
   }
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж

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

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

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

   Mas_Ord_Tek[ind][k][1] = NormalizeDouble(OrderOpenPrice(),
                                            Digits+1); 
// Курс откр. орд

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

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