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

Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I)

MetaTrader 5Примеры | 18 октября 2022, 08:52
1 054 0
Daniel Jose
Daniel Jose

1.0 - Введение

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

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

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


2.0 - Реализация

Чтобы начать наше путешествие в мир данной статьи, давайте начнем с исправления недостатка, который делает советник настоящим УНИЧТОЖИТЕЛЕМ денег. Опять же, если вы не будете постоянно менять точку входа, то этот недостаток вас не коснется. Но на всякий случай я советую вам серьезно подумать об исправлении кода. Даже несмотря на то, что в прикрепленном коде исправление уже будет реализовано, но вы можете подумать, что это повредит советнику, ведь он потеряет некоторую производительность, и это правда. Однако я спрашиваю вас, что лучше: потерять немного производительности или иметь риск потерять деньги на плохо сделанном входе?


2.0.1 - Ошибка точки входа

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

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

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

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

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

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Хотя это решение частично решает проблему, оно не решает ее полностью, например, в случае с ордерами BUY STOP и SELL STOP проблема решается всего лишь добавлением этих простых строк. Однако для ордеров BUY LIMIT и STOP LIMIT сервер немедленно исполнит ордер, как только мы сделаем клик, чтобы изменить точку входа, и что еще хуже в этом случае, мы уже войдем в позицию с убытком. В случае, если ордер настроен как пустой ордер (с лимитами прибыли или убытка) и точка Stop Loss находится за пределами ценовых лимитов, то помимо того, что сервер немедленно исполнит ордер, он также закроет его сразу после этого, что будет означать полную катастрофу на нашем торговом счете. Именно поэтому торговые системы так сложны в разработке, ведь обычно мы проводим несколько тестов на счете DEMO, и если всё вроде бы работает, переходим на счет REAL, и в этот момент мы начинаем терять деньги, не зная, что происходит на самом деле.

Я повторю это еще раз: Эта ошибка НЕ ВЛИЯЕТ на того, кто помещает точку входа на график и не изменяет ее. Проблема возникает, когда эта точка перемещается трейдером.

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

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

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Делается это следующим образом: Когда мы собираемся изменить точку входа отложенного ордера, мы проверяем, является ли ордер, находящийся в СТАКАНЕ ЦЕН, типом STOP LIMIT или BUY LIMIT, и если это не так, то поток исполнения продолжится до другой точки кода. Однако, если ордер имеет указанный тип, то мы делаем немедленный захват текущей цены актива, и мы будем основываться на следующих критериях: Если мы покупаем, то будем фиксировать текущее значение ASK, а если продаем, то будем использовать значение BID. Это заменяет старый метод использования значения LAST, но поскольку оно не используется на некоторых рынках, мы не будем использовать его в качестве эталона. После этого мы выполняем проверку, чтобы узнать, будет ли ордер в СТАКАНЕ ЦЕН признан недействительным или он будет только изменен.

Если ордер всё еще действителен, то система проигнорирует проверочный код и перейдет к той части, где ордер будет изменен. Но если ордер стакана цен недействителен, то система выполнит следующий код:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

Хотя это хорошо, но код выше изменит только ордеры SELL LIMIT и BUY LIMIT на SELL STOP и BUY STOP соответственно. А что если мы хотим вернуть эти типы обратно к исходным или просто предотвратить такое изменение, как нам тогда действовать в таких случаях?!

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

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Этот код не позволит вам изменить тип ордера. На самом деле вы можете изменить точку, в которой отложенный ордер будет исполнен, но вы не можете изменить ордер LIMIT на ордер STOP или наоборот. Теперь, если мы хотим продолжать бежать за ценой, чтобы заставить ее войти в определенный момент, то мы должны использовать код, показанный ниже. Это и будет код, присутствующий в советнике.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Важное замечание: При разборе этого кода следует быть очень осторожным из-за макроса ForceNewType. Обратите внимание, что этот макрос содержит break, выполнение которого приводит к выходу кода из блока case. Поэтому будьте очень осторожны при изменении или изучении этого кода.

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

Результат этих изменений можно увидеть на следующем видео:



2.0.2 - Подготовка к будущему

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

Итак, перейдем к следующему факту: Начиная со статьи Разработка советника с нуля (Часть 18), я показывал, как разработать систему ордеров, удобную в использовании для тех, кто торгует определенным активом. Но в 20-ой части этой серии система ордеров получила визуальные элементы, которые находятся там не случайно, а потому что в какой-то момент Chart Trade станет ненужным для торговли, так как всё будет указываться самой системой ордеров, и может быть очень легко изменено и настроено прямо на графике. Чтобы добраться до этой точки, нам нужно с чего-то начать, и мы сделаем это прямо сейчас.

