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

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

MetaTrader 5 | 27 3월 2024, 11:18
139 0
Daniel Jose
Daniel Jose

소개

"트레이딩 Expert Advisor 처음부터 개발하기" 시리즈 24편과 25편에서 우리는 시스템 견고성을 높이는 방법을 살펴보았습니다. 코드 수정 및 개선 사항이 있었지만 우리에게는 아직 몇 가지 세부 사항이 남아있었습니다. 아직 남아 있는 부분은 관련성이 낮아서가 아니라 실제로는 매우 중요합니다.

우리가 어떻게 하고 싶은지, 거래일 동안 우리가 어떤 일을 할 것인지와 관련된 몇 가지 질문이 있습니다. 많은 트레이더들은 특정한 가격에 주문한 후 그 가격에서 움직이지 않습니다. 어떤 일이 발생하든 그들은 이것이 완벽한 진입점이라고 생각하고 주문을 이동하지 않을 것입니다. 그들이 스톱 레벨을 이동하거나 스톱 레벨을 삭제할 수는 있지만 진입 지점을 변경하지는 않습니다.

따라서 코드에 남아있는 결함은 트레이더들에게 실제로 영향을 미치지 않습니다. 트레이더들이 주문 시스템에 결함이 있다는 사실을 알게 될 수도 있습니다(예: 이 글에서 수정할 결함). 그러나 가격을 쫓고 싶고 어쨌든 거래에 참여하려고 하지만 시장에 진입하고 싶지 않은 사람들은 시스템에서 많은 오류를 목격하게 될 것입니다. 에러 중 일부는 거래를 방해하고 (좋게 말하면) 거래를 안전하지 않게 만들 수 있지만 다른 트레이더들은 그 동안 수익을 올릴 수도 있습니다.


2.0. 구현

이 글의 여정을 시작하기 위해 EA의 결함을 수정하는 것부터 시작하겠습니다. 다시 말하지만 만약 여러분이 진입 지점을 계속 변경하지 않는다면 이 문제는 발생하지 않습니다. 하지만 만일을 대비하여 코드를 업데이트하는 것이 좋습니다. 첨부된 코드에 이미 수정 사항이 구현되어 있습니다. 이로 인해 여러분은 EA의 일부 성능이 저하되어 EA가 손상될 것이라고 생각할 수 있지만 이는 사실입니다. 하지만 어느 쪽이 더 나을까요 약간의 성과를 잃는 것과 잘못된 진입으로 손실을 감수하는 것 중 어느 쪽이 더 나을까요?


2.0.1. 진입점 오류

이 오류는 어떤 식으로든 수정이 필요한 오류입니다. 이 오류는 가장 치명적인 오류입니다. 이는 보류 중인 진입 즉 바이 스톱을 설정하고 진입 지점을 이동하여 이제 주문이 바이 리밋형이 되도록 할 때 발생합니다. 여기에는 문제가 없는 것처럼 보이지만 현재 개발 단계의 EA가 올바른 방식으로 변경되도록 할 수 없기 때문에 이 오류는 매우 치명적입니다. 실제로 여러분은 EA에 이러한 수정이 가능하도록 하게 하고 싶지만 이 경우 차트에는 관련 정보가 표시되지만 서버에는 다른 정보가 표시됩니다. 포지션이 열릴 때만 시스템이 바르게 업데이트되며 그 전까지는 EA가 차트에 표시하는 데이터와 서버에 있는 데이터 사이에 일관성이 없습니다.

어떤 경우에는 이러한 불일치가 발생하는 데 그치지만 어떤 경우에는 완전한 재앙이 될 수도 있습니다. 이를 이해하려면 이 글을 주의 깊게 읽어보세요.

우리에게는 다른 경로를 거쳐 적용하여 이 오류를 제거할 수 있는 솔루션이 있습니다. 그러나 호가창에서 주문을 제거하고 새 포지션으로 이동하고 주문 유형을 변경한 후 다시 호가창으로 반환하는 작동 원리는 항상 동일합니다. 이는 반드시 수행해야 하는 작업이지만 어떻게 구현할지는 구체적인 구현 방식에 따라 달라집니다.

우리는 가장 기본적인 솔루션을 구현할 예정이지만 이 기본적인 솔루션이 이상적인 것은 아니기 때문에 몇 가지 문제를 해결해야 할 것입니다.

해결책은 강조 표시된 줄을 추가해서 아래 함수를 수정하는 것입니다.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

