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

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

MetaTrader 5Примеры | 18 октября 2022, 14:35
1 104 0
Daniel Jose
Daniel Jose

1.0 - Введение

Когда система ордеров действительно начала разрабатываться, уже после статьи Разработка торгового советника с нуля (Часть 18), я не представлял, сколько времени потребуется, чтобы дойти до этого момента. Мы прошли через различные моменты, изменения, поправки и т.д. Я показал вам, как делать некоторые специфические вещи, например, как отмечать вещи или сделать более интуитивную систему, но были еще моменты, которые я не мог показать на этом этапе, потому что путь не был полностью подготовлен. И это путешествие позволило нам построить концепцию таким образом, чтобы все понимали идею и знали, как работает система.

Во всех предыдущих статьях я подготавливал почву для того, чтобы мы пришли к этой статье с одинаковым уровнем понимания того, как работает система, поэтому данный материал не был чрезвычайно запутанным или сложным. С самого начала был один вопрос, и я избегал подробно анализировать его, но очень важен для более опытных трейдеров. На первый взгляд это может показаться глупым, но когда придет время торговать, мы поймем, что нам чего-то не хватает в советнике, и тогда спросим себя "чего же здесь не хватает?". Я имею в виду способ восстановления значений take и stop, которые были удалены по какой-то причине, но мы снова хотим вернуть их на график.

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

На платформе MetaTrader 5 есть система тикетов, которая позволяет нам создавать или корректировать значения ордеров. Кстати, идея состоит в том, чтобы иметь советника, который поможет нам сделать ту же систему тикетов быстрее и эффективнее. Система MetaTrader 5 не идеальна, так как она значительно медленнее и более подвержена ошибкам, чем использование советника, который я учу вас разрабатывать.

Но до сих пор я ни разу не объяснял, как генерировать значения для уровней лимитных ордеров для TP и SL (Take Profit и Stop Loss). Я думаю, что тема удаления этих лимитов достаточно ясна и интуитивно понятна, чтобы вы точно знали, как действовать. Но как это воплотить, т.е. как мы должны действовать, чтобы установить лимиты или восстановить их прямо на графике? Это нечто весьма интригующее, что подразумевает некоторые вопросы, и, конечно же, это причина для создания данной статьи: показать один из многих способов создания этих лимитов прямо на графике, не прибегая к какому-либо внешнему ресурсу, просто используя систему ордеров советника.


2.0 - Приступаем к делу: реализация

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

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

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

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


2.0.1 - Моделирование системы перетаскивания

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

#define macroUpdate(A, B) if (B > 0) {                                                                  \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                     } else RemoveIndicator(ticket, A);
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

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

#define macroUpdate(A, B){ if (B > 0) {                                                                 \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }

Мы делаем следующее: Когда значение B, которое может быть Take или Stop, больше 0, мы проверим, находится ли индикатор на графике. Если его нет, то мы создадим его, разместим и настроим значение, которое он будет отображать. Если значение B равно 0, то мы полностью удалим индикатор с графика, и сделаем это в выделенной точке кода макроса. Но будет ли достаточно, если вместо полного удаления индикатора с графика, мы сохраним его элемент, и если этот элемент можно будет настроить для отображения того, что мы хотим сделать (то есть создать недостающий лимит(ы) на ордер или позицию), это превратит его обратно в ордер или в позицию типа OCO? ДА, этого было бы достаточно и в этом заключается идея: оставить элемент, в данном случае объект, который позволяет нам перемещать лимиты и создавать лимит, которой нам не хватает, только нам нужно будет перетащить этот элемент, и лимит будет создан. Это теоретическая основа, которую мы будем использовать для создания нашей системы.

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

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

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

        m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
        m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));

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

}

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

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

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

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

#define macroSetLinePrice(ticket, it, price)    ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)
#define macroGetPrice(ticket, it, ev)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE)

Данная модификация очень важна для остальной части системы, которую мы будем собирать. Но у вас может появиться вопрос: "Почему вы удаляете линии разреза?!" Причина в том, что нам нужно сделать систему еще более гибкой, и чтобы достичь этого зачеркнутый код был удален, а на его месте появилась выделенная строка. Но вы можете задаться вопросом, зачем нам macroGetPrice, если у нас нет макроса для присвоения значения цене. На самом деле есть только одна единственная точка, которая производит эту поправку цены, записывая ее на объект графика. Эту точку можно увидеть на фрагменте ниже:

