English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터(28부): 미래를 향해(III)

Expert Advisor 개발 기초부터(28부): 미래를 향해(III)

MetaTrader 5 | 22 4월 2024, 12:55
211 0
Daniel Jose
Daniel Jose

소개

제가 주문 시스템을 개발하기 시작할 때 EA 처음부터 개발하기(파트18) 기사를 보고는 제가 이 단계에 도달하는 데 얼마나 걸릴지 의문이었습니다. 우리는 다양한 변화, 수정 등을 해 왔습니다. 저는 표식를 하거나 시스템을 더 직관적으로 만드는 등 몇 가지 구체적인 작업을 수행하는 방법을 보여드렸습니다. 하지만 아직 완전히 준비되지 않은 부분들이 있어서 아직 보여드리지 못한 부분도 있었습니다. 이 여정을 통해 우리는 모든 사람이 이 아이디어를 이해하고 시스템이 어떻게 작동하는지 알 수 있는 방식으로 개념을 구축할 수 있었습니다.

우리는 이전의 모든 글을 통해 시스템 작동 방식에 대한 이해를 바탕으로 이 글에 임할 수 있는 발판을 마련했습니다. 따라서 저는 이 자료가 여러분에게 그리 혼란스럽거나 복잡하지 않기를 바랍니다. 저는 처음부터 한 가지 질문이 있었고 그래서 관련 부분을 자세히 분석하는 것은 피했습니다. 그러나 이 부분은 경험이 많은 트레이더들에게는 매우 중요합니다. 언뜻 보기에는 어리석어 보일 수 있지만 거래를 할 때가 되면 우리는 EA에서 무언가 놓치고 있다는 것을 알게 될 것입니다. "여기에 무엇이 빠진 것이지?"라고 자문해 보겠습니다. 저는 어떤 이유로 차트에서 삭제된 수익실현 및 손절매 값을 복원하는 방법에 대해 이야기하고 있는 것입니다.

만약 여러분이 이 작업을 시도해 본 적이 있다면 이것이 다소 어렵고 느린 작업이라는 것을 이해할 수 있었을 것입니다. 모든 것이 잘 작동하려면 "특정 시나리오를 따라야"하기 때문입니다. 그렇지 않으면 항상 실수를 하게 될 것입니다.

MetaTrader 5는 주문 값을 생성하고 수정할 수 있는 티켓 시스템을 제공합니다. 이 아이디어는 동일한 티켓 시스템을 더 빠르고 효율적으로 만들 수 있는 EA를 만드는 것입니다. MetaTrader 5 시스템은 완벽하지 않습니다; 때때로 우리가 개발 중인 EA를 사용하는 것보다 속도가 느리고 오류가 발생할 수 있습니다.

하지만 저는 지금까지 TP 및 SL 레벨(이익실현 및 손절매)의 값을 생성하는 방법에 대해 설명한 적이 없었습니다. 저는 스탑 레벨의 삭제는 충분히 명확하고 직관적이라고 생각합니다. 하지만 이를 구현하는 방법 즉 차트에서 바로 스탑을 설정하거나 복원하려면 어떻게 진행해야 할까요? 이는 매우 흥미로운 부분으로 우리로 하여금 몇 가지 의문을 가지게 합니다. 물론 이것이 이 기사를 작성한 이유입니다: 외부 리소스에 의존하지 않고 EA의 주문 시스템을 사용하여 차트에서 바로 스톱 레벨을 만드는 여러 가지 방법 중 하나를 여러분께 보여드리기 위해서 입니다.


2.0. 시작하기: 구현

첫째 EA는 우리의 개발 초기부터 오랫동안 점검 작업을 해왔습니다. 이를 중단하도록 해야 합니다. 이 점검 작업을 제거하려면 줄이 그어진 코드를 모두 제거하고 강조 표시된 코드를 추가해야 합니다:

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

줄이 그어진 코드는 사라지는 것이 아니라 다시 살아날 것입니다. 그러나 현재로서는 이점보다는 단점이 더 많습니다. 이 작업이 완료되면 우리는 EA 이외의 다른 리소스의 도움 없이 차트에서 바로 TP 및 SL 시스템을 구현하는 방법을 시작해 볼 수 있을 것입니다.