그러나 이 솔루션은 문제를 부분적으로 해결하지만 완전히 해결하지는 못합니다. 예를 들어 바이 스탑셀 스탑 주문의 경우 이 간단한 줄을 추가하면 문제가 해결됩니다. 하지만 바이 리밋스탑 리밋의 경우에는 우리가 진입 지점을 변경하기 위해 클릭할 경우 서버가 즉시 주문을 체결합니다. 여기서 더 나쁜 것은 우리가 손해를 보게 되는 포지션에 진입한다는 것입니다. 주문이 빈 주문(이익 또는 손실 수준 포함)으로 구성되고 손절매 지점이 가격 제한을 벗어난 경우 서버가 즉시 주문을 체결할 뿐만 아니라 그 직후 주문을 청산하므로 거래 계좌는 완전한 재앙을 맞을 수 있습니다. 그렇기 때문에 트레이딩 시스템을 개발하기가 매우 어려운 것입니다. 만약 데모 계좌에서 몇 가지 테스트를 수행한 후 모든 것이 정상적으로 작동하는 것 같아 계좌로 이동하면 이 시점에서 우리는 실제로 무슨 일이 일어나고 있는지 알지 못한 채 돈을 잃기 시작합니다.

다시 한 번 말씀드리지만 진입 지점을 한 번만 배치하고 변경하지 않는 경우에는 오류가 발생하지 않습니다. 문제는 트레이더가 포인트를 이동할 때 발생합니다.

실제로 스탑 명령은 잘 작동하고 있습니다. 이제 우리는 리밋 펜딩 주문의 문제를 해결해야 합니다. 이 문제는 쉽게 해결될 수 있는 것처럼 보이지만 한 가지 이해해야 할 점이 있습니다: 완벽한 솔루션은 없으며 시스템 개발자에게 가장 적합한 솔루션이 사용자에게 적합한 솔루션이 아닐 수도 있다는 것입니다..

이 문제에 대한 가능한 해결책 중 하나를 보여드리겠습니다. 이 솔루션은 위에 표시된 것과 동일한 함수로 구현됩니다. 새로운 코드는 다음과 같습니다:

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

이 작업은 다음과 같이 수행됩니다. 펜딩 주문의 진입 시점을 변경하려면 호가창(심리지수)의 주문이 스탑 리밋 또는 바이 리밋 유형인지 확인합니다. 그렇지 않은 경우 실행 되는 흐름은 코드의 다른 지점으로 계속 진행됩니다. 이후 우리는 현재 자산 가격을 즉시 캡처합니다. 그리고 다음과 같은 기준을 사용합니다: 매수 주문의 경우 현재 ASK 가격을 캡처합니다. 각각 매도 주문에 대한 BID 가격입니다. 이는 LAST 값을 사용하는 기존 방법을 대체하지만 일부 시장에서는 사용되지 않습니다. 그러므로 우리는 이를 참조용으로 사용하지 않을 것입니다. 그런 다음 주문서의 주문이 무효화되는지 아니면 수정만 되는지를 확인합니다.

주문이 여전히 유효하다면 시스템은 유효성 검사 코드를 무시하고 주문이 변경될 부분으로 이동합니다. 그러나 시장 심도의 주문이 유효하지 않은 경우 시스템은 다음의 코드를 실행합니다:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
break;

그러나 위의 코드는 셀 리밋 및 바이 리밋 주문만 각각 셀 스탑 및 바이 스탑으로 변경합니다. 이러한 유형을 원래 유형으로 되돌리거나 이렇게 변경되는 것을 방지하려면 어떻게 해야 할까요?

실행된 주문의 유형을 시스템이 변경하지 않게 하려면 강조 표시된 부분을 다음과 같은 코드로 바꾸면 됩니다:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

이 코드는 주문 유형이 변경되는 것을 방지합니다. 여러분은 펜딩 주문이 체결되는 지점을 변경할 수는 있지만 펜딩 주문을 스탑 주문으로 혹은 그 반대로 변경할 수는 없습니다. 이제 가격을 계속 추적하고 특정 시점에 강제로 진입하게 하려면 아래의 코드를 사용하세요. 이 코드가 EA에서 사용될 코드입니다.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

중요 참고 사항: 이 코드로 작업할 때 주의해야 할 점은 ForceNewType 매크로를 사용한다는 점입니다. 이 매크로에는 실행 시 코드가 'case' 블록을 종료하는 브레이크가 포함되어 있습니다. 따라서 이 블록을 수정할 때 여러분은 매우 신중해야 합니다.

