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

Daniel Jose | 28 сентября, 2022

1.0 - Введение

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

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


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

Для этого нам нужно сделать кое-что очень простое: МЫ НЕ СООБЩАЕМ СЕРВЕРУ О ВСЕХ ИЗМЕНЕНИЯХ, а только о нужном нам изменении. Даже простое выполняем этого - уже заставит всё работать нормально, хотя мы не будем абсолютно уверены, что всё именно так, как мы делаем в данный конкретный момент.

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

#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP))
                void MoveSelection(double price, uint keys)
                        {
                                static string memStr = NULL;
                                static ulong ticket = 0;
                                static eIndicatorTrade it;
                                eEventType ev;
                                double tp, sl, pr;
                                bool isPending;
                                
                                string sz0 = m_TradeLine.GetObjectSelected();
                                
                                if (sz0 != NULL)
                                {
                                        if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev);
                                        isPending = OrderSelect(ticket);
                                        switch (it)
                                        {
                                                case IT_TAKE:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP));
                                                        else ModifyPosition(ticket, price, macroGetPrice(IT_STOP));
                                                        break;
                                                case IT_STOP:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price);
                                                        else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price);
                                                        break;
                                                case IT_PENDING:
                                                        pr = macroGetPrice(IT_PENDING);
                                                        tp = macroGetPrice(IT_TAKE);
                                                        sl = macroGetPrice(IT_STOP);
                                                        ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr));
                                                        break;
                                        }
                                };
                        }
#undef macroGetPrice

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

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

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


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

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


3.0.1 - Создание индикатора-призрака

Все изменения внесем внутри класса C_IndicatorTradeView. Начнем с определения трех новых директив:

#define def_IndicatorGhost      "G"
#define def_IndicatorReal       "R"
#define def_IndicatorGhostColor clrDimGray

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

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

Следовательно, следующим шагом является изменение функции, позволяющей создавать уникальные имена.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}

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

Следующий шаг самый очевидный — мы должны создать сам индикатор:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2, cor3;
                                string sz0, sz1;
                                
                                switch (it)
                                {
                                        case IT_TAKE    :
                                                cor1 = clrForestGreen;
                                                cor2 = clrDarkGreen;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_STOP    :
                                                cor1 = clrFireBrick;
                                                cor2 = clrMaroon;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_PENDING:
                                                cor1 = clrCornflowerBlue;
                                                cor2 = clrDarkGoldenrod;
                                                cor3 = def_ColorVolumeEdit;
                                                break;
                                        case IT_RESULT  :
                                        default:
                                                cor1 = clrDarkBlue;
                                                cor2 = clrDarkBlue;
                                                cor3 = def_ColorVolumeResult;
                                                break;
                                }
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor);
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2);
                                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                                if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor);
                                m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1);
                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 92, 22);
                                                m_BackGround.Size(sz1, 92, 22);
                                                break;
                                        case IT_RESULT:
                                                m_BackGround.Size(sz1, 84, 34);
                                                break;
                                }
                                m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose);
                                m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0);
                                m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0);
                                m_EditInfo1.Size(sz0, 60, 14);
                                m_EditInfo1.Size(sz1, 60, 14);
                                if (it != IT_RESULT)
                                {
                                        m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor);
                                        m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                                        m_BtnMove.Size(sz1, 21, 21);
                                }else
                                {
                                        m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0);
                                        m_EditInfo2.Size(sz1, 60, 14);
                                }
                        }

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

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

#define macroSetAxleY(A, B)     {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y);                              \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y);                                 \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y);                                 \
                m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0));      \
                m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y));         \
                m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1);  \
                                }
                                                                        
#define macroSetAxleX(A, B, C)  {                                                       \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B);      \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B);         \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3);     \
                m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21);    \
                m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80);      \
                m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21);  \
                                }                                                                               
//+------------------------------------------------------------------+
inline void ReDrawAllsIndicator(void)
                        {
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, -1);
                                ulong           ticket;
                                double          price;
                                eIndicatorTrade it;
                                eEventType      ev;
                                
                                for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev))
                                        PositionAxlePrice(ticket, it, price);
                        }
//+------------------------------------------------------------------+
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price));
                                macroSetAxleY(it, true);
                                macroSetAxleY(it, false);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 110; break;
                                        case IT_STOP: desl = 220; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true);
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false);
                        }
#undef macroSetAxleX
#undef macroSetAxleY

И это все?! Просто изменить макросы? Да, не нужно заново создавать весь код, только надо что-то настроить. Всё, что действительно нужно сделать - это сообщить MetaTrader 5 имя объекта, которым мы манипулируем, а MetaTrader 5 сделает всё остальное за нас. Многие могут подумать, что для этого нам придется создать серию функций, но всё, что нам нужно - это добавить выделенные пункты.

И предпоследняя функция, которую мы изменим на этом этапе, показана ниже:

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;
                                
        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

И снова нужно было дополнить выделенные сегменты. Так создается наш призрак, который точно отражает то, что происходит с НАСТОЯЩИМ индикатором. На самом деле он слишком хорошо отражается, настолько хорошо, что нам придется создать еще несколько настроек, чтобы заставить вещи работать правильно. И дело не в том, что они были неправильными, но призрак слишком тесно связан с настоящим индикатором, и мы этого не хотим.

