English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров

Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров

MetaTrader 5Трейдинг | 2 июня 2022, 16:47
1 205 0
Daniel Jose
Daniel Jose

Введение

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

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


Планирование

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

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

           

Изображение слева представляет собой типичный фьючерсный контракт, в данном случае MINI DOLLAR FUTURE, который стартовал несколько дней назад, как видно на графике. А график справа показывает тот же контракт и содержит дополнительные данные, которые на самом деле являются значениями истекших контрактов, поэтому график справа является графиком истории. Для анализа старых уровней поддержки и сопротивления предпочтительнее использовать именно график справа. Но проблема появляется проблема, когда мы доходим до торговли. Её можно увидеть ниже:

          

Как видим, в CHART TRADE указан торгуемый актив, даже когда мы используем историю, CHART TRADE сообщает, что можно отправить ордер, и это видно на панели инструментов. На изображении слева на графике виден созданный там ордер по текущему контракте, но на изображении справа мы видим только тот ордер, который указан в окне сообщений, а на графике ордеров не видно.

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

Важно! Здесь мы посмотрим, как создать правила, чтобы иметь возможность использовать историю для работы. В нашем случае эти правила будут ориентированы на работу с Mini Dollar (WDO) и Mini Index (WIN), которые являются частью бразильской биржи (B3). Правильное понимание позволит адаптировать правила для любого типа фьючерсного контракта с любой биржи мира.

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


Как понять правила игры

Фьючерсные контракты WDO (mini dollar), WIN (mini index), DOL (dollar future) и IND (index future) следуют очень специфическим правилам в части сроков погашения и спецификации контракта. Сначала посмотрим, как узнать срок действия контракта:


Обратите внимание на выделенные области: СИНИМ цветом показана дата истечения срока действия контракта, а красным — точный момент времени, когда контракт перестанет существовать, когда он не будет торговаться. Знать это очень важно.

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

Первые 3 буквы обозначают тип контракта:

Код Контракт
WIN Фьючерсный контракт на индекс Mini Ibovespa 
IND Фьючерсный контракт на индекс Ibovespa
WDO Фьючерсный контракт на мини доллар
DOL Фьючерсный контракт на доллар

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

Месяц истечения Буква, обозначающая WDO и DOL Буква, обозначающая WIN и IND 
Январь F  
Февраль  G  G
Март  H  
Апрель  J  J
Май  K  
Июнь  М  М
Июль  N  
Август  Q  Q
Сентябрь  U  
Октябрь  V  V
Ноябрь  X  
Декабрь  Z  Z

И, наконец, дальше идут две цифры, которые представляют год истечения контракта, поэтому в качестве примера для фьючерсного контракта в долларах с истечением срока действия в апреле 2022 года есть такой код: DOLJ22. И это будет контракт, который должны были торговать до начала мая. Когда начнётся май месяц, этот контракт будет просрочен, так как правило для WIN и IND немного отличается, срок действия контракта истекает в ближайшую к 15 числам указанного месяца среду, то есть правило по всей видимости намного сложнее, но вы заметите, что советник отлично справляется с этим и всегда сообщает о правильном контракте.


Реализация

В нашем советнике уже есть необходимые точки для получения правил, нам останется только внести некоторые настройки относительно системы подачи ордеров, поэтому приступим к работе. Первым делом в объект класса C_Terminal добавим следующий код:

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

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

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


Мы имеем в виду, что у вас уже может быть режим кросс-ордера, но он еще не полностью реализован, индикация ордера отсутствует на графическом экране. Это не так сложно сделать, как многие из вас уже могли себе представить, единственное, что нам нужно указать с помощью горизонтальных линий, но это еще не всё. Когда мы используем крос- ордер, мы теряем некоторые вещи, которые предоставляет нам MetaTrader 5, и нам нужно реализовать эту логику, чтобы система ордеров продолжала работать и была также безопасна, стабильна и надежна, иначе у нас могли бы возникнуть проблемы с использованием кросс-ордеров.

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

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


Класс C_HLineTrade

Чтобы решить эту проблему, мы собираемся создать новый класс C_HLineTrade, который заменит систему, предоставляемую MetaTrader 5 для отображения ордеров на графике. Итак, давайте не спеша взглянем на этот класс, начиная с его объявления:

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

Обратите внимание, что мы определили несколько вещей, и они будут часто использоваться в коде. Так что надо будет обратить пристальное внимание на изменения, которых на самом деле будет много. Далее следует объявить конструктор и деструктор класса:
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

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

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

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

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
Для этой функции не имеет значения, сколько объектов будет создано, существует ли объект на момент вызова. Она гарантирует, что линия будет создана и размещена в нужном месте. Эта линия заменит собой линию, изначально использовавшуюся в MetaTrader.

Для нас важна функциональность, а не красота. Линии не выбираются при создании, и вы можете изменить это поведение. Но я использую систему обмена сообщениями MetaTrader 5 для позиционирования линий. Чтобы иметь возможность перемещать их, нужно было бы указывать явно. Для указания того, какая линия изменяется, у нас есть другая функция, показанная ниже:

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
Эта функция будет поддерживать выделение линии, а когда будет выбрана другая, она отменит выбор предыдущей, это делается очень просто, но на самом деле это будет касаться только линий, обрабатываемых классом. Есть еще одна последняя функция этого класса, которую следует выделить, и она показана ниже:
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
Эта функция, пожалуй, самая важная в этом классе, так как мы не знаем, сколько линий на графике, нам нужно знать, с какой линией манипулирует пользователь, эта функция делает именно это: она сообщает системе, что данные строки - это то, с чем работает пользователь.

