English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터(23부): 새로운 주문 시스템(VI)

Expert Advisor 개발 기초부터(23부): 새로운 주문 시스템(VI)

MetaTrader 5트레이딩 | 7 11월 2023, 09:47
363 0
Daniel Jose
Daniel Jose

소개

이전 글인 Expert Advisor 개발하기 기초부터(22부)에서 펜딩 오더와 포지션 스탑 레벨을 이동하는 시스템을 개발했습니다. 이 방법은 비교적 안전하지만(거래 서버에 있는 내용을 반영하기 때문에) 레벨을 빠르게 이동하는 가장 좋은 방법은 아닙니다.

문제는 마우스를 사용하여 무언가를 변경할 때마다 이 이벤트가 서버로 전송되고 우리는 응답을 기다려야 한다는 것입니다. 이 문제는 이벤트가 매 틱마다 전송된다는 사실과 관련이 있습니다. 즉 한 번에 몇 틱씩 레벨을 이동해야 하는 경우 모든 중간 값을 거쳐야 하므로 이는 전체 프로세스가 매우 느려지게 합니다. 우리는 이렇게 코드 변경을 구현하여 유연성을 높이고 레이블을 더 빠르게 변경할 수 있습니다.


1.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는 이를 수행하는 매우 간단한 방법을 제공합니다. 따라서 이 시리즈의 이전 글에서 다룬 내용에 이미 익숙하신 분이라면 이 글을 매우 쉽게 이해할 수 있을 것입니다.


2.0. 구현

고스트 레이블을 구현하기 위해 우리는 고스트 레이블을 실제 레이블로 간단히 생성합니다. 이전 글에서 설명한 방식대로 가격 작업으로 넘어갈 때까지는 이것은 실제 레이블의 그림자가 될 것입니다. 이 시점에서 실제 레이블이 움직이면 차트에 고스트 레이블이 나타납니다. 이렇게 하면 현재 상황을 쉽게 비교하고 변경 여부를 파악할 수 있습니다.


2.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
                        }

강조 표시된 조각만 변경되었을 뿐 특별한 사항은 없습니다.


2.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:

// ... The rest of the code...

해결책은 강조 표시된 코드입니다. 하지만 어떻게 이게 가능할까요? 펜딩 오더를 할 때 시스템에서 데이터를 생성하고 조작하므로 결국 차트에 주문이 체결될 위치를 나타내는 레이블이 표시된다는 점을 기억하세요. 이는 다음 코드에서 수행됩니다:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
        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;

//... Rest of the code...

매수를 위해 Shift 키를 누르는지, 매도를 위해 CTRL 키를 누르는지에 따라 시스템에서 펜딩 오더가 생성되는 모습을 보여줍니다. 주문은 차트에 바로 표시됩니다. 값 ​을 클릭한 다음 차트 트레이드에서 이익실현 또는 손절매로 사용할 표시를 주문할 지점으로 이동한 다음 마우스 왼쪽 버튼을 클릭하면 시스템에 해당 지점에 주문이 있어야 함을 알려주는 것입니다. 마우스를 다시 움직이면 주문을 나타내는 데 사용된 레이블이 제거되고 주문 표시가 남습니다.

지금까지는 특별한 것이 없었습니다. 하지만 코드를 살펴보면 다음과 같은 내용을 확인할 수 있습니다:

// ... CHARTEVENT_MOUSE_MOVE code....

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

// ... The rest of the code....

따라서 펜딩 오더를 생성하지 않고 키가 해제되면 선택할 수 있는 레이블로 마우스 가격 위치를 전송합니다. 이 작업은 강조 표시된 줄에서 수행됩니다. 마우스 왼쪽 버튼을 클릭하여 이 전송을 종료하면 지표의 선택이 해제되고 현재 로컬에 있는 변수의 상태가 변경됩니다, bIsMove. 하지만 이제 바꿔보겠습니다. 이 변수의 상태는 DispatchMessage 함수 내의 다른 이벤트에 의해서만 수정됩니다. 이 이벤트는 다음과 같습니다:

// ... Code ....

        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) {}

이제 bIsMove 대신 DispatchMessage를 사용합니다.

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

// ... Internal code...

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Internal code...

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

// ... Internal code...

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

// ... Internal code....

                                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;
                }
}

변경 사항이 강조 표시됩니다. 따라서 이제 전체 클래스는 사용자가 선택한 레이블로 작업할지 여부를 알 수 있습니다. 이렇게 하면 고스트와 실제 레이블을 분리하여 더 정확하게 표현할 수 있습니다.


2.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;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //Pressed SHIFT
                        bKeySell = (mKeys & 0x08) == 0x08;    //Pressed 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 이벤트가 사용됩니다. 사소한 것 같지만 이 이벤트는 코드의 모든 변경 사항이 보이도록 하고 문제가 발생하면 우리가 마우스 이벤트를 사용하는 한 나타납니다.


결론

아래 동영상을 통해 변경된 내용을 보다 명확하게 파악할 수 있습니다. 이제 주문 시스템의 측면에서 EA를 완전히 완성하려면 몇 가지 세부 사항만 더 추가하면 된다는 것을 알 수 있습니다.


MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10563

MQL5에서 ONNX 모델을 사용하는 방법 MQL5에서 ONNX 모델을 사용하는 방법
ONNX(Open Neural Network Exchange)는 머신 러닝 모델을 나타내기 위해 구축된 개방형 형식입니다. 이 기사에서는 금융 시계열을 예측하기 위해 CNN-LSTM 모델을 만드는 방법에 대해 살펴보겠습니다. 또한 MQL5 Expert Advisor에서 생성된 ONNX 모델을 사용하는 방법도 보여드리겠습니다.
모집단 최적화 알고리즘: 물고기 떼 검색(FSS) 모집단 최적화 알고리즘: 물고기 떼 검색(FSS)
물고기 떼 검색(FSS)은 대부분의 물고기(최대 80%)가 친척들로 구성된 집단인 물고기 떼에서 물고기의 행동에서 영감을 얻은 새로운 최적화 알고리즘입니다. 물고기의 떼가 먹이 사냥의 효율성과 포식자로부터 보호하는 데 중요한 역할을 한다는 것은 이미 입증된 사실입니다.
모집단 최적화 알고리즘: 반딧불이 알고리즘(FA) 모집단 최적화 알고리즘: 반딧불이 알고리즘(FA)
이 글에서는 반딧불이 알고리즘(FA) 최적화 방법에 대해 살펴보겠습니다. 수정을 통해 알고리즘은 주변부의 존재에서평점 테이블의 실제 리더가 되었습니다.
Expert Adviso 개발 기초부터(22부): 새로운 주문 시스템(V) Expert Adviso 개발 기초부터(22부): 새로운 주문 시스템(V)
오늘은 새로운 주문 시스템을 계속 개발할 예정입니다. 새로운 시스템을 구현하는 것은 그리 쉬운 일이 아닙니다. 프로세스를 복잡하게 만드는 문제가 종종 발생하기 때문입니다. 이러한 문제가 나타나면 우리는 개발을 멈추고 우리의 개발 방향에 대해 다시 분석해야 합니다.