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

Daniel Jose | 18 октября, 2022

1.0 - Введение

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

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

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


2.0 - Интуитивно понятная модель для работы

Наша система обозначения ордеров до настоящего момента осуществлялась следующим образом:

Индикаторы Take и Stop имеют такую форму, которая достаточно понятна интуитивно, так как зеленый цвет указывает на то, что значение будет зачислено на наш счет, а красный цвет указывает на то, что оно будет списано с нашего счета, что очень просто и понятно. Уже при наличии индикатора Stop, как показано ниже, у нас будет индикация того, что активация стопа приведет к появлению суммы, которая будет зачислена на наш счет. Другими словами, с лимитными индикаторами не стоит возиться, по крайней мере, на данный момент. Возможно, в будущем вы решите что-то в них изменить, но на данный момент они вполне подходят для использования.

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

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

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

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


2.0.1 - Как добавить новую информацию в индикаторы

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

#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"
#define def_BtnDayTrade         "Images\\NanoEA-SIMD\\Inf_DayTrade.bmp"
#define def_BtnSwing            "Images\\NanoEA-SIMD\\Inf_Swing.bmp"
#define def_BtnInfoBuy          "Images\\NanoEA-SIMD\\Inf_Buy.bmp"
#define def_BtnInfoSell         "Images\\NanoEA-SIMD\\Inf_Sell.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled
#resource "\\" + def_BtnDayTrade
#resource "\\" + def_BtnSwing
#resource "\\" + def_BtnInfoBuy
#resource "\\" + def_BtnInfoSell

В продолжении, нам придется только ввести 2 новых объекта в нашу систему ордеров.

//+------------------------------------------------------------------+
enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PENDING, IT_RESULT};
enum eEventType {EV_NULL, EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_PROFIT, EV_MOVE, EV_CHECK, EV_TYPE, EV_DS};
//+------------------------------------------------------------------+
C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck,
                        m_BtnInfoType,
                        m_BtnInfo_DS;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

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

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

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        if (it == IT_PENDING)
                                        {
                                                macroSwapName(it, EV_CHECK);
                                                macroSwapName(it, EV_TYPE);
                                                macroSwapName(it, EV_DS);
                                        }
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

Выделенные строки передают объекты призраку — все достаточно просто и понятно. Еще один простой код преобразует отложенные индикаторы в плавающие:

#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);
                                macroSwapAtFloat(IT_PENDING, EV_TYPE);
                                macroSwapAtFloat(IT_PENDING, EV_DS);
                                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

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

#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 ? 100 : (A == IT_PENDING ? 144 : 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);  }                                                                       \
                                                }
                                                                                                                
#define macroInfoBase(A)        {                                                                                               \
                m_BtnInfoType.Create(ticket, sz0 = macroMountName(ticket, A, EV_TYPE), def_BtnInfoBuy, def_BtnInfoSell);        \
                m_BtnInfoType.SetStateButton(sz0, m_Selection.bIsBuy);                                                          \
                m_BtnInfo_DS.Create(ticket, sz0 = macroMountName(ticket, A, EV_DS), def_BtnDayTrade, def_BtnSwing);             \
                m_BtnInfo_DS.SetStateButton(sz0, m_Selection.bIsDayTrade);                                                      \
                                }

                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);
                                                macroInfoBase(IT_PENDING);
                                                break;
                                        case IT_RESULT  :
                                                macroCreateIndicator(it, clrSlateBlue, clrSlateBlue, def_ColorVolumeResult);
                                                macroInfoBase(IT_RESULT);
                                                break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroInfoBase
#undef macroCreateIndicator

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

#define macroSetAxleY(A)                {                                                                               \
                m_BackGround.PositionAxleY(macroMountName(ticket, A, EV_GROUND), y);                                    \
                m_TradeLine.PositionAxleY(macroMountName(ticket, A, EV_LINE), y);                                       \
                m_BtnClose.PositionAxleY(macroMountName(ticket, A, EV_CLOSE), y);                                       \
                if (A != IT_RESULT)m_BtnMove.PositionAxleY(macroMountName(ticket, A, EV_MOVE), y, 1);                   \
                else m_EditInfo2.PositionAxleY(macroMountName(ticket, A, EV_PROFIT), y, 1);                             \
                m_EditInfo1.PositionAxleY(macroMountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));            \
                if (A == IT_PENDING) m_BtnCheck.PositionAxleY(macroMountName(ticket, A, EV_CHECK), y);                  \
                if ((A == IT_PENDING) || (A == IT_RESULT))      {                                                       \
                        m_BtnInfoType.PositionAxleY(macroMountName(ticket, A, EV_TYPE), y + (A == IT_PENDING ? 0 : 8)); \
                        m_BtnInfo_DS.PositionAxleY(macroMountName(ticket, A, EV_DS), y - (A == IT_PENDING ? 0: 8));     \
                                                                }                                                       \
                                        }
                                                                        