개발자마다 이 문제를 해결하기 위한 자신만의 아이디어가 있을 것입니다. 어떤 아이디어는 트레이더가 이해하기 쉬운 반면 어떤 아이디어는 더 어려울 수도 있고 어떤 아이디어는 실행하기 어려운 반면 어떤 아이디어는 더 쉬울 수 있습니다. 여기서 제가 사용하고 보여드리는 방식이 가장 적합하거나 가장 쉬운 방식이라고 말씀드리는 것은 아니지만 이것이 저의 작업 방식과 플랫폼 사용 방식에 가장 잘 맞는 방식입니다. 또한 이 방법을 사용하면 새로운 요소를 만들 필요가 없습니다. 우리는 코드의 일부만 수정할 것입니다.


2.0.1. 드래그 시스템 모델링

현재 개발 단계의 EA 코드 자체는 우리가 만들 시스템을 어떻게 모델링해야 하는지에 대한 몇 가지 힌트를 제공합니다. 다음 코드를 살펴보세요:

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

강조 표시된 줄에는 작업을 수행할 매크로가 포함되어 있습니다. 우리는 이 매크로를 수정하여 우리에게 필요한 기능, 즉 스탑 레벨 지표를 구현하는 데 필요한 도움말을 제공하도록 해야 합니다. 매크로 코드를 자세히 살펴보세요. 이 코드는 다음과 같이 표시됩니다:

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

우리는 다음을 수행합니다: 이익실현 또는 손절매가 될 수 있는 값인 B 값이 0보다 크면 차트에 지표가 있는지 확인합니다. 없는 경우에는 우리는 이를 생성하고 배치하고 표시할 값을 설정합니다. B 값이 0이면 우리는 차트에서 지표를 완전히 제거하고 매크로 코드에서 강조 표시된 지점에서 이 작업을 수행합니다. 하지만 차트에서 지표를 완전히 제거하는 대신 해당 요소를 유지하고 이 요소가 우리가 원하는 것을 표시하도록 구성할 수 있다면(즉, 주문 또는 포지션에 대해 누락된 스톱을 생성하는 것) 해당 요소가 주문 또는 OCO 포지션으로 돌아갈 수 있을까요? 네, 그 정도면 충분합니다. 바로 이것이 우리가 만들고자 하는 것입니다: 요소를 남겨두는 것입니다. 우리의 경우 스탑 레벨을 이동하고 누락된 스탑 레벨을 만드는 데 사용되는 객체를 남겨두면 됩니다. 이 요소를 드래그 하면 제한이 생성됩니다. 이것이 우리가 시스템을 구축하는 데 사용할 이론적 프레임워크 입니다.

하지만 이렇게 하는 것만으로는 우리에게 필요한 모든 것을 얻을 수 없습니다. 우리에게는 한 가지 더 변경해야 할 사항이 있습니다. 계속하기 전에 이 작업을 수행합니다. 이 변경 사항은 아래 코드에서 강조 표시되어 있습니다:

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

//... Internal code...

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

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

}

차트 트레이딩에서 입력한 초기값이 0인지 여부를 확인합니다. 이 경우 지표가 생성되지 않고 차트에 진입점만 표시됩니다.

이를 통해 나머지 시스템에서 보다 선형적인 작업을 수행할 수 있습니다. 스톱 레벨을 지정하지 않으면 우리가 차트에서 펜딩 오더를 낼 때 전체 주문 모델은 우리가 처음부터 값을 지정할 때와 동일한 동작을 합니다.

트레이더라면 이를 궁금해하지 않을 것입니다: 주문 또는 포지션에 있는 이것들은 무엇을 의미하나요? 트레이더는 이것들이 차트에서 이동할 수 있는 요소를 나타내고 있음을 알 수 있기 때문입니다.

하지만 이 모든 것에도 불구하고 우리에게는 여전히 작은 문제가 있어 두 가지 매크로를 변경할 것입니다. 아래를 참조하세요:

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