Последняя функция, которую нужно изменить, показана ниже:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                                {
                                        macroDestroy(IT_RESULT, true);
                                        macroDestroy(IT_RESULT, false);
                                        macroDestroy(IT_PENDING, true);
                                        macroDestroy(IT_PENDING, false);
                                        macroDestroy(IT_TAKE, true);
                                        macroDestroy(IT_TAKE, false);
                                        macroDestroy(IT_STOP, true);
                                        macroDestroy(IT_STOP, false);
                                } else
                                {
                                        macroDestroy(it, true);
                                        macroDestroy(it, false);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
                        }

Это было изменено в выделенных сегментах, что не заслуживает особого внимания.


3.0.2 - Отделим призрак от настоящего

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

Решение можно увидеть в следующем фрагменте:

                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price, tp, sl;
                                bool            isBuy,
                                                        bKeyBuy,
                                                        bKeySell,
                                                        bEClick;
                                long            info;
                                datetime        dt;
                                uint            mKeys;
                                eIndicatorTrade         it;
                                eEventType                      ev;
                                
                                static bool bMounting = false, bIsDT = false, bIsMove = false;
                                static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_MOUSE_MOVE:

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

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

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)
                {
                        Mouse.Hide();
                        bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl);
                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange);
                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange);
                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                        bMounting = true;
                }
                tp = price + (bKeyBuy ? valueTp : (-valueTp));
                sl = price + (bKeyBuy ? (-valueSl) : valueSl);
                UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy);
                if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT);
                }else if (bMounting)
                {
                        UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false);
                        Mouse.Show();
                        memLocal = 0;
                        bMounting = false;

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

В зависимости от того, нажимаете ли вы SHIFT для покупки или CTRL для продажи, система создаст представление создаваемого отложенного ордера. Мы можем это увидеть непосредственно на графике. Значения, которые будут использоваться в качестве тейк или стоп, фиксируются в рамках Chart Trade, поэтому вы должны перемещать представление в точку, где хотите разместить ордер, а затем, щелкнув левой кнопкой мыши, сообщать системе, что там должен быть размещен отложенный ордер. Как только мышь снова будет перемещена, индикатор, используемый для представления ордера, удаляется, оставляя индикатор ордера позади.

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

// ... Код CHARTEVENT_MOUSE_MOVE ....

        }else if ((!bMounting) && (bKeyBuy == bKeySell))
        {
                if (bEClick)
                {
                        bIsMove = false;
                        m_TradeLine.SpotLight();
                }
                MoveSelection(price, mKeys);
        }
break;

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

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

// ... Код ....

        case EV_MOVE:
                if (bIsMove)
                {
                        m_TradeLine.SpotLight();
                        bIsMove = false;
                }else
                {
                        m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                        bIsMove = true;
                }
        break;

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

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

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

Давайте посмотрим, как это делается на практике. Сначала, создадим приватную переменную.

bool    m_bIsMovingSelect;

Это будет отражать то, что я объяснил выше, но нам нужно её инициализировать.

C_IndicatorTradeView() : m_bIsMovingSelect(false) {}

Теперь перейдем к функции DispatchMessage и используем эту переменную вместо bIsMove.

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

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

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

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

                                }else if ((!bMounting) && (bKeyBuy == bKeySell))
                                {
                                        if (bEClick)
                                        {
                                                m_bIsMovingSelect = false;
                                                m_TradeLine.SpotLight();
                                        }
                                        MoveSelection(price, mKeys);
                                }
                                break;

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

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {

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

                                case EV_MOVE:
                                        if (m_bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_bIsMovingSelect = false;
                                        }else
                                        {
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                                m_bIsMovingSelect = true;
                                        }
                                        break;
                        }
                        break;
                }
}

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


3.0.3 - Переносим только самое важное

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

struct st00
{
        eIndicatorTrade it;
        bool            bIsMovingSelect,
                        bIsBuy;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_InfoSelection;

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

Здесь мы выделим только те функции, которые изменились и которые заслуживают некоторого пояснения. Первая — это функция SetTextValue.

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;

        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

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

Далее идет сам код перемещения, который вы можете видеть ниже:

void MoveSelection(double price)
{
        double tp, sl;
                                
        if (!m_InfoSelection.bIsMovingSelect) return;
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_PENDING:
                        tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr);
                        sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr);
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
        }
}

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

void SetPriceSelection(double price)
{
        bool isPending;
        if (!m_InfoSelection.bIsMovingSelect) return;
        isPending = OrderSelect(m_InfoSelection.ticket);
        m_InfoSelection.bIsMovingSelect = false;
        m_TradeLine.SpotLight();
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl);
                        else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl);
                        break;
                case IT_STOP:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price);
                        else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price);
                        break;
                case IT_PENDING:
                        ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr));
                        break;
        }
}

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

Последняя рекомендуемая функция показана чуть ниже:

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, bIsDT = 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)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.ticket = def_IndicatorTicket0;
                                        m_InfoSelection.bIsMovingSelect = true;
                                        m_InfoSelection.pr = price;
                                        bMounting = true;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        MoveSelection(0);
                                        m_InfoSelection.bIsMovingSelect = false;
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                MoveSelection(0);
                                m_InfoSelection.bIsMovingSelect = false;
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev))
                        {
                                CreateIndicatorTrade(ticket, it);
                                GetInfosTradeServer(ticket);
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateInfosIndicators(0, ticket, m_InfoSelection.pr, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ChartSetInteger(ChartID(), CHART_SHOW_OBJECT_DESCR, false);
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        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_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                        }
                                        break;
                        }
                        break;
        }
}

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


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

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