Как насчет изменения торгуемого объема прямо на ордере, без необходимости снимать ордер с графика, изменять объем в Chart Trade, а затем переустанавливать ордер на график?! Вам стало интересно, не так ли? Именно эту характеристику мы собираемся реализовать прямо сейчас, и она очень помогает в нескольких сценариях, но я предупреждаю: вам нужно научиться и понять, как использовать систему, потому что в любой другой платформе такого не найти и, по правде говоря, я никогда не видел советника, который имел бы такую функциональность. Но давайте посмотрим на то, что вам нужно сделать, чтобы иметь эту функциональность в любом вашем советнике.

Сначала мы определим новый индекс индикатора.

#define def_IndicatorFloat      3

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

После этого мы добавим новый объект в нашу систему:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Этот объект всегда позволит нам сделать несколько вещей, пока ордер имеет отложенный вид.

Теперь мы перейдем в класс C_Object_BitMap и внесем некоторые изменения в этот класс. Первое, что необходимо сделать - это добавить несколько определений:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

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

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

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

Мы также сделаем еще одну довольно простую модификацию, на этот раз в классе C_Object_Edit:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

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

Итак, давайте вернемся к классу C_IndicatorTradeView и внесем еще несколько изменений в код. Мы собираемся создать новую функцию для системы. Это можно увидеть чуть ниже:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

Эта функция делает следующее: при ее вызове все объекты индикатора будут переименованы, то есть значение, которое указывало на тикет ордера, будет заменено другим значением, которое в данном случае является индикатором, рассмотренным в начале данной темы. Но у нас остается еще один вопрос. Я не использую никакой структуры для ведения списка объектов индикатора, я делаю это другим способом. Таким образом мы позволяем MT5 заботиться об этом списке за нас, но из-за этого я не могу создавать неограниченные плавающие ордера, поскольку у нас будет ограничение на наличие только одного плавающего ордера. Чтобы проверить это, мы используем строку:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

Проверка здесь проста: если линия индикатора расположена в любой точке, то макрос вернет значение, отличающееся от 0, поэтому мы знаем, что уже есть индикатор, использующий зарезервированный тикет. Это будет важно в дальнейшем, чтобы советник восстановил данные индикатора, которому было отказано в запросе. Но нужно помнить, что MT5 изменяет состояние объекта Bitmap автоматически, поэтому мы должны сообщить вызывающей стороне, что произошел сбой.

Следующее изменение, которое должно быть - изменение в функции, которая создает индикаторы:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

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

Следующая действительно важная для нас функция показана чуть ниже:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

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

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

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Внутренний код ...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

// ... Остальной код ....

Посмотрите, как система строит сама себя: мы программируем всё меньше и меньше, пока система растет всё больше и больше.

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

Следите за тем, чтобы все вызовы поступали из одного места. У нас есть удаление ордера из стакана цен, но он будет продолжать находиться на графике, и все манипуляции выполняются так, как было показано в предыдущих статьях. Но если мы попытаемся найти конкретное время, когда ордер вернется в стакан цен, мы можем немного потеряться в коде. Теперь, если мы посмотрим на схему, то мы поймем, что вызов происходит из функции DispatchMessage, потому что это единственное место, которое вызывает функцию SetPriceSelection, но если мы посмотрим на функцию SetPriceSelection, там нет никакой ссылки на создание ордера с индексом, используемым в плавающей системе. Но обратите внимание на одну вещь, у нас есть создание ордера по индексу 0, и это именно то, что мы используем: мы меняем тикет ордера и сообщаем, что это будет тикет с индексом 0, и таким образом, ордер будет создан. Давайте посмотрим на следующий отрывок, чтобы понять, как всё работает.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

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

Результат изменений можно увидеть на видео ниже. Посмотрите, как изменяется объем и как отправляется новый ордер в указанной точке. Таким образом управление значительно облегчается.



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

Прикрепленные файлы |
Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II) Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II)
Давайте перейдем к более полноценной системе ордеров непосредственно на графике. В этой статье я вам покажу способ исправить систему ордеров или, скорее, как сделать её более интуитивно понятной.
Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II) Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II)
В этой статье мы сделаем финальный рывок к производительности советника... так что будьте готовы к долгому чтению. Чтобы сделать наш советник надежным, мы сначала удалим из кода всё, что не является частью торговой системы.
Разработка торгового советника с нуля (Часть 28): Навстречу будущему (III) Разработка торгового советника с нуля (Часть 28): Навстречу будущему (III)
Наша система ордеров по-прежнему не справляется с одной задачей, но мы, НАКОНЕЦ, разберемся с этим. На платформе MetaTrader 5 есть система тикетов, которая позволяет нам создавать или корректировать значения ордеров. Кстати, идея состоит в том, чтобы иметь советника, который поможет нам сделать ту же систему тикетов быстрее и эффективнее.
DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта
В статье реализуем возможность изменять свойства и внешний вид элемента управления SplitContainer после его создания.