이 수정 사항은 앞으로 우리가 구축할 시스템에서 매우 중요합니다. 그런데 우리는 왜 잘라낸 선을 빼야 할까요? 그 이유는 시스템을 더욱 유연하게 만들어야 하기 때문이며 이를 위해 우리는 줄이 그어진 코드를 제거하고 그 자리에 강조 표시된 줄을 표시했습니다. 우리에게는 가격에 값을 할당하는 매크로가 없는데 macroGetPrice가 필요한 이유는 무엇일까요? 사실은 이 가격 조정을 실제로 수행하는 지점은 가격 조정을 차트 객체에 기록하는 단 한 곳 뿐입니다. 이 지점은 아래 코드에서 확인할 수 있습니다:

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

// ... Additional code inside the class....

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

따라서 매크로가 코드의 다른 모든 위치에 보이도록 객체에 있는 가격을 조정할 필요가 없습니다. 사실 지금 당장 매크로를 사용할 필요는 없지만 나중에 이 코드가 변경될 때 오류가 발생할 가능성을 줄이기 위해 그대로 두겠습니다.

이제 시스템이 최신 상태로 되었습니다. 우리는 다음 주제로 넘어가서 모든 것이 계획대로 작동되도록 할 것입니다.


2.0.2. 새로운 업데이트 기능

이전 주제에서 이미 언급했듯이 업데이트 함수를 구성하기만 하면 EA가 모든 관련된 문제를 해결해 줍니다. 우리의 주요 관심사는 이익실현 및 손절매 지표입니다. 그리고 이들은 매크로 내에서 실행됩니다. 그러므로 우리는 매크로를 올바르게 설정하기만 하면 됩니다.

그러나 아직 해결되지 않은 문제가 있습니다. 우리는 나머지 지표와 독립적인 이동 버튼을 만들어야 합니다. 이를 위해 버튼 생성 코드를 분리해 보겠습니다:

// ... class code ...

#define def_ColorLineTake       clrDarkGreen
#define def_ColorLineStop       clrMaroon

// ... class code ....

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

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

// ... the rest of the class code ...

이 코드는 이동 버튼만 생성합니다. 다른 것은 생성하지 않습니다. 코드는 매우 간단하고 직관적입니다. 저는 이 코드를 매크로로 남겨둘까도 생각했지만 함수로 만들기로 결정했습니다. 이 코드는 내장 함수로 선언되어 있습니다. 그러므로 컴파일러는 매크로를 처리할 때와 동일하게 처리합니다. 동일한 코드 부분에 두 개의 새로운 정의가 있습니다. 이는 우리가 어느 시점에서는 이동 버튼만 만들고 이 이동 버튼이 시스템 전체에서 사용되는 것과 동일한 색상을 사용하기를 원하기 때문입니다. 유사한 경우에서 시스템이 다르게 작동하거나 다르게 보이는 것은 바람직하지 않습니다. 문제를 줄이기 위해 우리는 위와 같이 색상을 그대로 둡니다.

이제 업데이트 함수로 이동합니다; 전체 코드는 위에 나와 있습니다. 아래 버전과 이 글의 앞부분에 제시된 버전 간의 유일한 차이점은 강조 표시된 코드입니다. 이 코드는 Update 함수 자체에서 사용하는 매크로입니다.

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

이 매크로를 좀 더 자세히 살펴보고 실제로 어떤 일이 일어나고 있는지 이해해 보도록 하겠습니다. 여전히 발생하는 몇 가지 문제를 해결하려면 이를 이해하는 것이 필요합니다. 우리는 몇 가지 사항을 더 변경해야 합니다.

첫 번째 단계에서 다음과 같은 동작이 있습니다: 스탑 오더(이익실현 또는 손절) 중 하나를 제거하면 심볼 차트에서 관련 지표가 즉시 제거됩니다. 이를 위해 지표의 존재를 나타내는 것들 중 하나인 선이 있는지 확인합니다. 그런 다음 이 지표의 이동 객체가 있는지 확인합니다. 존재하지 않는 경우 생성합니다. 따라서 차트에는 지표가 없지만 차트에는 이동 객체인 지표의 나머지 부분이 여전히 존재합니다.