#define macroSetAxleX(A, B)             {                                                                                               \
                m_BackGround.PositionAxleX(macroMountName(ticket, A, EV_GROUND), B);                                                    \
                m_TradeLine.PositionAxleX(macroMountName(ticket, A, EV_LINE), B);                                                       \
                m_BtnClose.PositionAxleX(macroMountName(ticket, A, EV_CLOSE), B + 3);                                                   \
                m_EditInfo1.PositionAxleX(macroMountName(ticket, A, EV_EDIT), B + 21);                                                  \
                if (A != IT_RESULT) m_BtnMove.PositionAxleX(macroMountName(ticket, A, EV_MOVE), B + 80 + (A == IT_PENDING ? 52 : 0));   \
                else m_EditInfo2.PositionAxleX(macroMountName(ticket, A, EV_PROFIT), B + 21);                                           \
                if (A == IT_PENDING) m_BtnCheck.PositionAxleX(macroMountName(ticket, A, EV_CHECK), B + 82);                             \
                if ((A == IT_PENDING) || (A == IT_RESULT))      {                                                                       \
                        m_BtnInfoType.PositionAxleX(macroMountName(ticket, A, EV_TYPE), B + (A == IT_PENDING ? 100 : 82));              \
                        m_BtnInfo_DS.PositionAxleX(macroMountName(ticket, A, EV_DS), B + (A == IT_PENDING ? 118 : 82));                 \
                                                                }                                                                       \
                                        }
//---
        void ReDrawAllsIndicator(void)
                        {
                                C_IndicatorTradeView::st00 Local;
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, OBJ_EDIT);
                                ulong           ticket;
                                eIndicatorTrade it;
                                eEventType ev;
                                
                                Local = m_Selection;
                                m_Selection.ticket = 0;
                                for (int c0 = 0; c0 <= max; c0++)
                                   if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, OBJ_EDIT), ticket, it, ev))
                                      if ((it == IT_PENDING) || (it == IT_RESULT))
                                      {
                                        PositionAxlePrice(ticket, IT_STOP, macroGetLinePrice(ticket, IT_STOP));
                                        PositionAxlePrice(ticket, IT_TAKE, macroGetLinePrice(ticket, IT_TAKE));
                                        PositionAxlePrice(ticket, it, macroGetLinePrice(ticket, it));
                                        }
                                m_Selection = Local;
                                ChartRedraw();
                        }
//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetLinePrice(ticket, it, 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));
                        }
#undef macroSetAxleX
#undef macroSetAxleY

Я также подчеркиваю, что мне не нравится вносить резкие и радикальные изменения в код, единственные изменения, которые были неизбежны, выделены в коде выше.


2.0.2 - Проблемы на виду

Хотя в системе практически всё работает нормально, была обнаружена проблема, и я безуспешно искал простое решение в документации по MQL5. Проблема заключается в сложности различения вновь открытой позиции, т.е. определения того, является ли она Day Trade (короткие сделки, заключаемые в тот же день) или Swing Trade (более длительные сделки). В случае более старой позиции, открытой в предыдущий день, анализ упрощается, так как достаточно сравнить текущий день с днем открытия, т.е. если они отличаются, то позиция будет являться Swing Trade. Но что произойдет, если советник будет закрыт, а мы инициируем его в тот же день, когда была открыта позиция? Больше невозможно будет узнать, является ли позиция Day Trade или Swing Trade.

Эта проблема не существует для отложенных ордеров, поскольку для них у нас есть метод различения. При выполнении вызова OrderGetInteger с использованием параметра ORDER_TYPE_TIME, через перечислитель ENUM_ORDER_TYPE_TIME, мы узнаем, является ли ордер типом Day Trade или Swing Trade, но то же самое не происходит с позициями.

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

Для лучшего понимания давайте рассмотрим на применение данного решения.

inline char GetInfosTradeServer(ulong ticket)
{
        long info;
                                
        if (ticket == 0) return 0;
        if (OrderSelect(ticket))
        {
                if (OrderGetString(ORDER_SYMBOL) != Terminal.GetSymbol()) return 0;
                info = OrderGetInteger(ORDER_TYPE);
                m_Selection.bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                m_Selection.pr = OrderGetDouble(ORDER_PRICE_OPEN);
                m_Selection.tp = OrderGetDouble(ORDER_TP);
                m_Selection.sl = OrderGetDouble(ORDER_SL);
                m_Selection.vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                m_Selection.bIsDayTrade = ((ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) == ORDER_TIME_DAY);
                                        
                return -1;
        }
        if (PositionSelectByTicket(ticket))
        {
                if (PositionGetString(POSITION_SYMBOL) != Terminal.GetSymbol()) return 0;
                m_Selection.bIsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Selection.pr = PositionGetDouble(POSITION_PRICE_OPEN);
                m_Selection.tp = PositionGetDouble(POSITION_TP);
                m_Selection.sl = PositionGetDouble(POSITION_SL);
                m_Selection.vol = PositionGetDouble(POSITION_VOLUME);
                if (macroGetDate(PositionGetInteger(POSITION_TIME)) == macroGetDate(TimeTradeServer()))
                        m_Selection.bIsDayTrade = PositionGetString(POSITION_COMMENT) == def_COMMENT_TO_DAYTRADE;
                else m_Selection.bIsDayTrade = false;
                                        
                return 1;
        }
        return 0;
}