시스템이 진입 지점을 이동하는 데 더 이상 오류가 발생하지는 않지만 해결해야 할 또 다른 문제가 있습니다. 저는 수정하거나 동일한 유형의 주문을 유지하여 문제를 해결하는 방법을 보여 드렸으니 여러분은 자신에게 가장 적합한 방법을 선택하세요. 이러한 솔루션에는 각각의 장단점이 있다는 점을 기억하세요. 하지만 제가 여기서 자세히 설명하지는 않겠습니다. 시스템을 수정하고 구현하는 방법만 보여드리겠습니다.

이러한 변경의 결과는 다음 동영상에서 확인할 수 있습니다:



2.0.2. 다음을 위한 준비

위의 변경으로 문제가 해결되었지만 더 해야 할 일이 있습니다. 저는 이러한 변화의 시작을 보여드리겠습니다. EA의 주문 시스템을 살펴보면 아직 개선의 여지가 많다느 것을 알 수 있습니다. 트레이더마다 시장에서 행동하는 방식은 틀립니다. 그러므로 저는 여러분들이 가장 적합한 경로를 선택할 수 있도록 설명해드리고자 합니다. 제가 보여드릴 시스템을 반드시 사용해야 한다는 의무감을 느끼지 않으셨으면 합니다. 대신 저는 누구나 맞춤형 EA를 개발할 수 있도록 기반을 만들고 싶습니다.

이제 다음 부분으로 넘어가겠습니다: 저는 18부부터 특정 자산을 거래하는 사람들이 쉽게 사용할 수 있는 주문 시스템을 개발하는 방법을 보여드렸습니다. 그러나 파트 20에서는 주문 시스템 자체에 모든 것이 표시되므로 차트에서 바로 모든 것을 변경하고 구성할 수 있기 때문에 어느 시점에서 차트 거래가 거래에 필요하지 않게 될 것이기 때문에 주문 시스템에 시각적 요소가 추가되었습니다. 이 지점에 도달하기 위해서 우리는 어딘가에서 시작해야 하며 바로 시작할 것입니다.

차트에서 주문을 제거할 필요 없이 주문 내에서 직접 볼륨을 변경하여 차트 트레이딩에서 볼륨을 변경한 다음 차트에 다시 주문을 넣는 것은 어떨까요? 흥미롭지 않나요? 우리는 이 기능을 지금 바로 구현할 예정입니다. 이 기능은 여러 시나리오에서 많은 도움이 되지만 다른 플랫폼에서는 찾을 수 없으므로 우리는 시스템 사용법을 배우고 이해해야 합니다. 솔직히 저는 이런 기능을 갖춘 EA를 본 적이 없습니다. 이제 모든 EA에서 이 기능을 사용하려면 어떻게 해야 하는지 알아보겠습니다.

먼저 새로운 지표 인덱스를 정의합니다.

#define def_IndicatorFloat      3

펜딩 오더가 이 값을 티켓으로 받으면 이 펜딩 주문은 완전히 다른 방식으로 처리될 수 있습니다. 이전에 존재했던 모든 것은 주문 시스템에 그대로 유지되며 우리는 새로운 인덱스1 만 추가합니다.

그런 다음 시스템에 새로운 객체를 추가합니다:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

이 객체는 주문이 대기 중인 동안 항상 몇 가지 기능을 활성화합니다.

이제 우리는 C_Object_BitMap 클래스로 이동하여 편집합니다. 몇 가지 정의를 추가합니다:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

우리는 이 수업에서 무슨 일이 일어나고 있는지 알아야 합니다. 다음 함수를 추가합니다:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton은 버튼의 상태를 반환합니다. MetaTrader 5는 상태를 변경하므로 추가 단계를 구현할 필요 없이 버튼 값이 참인지 거짓인지만 확인하면 됩니다. 하지만 상태는 우리가 원하는 것을 반영하지 않을 수도 있습니다. 그런 다음 SetStateButton을 사용하여 상태가 거래 서버와 EA에 표시되는 실제 상태를 반영하도록 설정합니다.

또 다른 간단한 수정은 C_Object_Edit 클래스에서 가능합니다:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