두 번째 단계는 스톱 레벨을 생성하는 경우에 생깁니다: 서버에 있는 주문이 차트에 나타나야 하는 스탑 레벨을 가지게 됩니다. 이 경우 움직임을 나타내는 객체가 제거되고 완전한 지표가 생성되어 현재 스톱 레벨(이익실현 또는 손절매)이 어디에 있는지를 나타내는 적절한 위치에 배치됩니다.

마지막 단계에서는 지표를 올바른 지점에 배치합니다. 한 가지 흥미로운 사항: 스톱 레벨 지표가 이동의 가능성을 나타내는 객체일 뿐이라면 스톱 레벨 지표가 설정되는 지점은 정확히 주문 또는 포지션의 가격이 될 것입니다. 즉 이익실현 및 손절매를 생성할 수 있는 이동 객체는 해당 객체가 속한 주문 또는 포지션의 가격선에 연결됩니다. 따라서 주문 또는 포지션에 스톱 레벨 중 하나가 누락된 경우 쉽게 알 수 있습니다.

움직임을 나타내는 객체를 클릭하면 평소와 같이 고스트가 생성되고 동시에 전체 지표의 표현도 생성됩니다. 이 작업은 코드를 추가하거나 수정하지 않고 수행됩니다. 이제부터는 우리는 이전과 마찬가지로 일반적인 방법으로 스톱 레벨을 이동하고 조정할 수 있습니다. 하지만 특정 지점을 클릭하기 전까지는 스탑 오더가 존재하지 않습니다. 이 부분은 실제 계정에서 시스템이 어떻게 작동하는지 보여드리는 데모 동영상에서 명확하게 확인할 수 있습니다.

모든 것이 정상인 것처럼 보이지만 여기에는 몇 가지 불일치가 있습니다. 그러므로 우리는 어떤 시점에서는 코드를 만들거나 변경해야 합니다. 이 문제는 다음 주제에서 살펴보겠습니다.


2.0.3. 플로팅 지표의 불편함 해결

첫 번째 불편함은 플로팅 지표 모드에 있을 때 나타나는데 차트에는 표시되지만 서버에는 표시되지 않는다는 점입니다. 자세한 내용은 플로팅 지표의 작동 원리와 구현 방법을 설명하는 기사 EA 개발하기 처음부터(26부)(27부)를 참조하세요. 이러한 지표는 유용하므로 EA에서 제거되지 않습니다. 하지만 이러한 지표들은 실제로 거래 서버에 존재하는 주문과 포지션을 나타내는 지표와 작동 방식이 다르기 때문에 위에서 살펴본 시스템에는 적합하지 않습니다. 플로팅 지표를 사용할 때 나타나는 문제를 해결하려면 아래 그림과 같이 DispatchMessage 함수로 이동하여 항목을 조정해야 합니다.

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