#define macroSetPrice(ticket, it, ev, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE, price)
//---

// ... Дополнительный код внутри класса ....

//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                if (it != IT_RESULT) macroSetPrice(ticket, it, EV_MOVE, price);
                                macroSetPrice(ticket, it, EV_LINE, price);
                                macroSetAxleY(it);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 160; break;
                                        case IT_STOP: desl = 270; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2));
                        }

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

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


2.0.2 - Новая функция обновления

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

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

// ... код класса ...

#define def_ColorLineTake       clrDarkGreen
#define def_ColorLineStop       clrMaroon

// ... код класса ....

inline void CreateBtnMoveIndicator(ulong ticket, eIndicatorTrade it, color C = clrNONE)
                        {
                                string sz0 = macroMountName(ticket, it, EV_MOVE);

                                ObjectDelete(Terminal.Get_ID(), macroMountName(ticket, it, EV_MOVE));
                                m_BtnMove.Create(ticket, sz0, "Wingdings", "u", 17, (C == clrNONE ? (it == IT_TAKE ? def_ColorLineTake : def_ColorLineStop) : C));
                                m_BtnMove.Size(sz0, 21, 23);
                        }

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

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

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

#define macroUpdate(A, B){                                                                                                      \
                if (B == 0) {   if (macroGetPrice(ticket, A, EV_LINE) > 0) RemoveIndicator(ticket, A);                          \
                                if (macroGetPrice(ticket, A, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, A);                  \
                            } else if (b0 = (macroGetPrice(ticket, A, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, (B == 0 ? pr : B));                                                                \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                                        \
                        }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetPrice(ticket, IT_RESULT, EV_LINE);
                                        if ((pr == 0) && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                macroUpdate(IT_TAKE, tp);
                                macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

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

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

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

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

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

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


2.0.3 - Решение проблемы плавающих индикаторов

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;

// ... Внутренний код ...
                        
        switch (id)
        {

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

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_TYPE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1)));
                                                m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1)));
                                                m_Selection.ticket = 0;
                                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                        } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam));
                                        break;
                                case EV_DS:
                                        if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam));
                                        break;
                                case EV_CLOSE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                RemoveIndicator(def_IndicatorFloat, it);
                                                if (it != IT_PENDING) UpdateIndicators(def_IndicatorFloat, (it == IT_TAKE ? 0 : m_Selection.tp), (it == IT_STOP ? 0 : m_Selection.sl), m_Selection.vol, m_Selection.bIsBuy);
                                        }else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)

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

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


2.0.4 - Недостаток отрицательного значения Тейка

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

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

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

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

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
                        bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl,
                        MousePrice;
}m_Selection;

Но тогда вы можете задать себе такой вопрос: почему бы вам просто не изменить цену на линии мыши? Причина в том, что для корректного управлять мышью необходимо использовать системный вызов, т.е. необходимо управлять значениями позиции мыши через WINDOWS API, а это заставит нас включить использование внешних dll, что мы не хотим делать здесь и сейчас, поэтому проще установить локальное значение внутри советника, а выделенные данные будут хранить это значение за нас.

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