이 수정은 값을 편집할 수 있는지 여부를 표시합니다. 우리는 차트 트레이딩을 사용하지 않고 차트에서 직접 주문량을 수정할 수 있기를 원합니다. 생성된 펜딩 오더는 항상 읽기 전용 모드이지만 우리는 이를 변경할 수 있는 시스템을 만들 예정입니다.

이제 C_IndicatorTradeView로 돌아가 보겠습니다. 그리고 몇 가지 변경 사항을 더 구현해 보겠습니다. 우리는 시스템을 위한 새로운 함수를 만들려고 합니다. 그 내용은 다음과 같습니다:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

이 함수가 호출되면 모든 지표 객체의 이름이 변경됩니다. 즉 주문 티켓을 가리키는 값이 다른 값으로 바뀝니다. 이 경우가 우리가 이 주제를 시작할 때 생각했던 지표입니다. 우리에게는 아직 질문이 하나 더 있습니다. 저는 지표 객체 목록을 유지하기 위해 어떤 구조도 사용하지 않고 다른 방식으로 관리합니다. 이렇게 하면 MetaTrader 5가 이 목록을 자동으로 처리합니다. 하지만 이 때문에 저는 플로팅 오더를 무제한으로 생성할 수 없게 됩니다. 하나의 플로팅 오더만 생성할 수 있게 제한 받기 때문입니다. 이는 다음 줄을 사용하여 확인할 수 있습니다:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

여기서의 확인은 간단합니다: 지표 라인이 어딘가에 있으면 매크로가 0과 다른 값을 반환하므로 예약된 티켓을 사용하는 지표가 이미 있다는 것을 알 수 있습니다. 이는 나중에 EA가 요청이 거부된 보조지표의 데이터를 복원하는 데 중요합니다. MetaTrader 5는 비트맵 객체의 상태를 자동으로 변경하므로 호출자에게 실패에 대해 알려야 합니다.

다음으로 필요한 변경 사항은 지표를 만드는 함수입니다:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

강조 표시된 부분은 모두 새로운 시스템을 지원하기 위해 추가되었습니다. 기본적으로 우리는 여기에 항상 true로 설정되는 확인란을 생성하여 주문이 주문서에 즉시 접수되도록 합니다. 저는 이 거래 방식을 수정하고 싶지 않았지만 단순히 체크박스의 값을 'true'에서 'false'로 변경하는 것만으로 주문이 바로 체결되지 않는 것은 아닙니다. 이 변경을 하기 위해서는 또 다른 더 깊은 곳의 변경이 필요하며 여기서 문제는 어느 시점에서는 여러분이 주문을 하다가 확인란을 체크하는 것을 잊어버릴 수 있다는 것입니다. 만약 진입 지점을 놓치게 되면 여러분은 EA에 결함이 있다고 생각할 수도 있지만 실제로는 단순한 건망증인 것입니다. 따라서 이를 방지하려면 명시적으로 상태를 변경해야 합니다. 기본적으로는 펜딩 오더가 호가창으로 바로 이동하기 때문입니다.

다음으로 중요한 함수는 다음과 같습니다:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

강조 표시된 코드 부분은 흥미로운 기능을 수행합니다: 이 부분은 셀렉터에 사용될 값만 업데이트하지만 이러한 값은 실제로 지표 자체에 저장됩니다. 보다 일반적인 방식으로 시스템을 이동하는 경우도 있으므로 위치 계산을 수행하는 함수가 올바른 값을 지정할 수 있도록 셀렉터에 이러한 값들을 지정해야 합니다.

이 함수에는 이해가 되지 않는 부분이 있습니다. 이 함수는 펜딩 오더의 데이터를 생성하고 수정할 책임이 있지만 이 데이터를 보면 펜딩 오더가 호가창으로 반환되는 지점을 볼 수 없습니다. 차트에서 직접 주문의 가치 볼륨을 이동, 수정 및 조정할 수 있지만 차트에 어떻게 반환되는지는 확인할 수 없습니다.

사실입니다. 지정가 주문 변경 및 생성을 위한 전체 시스템이 위의 함수에서 구현됩니다. 이상하게도 이 함수는 우리가 원한다고 해서 주문창에 주문을 다시 넣는 것이 아니라 아래와 같이 실제로 요청을 하기 때문에 주문을 다시 넣습니다. 복잡하게 만들지 않기 위해 시장 심도에 주문 요청을 담당하는 부분만 보여드리겠습니다.

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

// ... Internal code...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

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

시스템이 점점 더 커짐에 따라 프로그래밍의 양이 점점 줄어드는 것을 볼 수 있습니다.