// ... Internal code...
                        
        switch (id)
        {

// ... Internal code...

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

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

위에 강조 표시된 변경 사항을 적용하면 플로팅 지표 설정과 관련된 다른 문제가 거의 없어집니다. 왜냐하면 이제 플로팅 지표 데이터를 조정하는 방법과 트레이딩 서버에 존재하는 데이터를 나타내는 방법이 동일하기 때문입니다. 그러나 이것이 우리의 문제를 완전히 해결해 주지는 않습니다. 우리는 오랫동안 지속되어 온 또 다른 불편을 해결해야 합니다. 이에 대해서는 별도의 논의가 필요하므로 다음 주제로 넘어가 다루어 보겠습니다.


2.0.4. 음수 이익 실현 값의 단점

이 글에서 논의하는 마지막 단점은 이익 실현 값이 종종 음수로 구성될 수 있다는 점이며 이러한 문제는 꽤 오랫동안 발생해 왔습니다. 그러나 이 문제는 트레이딩 시스템에서는 의미가 없습니다: 만약 여러분이 값을 서버로 보내려고 하면 오류 메시지가 반환됩니다. 따라서 이 문제를 해결하고 또 다른 문제, 즉 펜딩 오더의 스톱 값이 양수로 변경될 수 있다는 문제도 해결해야 합니다.

EA는 이제 그렇게 할 수 있으며 더 안 좋은 상황은 주문 시스템이 이 값이 서버에 있다고 표시하지만 실제로는 서버가 오류를 반환하고 EA는 이를 무시한다는 것입니다. 펜딩 오더의 경우 문제가 더 복잡해집니다. 왜냐하면 포지션의 경우 이를 핸들링 하는 방식이 달라져야 하는데 이 버그가 아직 수정되지 않았기 때문입니다. 차트에서 우리가 직접 스톱 레벨을 정의할 수 있게 되면 이러한 단점은 사라질 것입니다.

오픈 포지션의 경우 양수 값으로 손절매를 설정할 수 있습니다. 이는 손절매가 발동하면 관련 값이 계좌에 입금된다는 것을 의미합니다. 그러나 펜딩 오더는 이 경우에 서버가 올바른 주문을 생성하지 못하도록 하는 오류가 발생합니다. 이 문제를 해결하려면 이익 실현 값을 확인해야 합니다: 이익 실현 값이 0보다 작거나 같으면 더 작은 값으로 변경되지 않도록 해야 합니다. 또한 펜딩 오더의 경우 손절매가 0보다 크지 않도록 해야 합니다. 조건 0이 충족되면 우리는 허용되는 최소값을 EA가 사용하도록 강제해야 합니다. 이 경우 주문 또는 포지션은 트레이딩 시스템에서 어느 정도 의미가 있지만 진입가격과 같은 손절매로 포지션에 진입하거나 이익 실현을 하는 것은 의미가 없습니다.

이 작업을 최대한 쉽게 수행하려면 시스템에 변수를 만들어야 하는데 아래에서 이를 확인할 수 있습니다:

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

만약 마우스 라인의 가격대를 변경하면 어떨까요? 그 이유는 마우스를 올바르게 조작하려면 시스템 호출을 사용해야 하는데, 즉 WINDOWS API를 통해 마우스 위치 값을 조작해야 하는데, 이렇게 하면 외부 dll을 사용해야 합니다. 그러나 저는 그렇게 하고 싶지 않습니다. 이렇게 하면 EA 내에서 로컬 값을 조합하는 것이 더 간단 해지며 강조 표시된 데이터가 이 값을 저장합니다.

이 값은 세 군데 다른 곳에서 사용됩니다. 첫 번째 장소는 이동 함수입니다. 아래 코드는 정확히 어디에서 이런 일이 발생하는지 보여줍니다:

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

위의 함수는 스탑 오더 포인트를 이동하는 역할을 합니다. 그러므로 이 함수에 모든 것을 넣으면 어떨까요? 그 이유는 이익 실현 또는 손절매 수준을 올바르게 설정하려면 몇 가지 계산이 필요하기 때문입니다. 그리고 이를 다른 곳에서 계산하는 것이 훨씬 쉽기 때문입니다. 다른 곳에서도 이 값을 변경해야 하므로 여기에 값이 참조되는 두 번째 위치가 있습니다. 아래 코드를 참조하세요.

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

// ... Internal code ....      
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse button click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        m_Selection.it = IT_PENDING;
                                        m_Selection.pr = price;
                                }
                                m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
                                m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));
                                m_Selection.bIsBuy = bKeyBuy;
                                m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy);
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0);
                                        bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price);
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(m_Selection.MousePrice); else MoveSelection(price);
                        }
                        break;

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

이 덕분에 우리는 시스템에서 예측 가능한 동작을 가지게 되었습니다. 값이 참조되는 또 다른 지점이 있지만 복잡성 때문에 저는 매크로가 더 이상 존재하지 않도록 모든 것을 수정하기로 결정했습니다. 이제 매크로는 함수가 될 것입니다. 따라서 새로운 Update 함수는 다음과 같습니다:

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

강조 표시된 지점은 이전의 매크로를 대체하지만 제가 말했듯이 필요한 코드가 훨씬 더 복잡하므로 세 번째이자 마지막 지점이 실제로 어디에서 참조되는지 살펴 보겠습니다. 위 코드에서 이익 실현과 손절매 지표는 차이가 없습니다: 둘 다 동일하게 처리됩니다. 아래 코드를 살펴보세요.

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