void MoveSelection(double price)
{
        if (m_Selection.ticket == 0) return;
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        UpdateIndicators(m_Selection.ticket, price, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateIndicators(m_Selection.ticket, m_Selection.tp, price, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_PENDING:
                        PositionAxlePrice(m_Selection.ticket, IT_PENDING, price);
                        UpdateIndicators(m_Selection.ticket, (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.vol, m_Selection.bIsBuy);
                        m_Selection.MousePrice = price;
                        break;
        }
        if (Mouse.IsVisible())
        {
                m_TradeLine.SpotLight(macroMountName(m_Selection.ticket, m_Selection.it, EV_LINE));
                Mouse.Hide();
        }
}

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,

// ... Внутренний код ....      
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Клик по левой кнопке
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //Нажатый SHIFT
                        bKeySell = (mKeys & 0x08) == 0x08;    //Нажатый CTRL
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        m_Selection.it = IT_PENDING;
                                        m_Selection.pr = price;
                                }
                                m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
                                m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));
                                m_Selection.bIsBuy = bKeyBuy;
                                m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy);
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0);
                                        bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price);
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(m_Selection.MousePrice); else MoveSelection(price);
                        }
                        break;

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

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

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0, bPen = true;
                                
        if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
        {
                bPen = (pr = macroGetPrice(ticket, IT_RESULT, EV_LINE)) == 0;
                if (bPen && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                {
                        CreateIndicator(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                        ChartRedraw();
                }
                pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                SetTextValue(ticket, IT_PENDING, vol);
        }
        b0 = UpdateIndicatorsLimits(ticket, IT_TAKE, tp, vol, pr, isBuy, bPen);
        b0 = (UpdateIndicatorsLimits(ticket, IT_STOP, sl, vol, pr, isBuy, bPen) ? true : b0);
        if (b0) ChartRedraw();
}

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

inline bool UpdateIndicatorsLimits(ulong ticket, eIndicatorTrade it, double price, double vol, double pr, bool isBuy, bool isPen)
{
        bool b0 = false;
        double d1 = Terminal.GetPointPerTick();
                
        if (
    price  == 0)
        {
                if (macroGetPrice(ticket, it, EV_LINE) > 0) RemoveIndicator(ticket, it);
                if (macroGetPrice(ticket, it, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, it);
        } else if (b0 = (macroGetPrice(ticket, it, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, it);
        switch (it)
        {
                case IT_TAKE:
                        price = (price == 0 ? 0 : (((isBuy ? price - pr : pr - price) > 0) ? price : (isBuy ? pr + d1 : pr - d1)));
                        break;
                case IT_STOP:
                        price = (price == 0 ? 0 : (isPen ? (((isBuy ? price - pr : pr - price) < 0) ? price : (isBuy ? pr - d1 : pr + d1)) : price));
                        break;
        }
        if (m_Selection.it == it) m_Selection.MousePrice = price;
        PositionAxlePrice(ticket, it, (price == 0 ? pr : price));
        SetTextValue(ticket, it, vol, (isBuy ? price - pr : pr - price));
                        
        return b0;
}

С данного момента в случае отложенного ордера значение тейка не может быть непостоянным, поскольку у нас есть максимальный лимит, разрешенный для использования в индикаторе. Разместить отложенный ордер на покупку и попытаться переместить значение тейка на НЕГАТИВНОЕ значение (т.е. ниже точки входа) больше не удастся, потому что расчет индикатора тейка будет препятствовать этому. Преимущество такого написания кода заключается в том, что, независимо от того, ордер это или позиция, значение тейка никогда не может быть отрицательным, потому что сам советник не допустит этого.

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


3.0 - Заключение

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

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



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

Прикрепленные файлы |
Разработка торгового советника с нуля (Часть 29): Говорящая платформа Разработка торгового советника с нуля (Часть 29): Говорящая платформа
В этой статье мы научимся, как заставить платформу MT5 говорить. А что если мы сделаем советника более веселым? Торговля на финансовых рынках часто является чрезвычайно скучным и монотонным занятием, но мы можем сделать эту работу менее утомительной. Этот проект может стать опасным, если у вас есть проблема, делающая вас зависимым, но на самом деле весь сценарий с модификациями может быть более увлекательным и менее скучным.
Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II) Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II)
Давайте перейдем к более полноценной системе ордеров непосредственно на графике. В этой статье я вам покажу способ исправить систему ордеров или, скорее, как сделать её более интуитивно понятной.
Машинное обучение и Data Science — Нейросети (Часть 01): Разбираем нейронные сети с прямой связью Машинное обучение и Data Science — Нейросети (Часть 01): Разбираем нейронные сети с прямой связью
Многие любят, но немногие понимают все операции, лежащие в основе нейронных сетей. В этой статье я постараюсь простым языком объяснить все, что происходит за закрытыми дверями многоуровневого перцептрона с прямой связью Feed Forward.
Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I) Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I)
Сегодня мы выведем нашу систему ордеров на новый уровень, но сначала нам нужно решить несколько задач. Сейчас у нас есть разные вопросы, которые связаны с тем, как мы хотим работать и какие вещи мы делаем в течение торгового дня.