강조 표시된 코드는 주제의 시작 부분에서 만든 지표와 관련이 있습니다. 모든 것이 잘 작동하는 것 처럼 보이지만 나중에 변경될 몇 가지 사항이 있습니다. 플로팅 주문이 호가창으로 돌아오게 되면 데이 트레이딩 주문이기 때문에 하루가 끝나면 마감되게 됩니다. 이 부분은 추후 변경될 예정이지만 이 점을 숙지하고 있으셔야 합니다. 이제 이 모든 것이 혼란스러우실 수도 있고 확인란을 클릭했을 때 체결되지 않은 주문이 실제로 주문창에 어떻게 들어오고 나가는지 이해하지 못하실 수도 있습니다. 아래 다이어그램을 참조하세요:

모든 호출이 같은 곳에서 걸려오는지 확인합니다. 시장 심도에서 주문이 삭제되었지만 차트에는 계속 표시됩니다. 모든 조작은 이전 글에서 설명한 대로 수행됩니다. 그러나 주문이 시장 심도로 돌아가는 특정 시간을 찾으려고 하면 여러분은 코드에서 약간의 혼란을 겪을 수 있습니다. 이제 다이어그램을 보면 이 호출이 DispatchMessage 함수에서 나온다는 것을 알 수 있는데 이 함수가 SetPriceSelection 함수를 호출하는 유일한 곳이기 때문입니다. 그러나 SetPriceSelection 함수를 살펴보면 플로팅 시스템에서 사용되는 인덱스로 주문을 생성하는 참조가 없다는 것을 알 수 있습니다. 하지만 한 가지 주의할 점이 있습니다. 인덱스 0에 의해 생성된 주문이 있는데 이것이 바로 우리가 사용하는 방식입니다. 주문 티켓을 변경하고 이 티켓이 인덱스 0 티켓이 될 것임을 알려주면 주문이 생성됩니다. 작동 원리는 아래 코드를 참조하세요.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

강조 표시된 줄을 제외하고는 코드가 완벽합니다. 현재로서는 이 문제를 해결할 수 있는 방법이 없습니다. 클래스 자체를 변경해야 하므로 이 작업은 다음 글에서 다룰 예정입니다.

아래 동영상은 변경된 결과를 보여줍니다. 볼륨이 수정되는 방식과 지정된 시점에 새 주문이 전송되는 방식에 주의하세요. 이제 우리는 EA를 훨씬 더 쉽게 사용할 수 있습니다.



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

트레이딩 전문가 어드바이저를 처음부터 개발하기(27부): 다음을 향해(II) 트레이딩 전문가 어드바이저를 처음부터 개발하기(27부): 다음을 향해(II)
차트에서 좀 더 완전한 주문 시스템을 살펴보겠습니다. 이 글에서는 주문 시스템을 수정하거나 오히려 더 직관적으로 만드는 방법을 보여드리겠습니다.
프랙탈로 트레이딩 시스템 설계하는 방법 알아보기 프랙탈로 트레이딩 시스템 설계하는 방법 알아보기
이 글은 가장 인기 있는 보조지표를 기반으로 트레이딩 시스템을 설계하는 방법에 대한 시리즈의 새로운 글입니다. 우리는 프랙탈 지표인 새로운 지표에 대해 배우고 이를 기반으로 MetaTrader 5 터미널에서 실행될 거래 시스템을 설계하는 방법을 알아볼 것입니다.
Expert Advisor 개발 기초부터(28부): 미래를 향해(III) Expert Advisor 개발 기초부터(28부): 미래를 향해(III)
아직 우리의 주문 시스템에는 미흡한 부분이 하나 있습니다. 조만간 해결하도록 하겠습니다. MetaTrader 5는 주문 값을 생성하고 수정할 수 있는 티켓 시스템을 제공합니다. 이 아이디어는 동일한 티켓 시스템을 더 빠르고 효율적으로 만들 수 있는 EA를 만드는 것입니다.
모집단 최적화 알고리즘: 침입성 잡초 최적화(IWO) 모집단 최적화 알고리즘: 침입성 잡초 최적화(IWO)
다양한 조건에서 살아남는 잡초의 놀라운 능력은 강력한 최적화 알고리즘을 만들기 위한 아이디어가 되었습니다. IWO는 앞서 검토한 알고리즘 중 가장 우수한 알고리즘 중 하나입니다.