이제부터는 허용되는 값에 한도가 있으므로 펜딩 오더의 이익 실현 값은 틀릴 수가 없습니다. 펜딩 바이 오더를 낸 다음 펜딩 오더를 음수 값(즉, 진입가 이하)으로 변경하는 것은 더 이상 불가능합니다. 이익 실현 지표가 이를 계산 하기 때문입니다. 이런 식으로 코드를 작성하면 주문이든 포지션이든 상관없이 실현 값이 음수가 될 수 없다는 이점이 있습니다. 왜냐하면 EA 자체에서 허용하지 않기 때문입니다.

이제 손절매에 관련해 우리가 주문 또는 포지션 중 무엇을 관리하고 있는지 확인하는 약간 다른 계산이 있습니다. 주문인 경우 스톱 값은 절대로 양수가 되지 않으며 포지션인 경우 EA는 다른 모든 조건을 무시합니다. 이 경우 EA는 트레이더가 지정한 값을 수락해야 합니다. 따라서 우리는 이제 포지션의 경우에만 포지션 시스템 코드에 아무런 해를 끼치지 않고 양수 손절매를 설정할 수 있으며 이렇게 하면 제출된 데이터가 거부되지 않고 EA가 최종적으로 거래 서버와 상호 작용합니다.


결론

마침내 여러 차례의 기사 끝에 우리는 완성작에 이르렀고 이제 다양한 상황과 시장 조건에 적의 완벽하게 적응할 수 있는 주문 시스템을 갖추게 되었습니다. 이제부터 우리는 시장 분석과 함께 마우스와 키보드를 사용해 완전한 그래픽 시스템을 통해 거래에 진입하거나 청산할 수 있습니다.

시스템이 어떻게 작동하는지 현재 개발 단계에서는 어떤 모습인지 궁금하신 분들은 아래 동영상을 시청해 주세요. 지금까지 이 시리즈 기사를 읽어주신 모든 분들께 감사드립니다. 하지만 아직 작업이 끝나지 않았고, 이 EA가 멋진 것이 되기까지에 해야 할 일이 많습니다. 다음 글에서 뵙겠습니다! 👍



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

시장과 시장이 보여 주는 글로벌 패턴의 물리학 시장과 시장이 보여 주는 글로벌 패턴의 물리학
이 글에서는 시장에 대한 이해가 조금이라도 있는 시스템이라면 글로벌 규모로 운영 가능하다는 가정을 테스트해 보려고 합니다. 저는 어떤 이론이나 패턴을 발명하지 않을 것이고 알려진 사실만을 사용하며 이러한 사실을 점차 수학적인 분석 언어로 번역할 것입니다.
트레이딩 전문가 어드바이저를 처음부터 개발하기(27부): 다음을 향해(II) 트레이딩 전문가 어드바이저를 처음부터 개발하기(27부): 다음을 향해(II)
차트에서 좀 더 완전한 주문 시스템을 살펴보겠습니다. 이 글에서는 주문 시스템을 수정하거나 오히려 더 직관적으로 만드는 방법을 보여드리겠습니다.
조합론과 트레이딩 확률(4부): 베르누이 논리 조합론과 트레이딩 확률(4부): 베르누이 논리
이 글에서는 잘 알려진 베르누이 기법을 알아보고 이를 트레이딩과 관련한 데이터 배열을 설명하는 데 어떻게 사용할 수 있는지 보여드리겠습니다. 그런 다음 이 모든 것이 스스로 적응하는 트레이딩 시스템을 만드는 데에 사용될 것입니다. 우리는 또한 베르누이 공식의 특별한 경우인 보다 일반적인 알고리즘을 찾아보고 관련된 응용 프로그램을 찾아볼 것입니다.
Expert Advisor 개발 기초부터(26부): 미래를 향해(I) Expert Advisor 개발 기초부터(26부): 미래를 향해(I)
오늘은 주문 시스템을 한 단계 더 발전시켜 보겠습니다. 하지만 그 전에 우리는 몇 가지 문제를 해결해야 합니다. 우리가 어떻게 하고 싶은지, 거래일 동안 우리가 어떤 일을 할 것인지와 관련된 몇 가지 질문이 있습니다.