Хорошо, но это лишь малая часть всего, что нам нужно сделать, система еще даже не близка к тому, что быть функциональной. Поэтому давайте перейдем к следующему шагу, добавляя и изменяя функции класса C_Router, который является классом, ответственным за маршрутизацию ордера. Этот класс наследует функциональность, которую мы только что создали в классе C_HLineTrade и которую можно увидеть в следующем фрагменте:

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade


Новый класс C_Router

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

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

u:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

Раньше эта функция собирала данные только об одной открытой позиции и сохраняла их в свою обсерваторию. Но теперь все резко активизируется: функция будет отображать на графике абсолютно все открытые или отложенные позиции. Это определенно является заменой системы, найденной в MetaTrader. Поскольку здесь всё серьезно, эту функцию нужно очень хорошо понимать, потому что если она выйдет из строя, в дело вступит вся система кросс-ордеров. Итак, прежде чем мы будем торговать на реальном счете, давайте запустим ее на демо-счете для тестирования. Такие вещи нужно ТЕСТИРОВАТЬ и еще раз ТЕСТИРОВАТЬ, пока мы не будем абсолютно уверены, что всё работает как нужно. Давайте настроим всё необходимое, потому что работа этой системы немного отличается от того, как работает MetaTrader 5.

Обратите пристальное внимание на выделенные строки и ответьте честно: понятно ли, что они на самом деле делают? Причина, по которой эти две строки находятся здесь, будет понятна, когда будем говорить о классе C_OrderView чуть позже в этой статье. Без этих двух строк код очень нестабилен и работает странно. А кроме выделенной части, вся остальная часть функции очень проста — она создает через объект класса C_HLineTrade каждую из линий. В этом случае есть только одна линия, которую нельзя выбрать, и это легко указать, как показано во фрагменте:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

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

C_TemplateChart Chart;

// ... Код советника ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

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

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

Так как система теперь анализирует не только один ордер, нужно понимать еще кое-что: как понять результат операций? Почему что это важно? Когда у вас есть только одна открытая позиция, системе легко понять, но по мере увеличения количества открытых позиций, надо разбираться в том, что происходит. Функция, которая сообщает об этом, показана ниже:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}

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

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                        
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

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

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

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
Первая отвечает за модификацию ордера, который всё еще открыт, а другая модифицирует открытую позицию, хотя они кажутся одинаковыми, но это не так. У нас есть не менее важная функция для системы:
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

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



Давайте внимательно посмотрим на изображение выше. В окне сообщения у нас есть открытый ордер и указан актив, в котором он открыт. В CHART TRADE указан торгуемый актив. Заметим, что это тот же самый, который указан в окне сообщения; теперь давайте посмотрим, какой актив отображается на графике. Это мы можем увидеть в заголовке окна графика... но он совсем другой, это не актив на графике, это история mini index, означающий, что мы не используем систему MetaTrader 5 для нашей поддержки, мы используем систему перекрестных ордеров, которая описана в этой статье. Пока у нас есть только этот функционал, который позволяет нам показывать, где расположен ордер, но этого недостаточно, чтобы иметь функциональную систему, которая позволяет нам работать через систему перекрестных ордеров, нам нужно еще кое-что. В случае передвижения ордеров, это будет достигнуто в другом классе.


Новая функциональность в классе C_OrderView

Хотя класс объекта C_OrderView может делать несколько вещей, он еще не может обрабатывать данные открытого или отложенного ордера, однако, когда мы добавляем систему сообщений, у нас появляются другие возможности использования. Это единственное дополнение, которое мы сделаем в классе на данный момент. Полностью функцию можно увидеть ниже:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

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


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

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


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

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

Результат этой модификации кода можно увидеть на следующей анимации.


Заключение

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

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

Прилагаю код всех советников на данный момент.


Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10383

Прикрепленные файлы |
EA.zip (12013.17 KB)
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка" DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
В статье создадим класс объекта элемента управления WinForms "Текстовая метка". Такой объект будет иметь возможность позиционирования в любом месте своего контейнера, а его собственный функционал будет повторять некоторый функционал текстовой метки MS Visual Studio — мы сможем задать для выводимого текста параметры шрифта.
Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам
Как получить доступ к пользовательским индикаторам непосредственно в советнике? Торговый советник будет действительно полезен только в том случае, если в нем можно будет использовать пользовательские индикаторы, иначе это будет просто набор кодов и инструкций.
Разработка торгового советника с нуля (Часть 12): Время и торговля (I) Разработка торгового советника с нуля (Часть 12): Время и торговля (I)
Сегодня мы создадим Time & Trade с быстрой интерпретацией для чтения потока ордеров. Это первая часть, в которой мы будем строить эту систему. В следующей статье мы дополним систему недостающей информацией, поскольку нам потребуется добавить в код нашего эксперта несколько новых вещей.
Нейросети — это просто (Часть 16): Практическое использование кластеризации Нейросети — это просто (Часть 16): Практическое использование кластеризации
В предыдущей статье мы построили класс для кластеризации данных. В этой статье я хочу с вами поделиться вариантами возможного использования полученных результатов для решения практических задач трейдинга.