Как упоминалось выше, в случае отложенных ордеров достаточно сделать вызов OrderGetInteger, чтобы получить нужное нам значение. Функция, связанная с позициями, уже требует больших усилий из-за своей сложности: Мы сверяем день открытия позиции с текущим днем торгового сервера. Если оба варианта одинаковы, то мы проверим комментарий в ордере. Если комментарий указывает на строку, которая используется в классе C_Router, чтобы сообщить, что в случае открытия позиции она будет типа Day Trade, советник интерпретирует ее и сообщит об этом в индикаторе позиции. Но необходимо, чтобы комментарий не менялся до конца дня, потому что если изменение произойдет, то советник может сообщить, что позиция Day Trade на самом деле является Swing Trade, и в таком случае вина будет лежать не на советнике, а на том, что трейдер преждевременно изменил комментарий.

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

Как это выглядит с отложенными ордерами, показано на видео ниже:


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


2.0.3 - Ответы на сообщения платформы

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

Полный код сообщений можно найти ниже.

#define macroGetDataIndicatorFloat      {                                                                                                               \
                m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();      \
                m_Selection.bIsBuy = m_BtnInfoType.GetStateButton(macroMountName(def_IndicatorFloat, IT_PENDING, EV_TYPE));                             \
                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.bIsDayTrade = m_BtnInfo_DS.GetStateButton(macroMountName(def_IndicatorFloat, IT_PENDING, EV_DS));                           \
                                        }
                                                                                                
                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price;
                                bool   	bKeyBuy,
                                        bKeySell,
                                        bEClick;
                                datetime dt;
                                uint     mKeys;
                                char     cRet;
                                eIndicatorTrade  it;
                                eEventType       ev;
                                
                                static bool bMounting = false;
                                static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                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 = m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                                        m_Selection.sl = 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(price); else MoveSelection(price);
                                                }
                                                break;
                                        case CHARTEVENT_OBJECT_DELETE:
                                                if (GetIndicatorInfos(sparam, ticket, it, ev))
                                                {
                                                        if (GetInfosTradeServer(ticket) == 0) break;
                                                        CreateIndicator(ticket, it);
                                                        if ((it == IT_PENDING) || (it == IT_RESULT))
                                                                PositionAxlePrice(ticket, it, m_Selection.pr);
                                                        ChartRedraw();
                                                        m_TradeLine.SpotLight();
                                                        m_Selection.ticket = 0;
                                                        UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                                }
                                                break;
                                        case CHARTEVENT_OBJECT_ENDEDIT:
                                                macroGetDataIndicatorFloat;
                                                m_Selection.ticket = 0;
                                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                                break;
                                        case CHARTEVENT_CHART_CHANGE:
                                                ReDrawAllsIndicator();
                                                break;
                                        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) 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)
                                                                        {
                                                                                macroGetDataIndicatorFloat;
                                                                                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
                                                                {
                                                                        macroGetDataIndicatorFloat;
                                                                        m_Selection.ticket = def_IndicatorTicket0;
                                                                        m_Selection.it = IT_PENDING;
                                                                        SetPriceSelection(m_Selection.pr);
                                                                        RemoveIndicator(def_IndicatorFloat);
                                                                }
                                                                break;
                                                }
                                                break;
                                }
                        }
#undef macroGetDataIndicatorFloat

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

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

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

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;

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

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

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

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

С учетом всех этих изменений, новые индикаторы на графике можно увидеть ниже:

Виды отложенных ордеров

 

Индикатор позиции:

Обратите внимание, что гораздо проще определить, что делает отложенный ордер или открытая позиция, ведь вы можете точно знать, каково ожидаемое движение или срок жизни позиции. Стрелка, направленная вверх, имеет зеленый цвет и указывает на позицию покупки, а если она красная и направлена вниз, то мы ожидаем нисходящего движения. Итак, буква D представляет Day Trade, то есть она будет закрыта в конце дня, но если вместо нее там буква S, мы имеем Swing Trade, и операция не обязательно будет завершена в конце дня.

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




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

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