English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터 (파트 11): 교차 주문 시스템(Cross order system)

Expert Advisor 개발 기초부터 (파트 11): 교차 주문 시스템(Cross order system)

MetaTrader 5트레이딩 | 16 11월 2022, 14:52
351 0
Daniel Jose
Daniel Jose

소개

트레이더의 삶을 매우 어렵게 만드는 한 가지 자산이 있습니다. - 바로 선물 계약입니다. 왜 선물이 어렵게 만드는 것일까요? 금융 상품의 계약이 만료되면 새로운 계약이 생성되고 우리는 거래를 하게 됩니다. 실제로 계약이 만료되면 우리는 분석을 끝내고 모든 것을 템플릿으로 저장하고 이 템플릿을 새로운 계약으로 가져와 분석을 계속해야 합니다. 이것은 이런 자산을 거래하는 사람이라면 누구나 겪는 일이지만 선물 계약도 그 내역이 있고 이를 사용하여 지속적으로 분석할 수 있습니다.

전문 트레이더는 특정한 과거의 정보를 분석하는 것을 좋아하며 이 경우 두 번째 차트가 필요합니다. 그러나 적절한 도구를 사용하면 두 번째 차트가 필요하지 않습니다. 이러한 도구 중 하나는 교차 주문 시스템을 사용하는 것입니다.


계획

이 시리즈의 첫 번째 기사에서 이미 이러한 유형의 주문에 대해 언급했지만 구현을 하지는 않았습니다. 그 기사에서 우리는 MetaTrader 5 플랫폼에서 작동할 수 있는 시스템을 만들었고 다른 부분에 중점을 두었습니다. 이 기사에서는 이 기능을 구현하는 방법을 보여줍니다.

이 기능을 만든 이유를 이해하려면 다음 두 이미지를 살펴보세요.

           

왼쪽의 이미지는 전형적인 선물계약의 경우이며 차트에서 볼 수 있듯이 며칠 전 부터의 MINI DOLLAR FUTURE입니다. 오른쪽 차트는 동일한 계약을 보여주고 실제로 만료된 계약의 가치를 나타내는 추가 데이터를 포함하므로 오른쪽 차트는 과거 차트입니다. 오른쪽 차트는 이전 지지선과 저항선을 분석하는 데 더 적합합니다. 그러나 거래를 하려고 할 때 문제가 발생합니다. 아래와 같습니다:

          

보시다시피 차트 트레이드(CHART TRADE)에 거래되는 심볼이 명시되어 있고 거래 이력을 확인했을 때도 차트 트레이드로 주문을 보낼 수 있다고 나와있습니다 - 이것은 툴바에서 볼 수 있습니다. 왼쪽 이미지에서 차트에는 현재 계약에 대해 생성된 주문이 있지만 오른쪽 이미지에서는 주문을 메시지 상자에서만 볼 수 있고 차트에는 아무것도 표시되지 않습니다.

이것이 단지 모니터에 나타나는지 여부의 문제라고 생각할 수도 있지만 아닙니다. 모든 것이 훨씬 더 복잡합니다. 이것이 우리가 이 기사에서 다룰 내용입니다.

중요! 이제 작업에 대한 히스토리 데이터를 사용할 수 있는 규칙을 만드는 방법을 살펴보겠습니다. 우리의 경우 브라질 거래소(B3)에서 거래되는 미니 달러(WDO) 및 미니 인덱스(WIN)를 사용하여 이러한 규칙을 살펴보겠습니다. 이에 대해 이해하면 전 세계 모든 거래소의 모든 유형의 선물 계약에 대해 같은 규칙을 적용할 수 있을 것입니다.

시스템은 자산이 무엇이든 제한되지 않으며 관련된 코드를 변경하는 것과 관련됩니다. 이것이 올바르게 수행되면 자산 계약이 만료되는지 여부와 다음 계약이 무엇인지에 대해 걱정할 필요가 없는 Expert Advisor를 얻게 될 것입니다. - EA가 필요에 따라 계약을 변경할 것입니다.


게임의 규칙을 이해하는 방법

WDO(미니 달러), WIN(미니 지수), DOL(달러 선물) 및 IND(지수 선물) 선물 계약은 만기 및 계약 사양과 관련하여 매우 구체적인 규칙을 따릅니다. 먼저 계약 만료 날짜를 찾는 방법을 살펴보겠습니다:


강조 표시된 정보에 주의하십시오: 파란색은 계약 만료 날짜를 나타내고 빨간색은 계약이 만료되어 더 이상 거래되지 않는 날짜를 나타냅니다. 이것을 아는 것은 매우 중요합니다.

계약 기간은 계약 자체에 지정되지만 이름은 지정되지 않습니다. 다행히도 우리는 시장 전체에서 사용되는 엄격한 규칙에 따라 이름을 쉽게 찾을 수 있습니다. 달러 및 지수 선물 계약의 경우 다음과 같습니다:

처음 세 글자는 계약 유형을 나타냅니다:

코드 계약
WIN 미니 Ibovespa 지수 선물 계약 
IND Ibovespa 지수 선물 계약
WDO 미니 달러 선물 계약
DOL 달러 선물 계약

이 코드 뒤에 계약 만료의 월을 나타내는 문자가 옵니다.

만기 월 WDO 및 DOL을 나타내는 문자 WIN과 IND를 나타내는 문자 
1월 F
 
2월  G  G
3월  H  
4월  J  J
5월  K  
6월  M  M
7월  N  
8월  Q  Q
9월  U  
10월  V  V
11월  X  
12월  Z  Z

그 뒤에 계약이 만료 되는 연도를 나타내는 두 자리 숫자가 옵니다. 예를 들어 2022년 4월에 만료되는 달러 선물 계약은 DOLJ22로 표시됩니다. 이는 5월 초까지 거래 가능한 계약이라는 것입니다. 5월이 시작되면 계약이 만료됩니다. WIN과 IND의 규칙이 약간 다르기 때문에 계약은 실제로 표시된 달의 15일에 가장 가까운 수요일에 만료됩니다. 이와 같이 규칙은 좀 더 복잡해지지만 EA가 이를 관리할 수 있으며 항상 올바른 계약을 제공할 것입니다.


구현

우리 EA는 이미 규칙을 받는 데 필요한 것들을 가지고 있습니다. 여기에서 주문 전송 시스템과 관련된 몇 가지 설정을 구현해야 합니다. 이제 작업을 시작하겠습니다. 먼저 C_Terminal 클래스 객체에 다음 코드를 추가합니다.

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

이 코드는 위에서 본 규칙을 사용하여 자산 이름을 생성합니다. 항상 현재 계약을 사용하도록 하기 위해 강조 표시된 줄에 표시된 부분을 구현합니다. 이때 자산은 플랫폼에 대해 유효해야 하며 EA는 생성된 이름을 사용합니다. 다른 선물 계약으로 작업하려면 경우에 따라 이름이 다를 수 있습니다. 그러므로 이름이 올바르게 생성되도록 이전 코드를 수정해야 합니다. 코드는 연결된 자산에만 국한되지 않습니다. 그러나 올바른 규칙을 사용하는 한 코드는 모든 유형의 선물 계약에 사용할 수 있습니다.

다음은 주문 세부 정보가 있는 부분입니다. 이 개발 단계에서 시스템을 사용하면 다음과 같이 동작하는 것을 볼 수 있습니다.


즉 지금도 교차 주문 모드를 사용할 수 있지만 아직 완전히 구현되지 않았습니다. - 차트에 주문 표시가 없습니다. 수평선을 사용하여 주문을 표시해야 하므로 구현하기가 그리 어렵지 않습니다. 하지만 그게 다가 아닙니다. 교차 주문을 사용할 때 MetaTrader 5에서 제공하는 몇 가지 사항을 놓치게 됩니다. 그러므로 주문 시스템이 안전하고 안정적으로 작동할 수 있도록 누락된 논리를 구현해야 합니다. 그렇지 않고 교차 주문을 사용하면 문제가 발생할 수 있습니다.

이러한 관점에서 보면 문제가 그렇게 간단해 보이지 않습니다. 왜냐하면 이러한 경우에는 MetaTrader 플랫폼이 원래 제공하는 모든 로직을 우리가 직접 만들어야 하기 때문에 간단하지 않습니다. 따라서 가장 먼저 해야 할 일은 MetaTrader의 내부 시스템을 잊어버리는 것입니다. - 교차 주문 시스템을 사용하기 시작한 순간부터는 MetaTrader의 내부 시스템을 사용할 수 없다고 생각하는 것입니다.

이제부터는 주문 티켓이 규칙을 만듭니다. 그러나 이렇게 하면 몇 가지 부정적인 결과를 가져옵니다. 가장 부정적인 것 중 하나는 차트에 얼마나 많은 주문이 있는지를 알 수가 없다는 것입니다. 주문 수를 제한하는 것은 거래자에게 확실히 불쾌할 것입니다. 따라서 일반적으로 MetaTrader에서 사용하는 것처럼 거래자가 시스템을 동일한 방식으로 사용할 수 있도록 해야 합니다. 이것이 해결해야 할 첫 번째 문제입니다.


클래스 C_HLineTrade

이 문제를 해결하기 위해 새로운 클래스 C_HLineTrade를 만들어 원래 MetaTrader 5에서 제공하는 차트에 주문을 표시하는 시스템을 대체하도록 할 것입니다. 이제 클래스 선언부터 시작하겠습니다.

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

여기에 몇 가지 사항이 정의되어 있습니다. - 이것들은 코드에서 자주 사용될 것입니다. 따라서 추가적인 변경 사항에 많은 주의를 기울이십시오. - 실제로 많은 변경 사항이 있을 것입니다. 다음으로 클래스 생성자와 소멸자를 선언합니다.
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

생성자는 원래의 라인이 보이지 않도록 할 것이고 소멸자는 원래의 라인을 차트에 다시 넣습니다. 두 함수 모두 다음과 같은 함수를 가지고 있습니다.

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

강조 표시된 선은 객체(이 경우 수평선)가 클래스에서 사용하는 객체 중 하나인지를 확인합니다. 맞으면 객체를 삭제합니다. 우리는 얼마나 많은 객체를 가지고 있는지 모르지만 시스템은 객체들을 검사하여 클래스에 의해 생성된 모든 것을 정리하려고 합니다. 이 클래스에서 주요 함수는 아래와 같습니다:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
이 함수의 경우 생성될 객체의 수와 해당 객체가 존재하는지 여부는 중요하지 않습니다. 라인이 생성되고 올바른 위치에 배치되도록 합니다. 이 새롭게 생성된 라인은 MetaTrader에서 원래 사용된 라인을 대체합니다.

우리의 목적은 멋지고 아름답기보다는 기능적으로 만드는 것입니다. 이것이 라인이 생성될 때 선택되지 않는 이유입니다. - 필요한 경우 여러분이 이러한 작동을 변경할 수 있습니다. 그러나 저는 라인을 배치하기 위해 MetaTrader 5 메시징 시스템을 이용합니다. 따라서 라인을 이동하려면 이를 명시적으로 나타내야 합니다. 조정 중인 라인을 나타내기 위해 다음과 같은 또 다른 함수가 있습니다:

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
이 함수는 라인을 선택하는 것을 구현합니다. 다른 라인이 선택되어 있으면 이전의 선택이 취소됩니다. 이것은 모두 간단합니다. 이 함수는 클래스에서 실제로 처리하는 행만 조작합니다. 이 클래스의 또 다른 중요한 함수는 다음과 같습니다:
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
이 함수는 아마도 이 클래스에서 가장 중요한 함수일 것입니다: 차트에 몇 개의 라인이 있는지 모르기 때문에 사용자가 어떤 라인을 다루고 있는지 알아야 합니다. 이 함수는 바로 그러한 일을 합니다. - 이 함수는 어떤 라인이 다루어지고 있는지를 시스템에 알려줍니다.

그러나 이것은 우리가 해야 할 일의 극히 일부일 뿐입니다. 시스템이 제대로 작동하려면 아직 멀었습니다. 이제 다음 단계로 넘어가겠습니다. - 주문 라우팅을 담당하는 C_Router 클래스에 함수를 추가하고 수정하겠습니다. 이 클래스는 C_HLineTrade 클래스에서 방금 만든 기능을 상속합니다. 다음 코드를 참조하십시오:

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade



새 클래스 C_Router

소스 C_Router 클래스에는 주문이 한개만 열리도록 하는 제한이 있습니다. 우리는 이 제한을 해제할 것입니다. 그러므로 C_Router 클래스에 중요한 변경 사항을 적용해야 합니다.

첫 번째 변경 사항은 클래스 업데이트 함수와 관련된 것으로 다음과 같습니다.

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

이전에는 이 함수는 진입한 한 개의 포지션에 대해서만 데이터를 수집하여 저장했습니다. 이제 이 함수는 모든 진입한 포지션과 미체결 중인 주문을 차트에 표시할 것입니다. 이렇게 MetaTrader에서 제공하는 시스템을 완전히 대체하는 것입니다. 이러한 변경은 중요한 사항으로 그 작동 방식을 이해하는 것이 중요합니다. 실패할 경우 교차 주문 시스템 전체에 영향을 미치기 때문입니다. 따라서 실제 계정에서 거래하기 전에 데모 계정에서 이 시스템을 테스트해 봐야 합니다. 이러한 시스템은 모든 것이 제대로 작동하는지 확신할 때까지 충분하게 테스트 되어야 합니다. 우선 우리가 구성하는 시스템은 MetaTrader 5가 작동하는 방식과 약간 다르게 작동합니다.

강조 표시된 줄을 보고 정직하게 대답해 보십시오: 이 줄이 실제로 하는 일이 무엇인지 분명한가요? 이 두 코드 줄이 여기에 있는 이유는 나중에 이 기사의 뒷부분에서 C_OrderView 클래스에 대해 다룰 때 명확해질 것입니다. 이 두 줄이 없으면 코드가 매우 불안정하고 이상하게 작동합니다. 나머지 코드는 매우 간단합니다. - C_HLineTrade 클래스 객체를 통해 각 라인을 생성합니다. 이 경우 선택할 수 없는 한 라인만 있습니다. 이는 아래 코드와 같이 나타납니다:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

다른 말로 하면 시스템이 매우 간단해 졌다는 말입니다. 이 함수는 OnTrade의 이벤트에서 EA에 의해 호출됩니다:

C_TemplateChart Chart;

// ... Expert Advisor code ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

// ... The rest of the Expert Advisor code ...

강조 표시된 코드는 화면에서 명령을 업데이트합니다. 이를 위해 C_TemplateChart 차트를 사용하고 있다는 것을 주목하십시오. - 이는 시스템의 클래스 구조가 변경되었기 때문입니다. 새로운 구조는 아래와 같습니다:

이 구조는 EA 내에서 메시지의 흐름을 가능하게 합니다. 메시지 흐름이 특정 클래스로 들어가는 방법이 의심스러울 때마다 이 클래스 상속 그래프를 살펴보십시오. public으로 간주되는 유일한 클래스는 C_Terminal 객체 클래스이고 다른 모든 클래스는 클래스 간의 상속에 의해 처리되며 이 시스템에서는 변수가 하나도 공개되지 않습니다.

이제 시스템은 주문 하나만을 분석하는 것이 아니기 때문에 다른 부분도 이해해야 합니다: 작업 결과를 어떻게 이해할 수 있을까요? 왜 그것이 중요할까요? 진입한 포지션이 하나만 있을 때 시스템은 모든 것을 쉽게 이해할 수 있지만 진입한 포지션의 수가 증가할수록 여러분은 무슨 일이 일어나고 있는지를 파악해야 합니다. 이 정보를 제공하는 함수는 다음과 같습니다:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}

많은 변화가 없습니다. 강조 표시된 함수 코드를 살펴보십시오:

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                        
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

이 함수는 3개의 강조 표시된 부분으로 구성됩니다. 노란색 부분은 진입한 포지션의 결과를 알려주고 녹색 부분은 높은 변동성으로 인해 손절매를 놓친 경우의 포지션을 확인합니다. 이 경우 가능한 빨리 포지션을 청산해야 합니다. 따라서 이 함수는 자산에 대해 하나의 포지션이 미결인 경우를 제외하고 단일 포지션의 결과를 반환하지 않습니다.

교차 주문 모델을 사용할 때 시스템이 계속 작동하도록 도와주는 다른 함수도 있습니다. 아래 코드에서 살펴보세요:

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
첫 번째는 아직 미결인 주문을 수정하는 것을 담당하고 다른 하나는 보유 포지션을 수정합니다. 이 둘은 같은 것처럼 보이지만 그렇지 않습니다. 시스템에는 또 다른 중요한 함수가 있습니다.
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

이상의 함수로 C_Router 클래스에 대해 살펴보았습니다. 우리는 MetaTrader에서 일반적으로 지원되는 기능을 가진 기본 시스템을 구현했습니다. - 그러나 그렇지 않습니다. 교차 주문 시스템은 더 이상 그러한 기능을 지원을 하지 않습니다. 그러나 아직 시스템이 완성된 것은 아닙니다. 시스템이 실제로 작동하도록 하려면 다른 것을 추가해야 합니다. 현재 주문이 있다면 아래와 같을 것입니다. 이것은 다음 단계를 완료하는 데 필요합니다.



위의 이미지를 자세히 살펴보세요. 메시지 상자에는 진입 주문과 해당 주문이 진입한 자산이 표시됩니다. 거래된 자산은 CHART TRADE에 표시됩니다. 이 자산은 메시지 상자에 표시된 것과 동일한 자산입니다. 이제 차트에 표시된 자산을 확인해 보겠습니다. 자산명은 차트 창 헤더에서 확인할 수 있습니다. 하지만 완전히 다릅니다. - 차트에 있는 자산은 아니라 미니 인덱스 히스토리입니다. 이는 우리가 MetaTrader 5 내부의 시스템을 사용하지 않고 이 기사에서 설명하고 있는 교차 주문 시스템을 사용한다는 것을 의미합니다. 이제 주문이 있는 위치를 표시할 수 있는 기능에 대해 알아봅니다. 그러나 우리는 교차 주문 시스템을 통해 작동 가능한 완전한 기능의 시스템을 원하기 때문에 이것으로는 충분하지 않습니다. 그래서 우리는 더 필요한 것이 있습니다. 주문 이동과 관련된 이벤트는 다른 클래스에서 구현될 예정입니다.


C_OrderView 클래스의 새로운 기능

C_OrderView 객체 클래스는 아직 미결 또는 보류 중인 주문 데이터를 처리할 수 없습니다. 그러나 메시징 시스템을 추가하면 더 많은 것을 사용할 수 있습니다. 이것이 지금 우리가 클래스에 추가 하려고 하는 것입니다. 전체 함수 코드는 다음과 같습니다:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

이 코드를 통해 교차 주문 시스템이 완성됩니다. 이제 교차 주문 시스템 없이 가능했던 것과 거의 동일한 작업을 수행할 수 있습니다. 일반적으로 함수는 매우 명확해야 합니다. 그러나 매우 일반적이지 않은 이벤트 유형(CHARTEVENT_OBJECT_DELETE)이 있습니다. 사용자가 라인을 삭제하면 차트와 주문 시스템에 반영되므로 차트에서 라인 삭제를 시작할 때 매우 주의해야 합니다. 다음 애니메이션과 같이 주문이 그대로 유지되므로 차트에서 EA를 제거할 때 걱정할 필요가 없습니다:


그러나 EA가 차트에 있는 경우 라인, 특히 객체 목록에 숨겨진 라인을 삭제할 때 매우 주의해야 합니다. 그렇지 않으면 교차 주문 시스템에서 생성된 라인을 제거할 때 주문 시스템에서 아래와 같은 일이 발생합니다.

시스템 데모를 마치기 위해 가격 라인을 드래그 할 때 주문에 어떤 일이 발생하는지 보겠습니다. 다음 사항을 기억하십시오. 드래그 한 라인을 선택해야 합니다: 그렇지 않으면 라인을 이동할 수 없습니다. 가격 변동은 차트에서 라인이 해제될 때 발생하며 그 전에는 가격이 이전과 동일한 위치에 유지됩니다.


라인이 선택 되었는지 여부를 알기 어렵다면 선택하는 것과 관련한 코드를 변경해 봅시다. 변경 사항은 아래에 강조 표시되어 있습니다. 이 변경 사항은 첨부된 버전에 이미 구현되어 있습니다.

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

이와 같은 코드 수정의 결과는 아래 그림에서 볼 수 있습니다.


결론

이상 MetaTrader에서 교차 주문 시스템을 만드는 방법을 살펴 봤습니다. 이 시스템이 이상의 지식을 얻은 모든 사람에게 유용하기를 바랍니다. 그러나 다음 사항을 기억하십시오: 이 시스템으로 라이브 계정 거래를 시작하기 전에 다양한 시장 시나리오에서 최대한 철저하게 테스트해야 합니다. 이 시스템은 MetaTrader 플랫폼에서 구현되지만 오류 처리 등은 플랫폼으로 지원되는 것이 거의 없기 때문입니다. 에러가 발생할 경우 큰 손실을 입지 않도록 신속하게 조치를 취해야 합니다. 테스트를 통해 다양한 시나리오에서 문제가 발생하는 지점을 찾을 수 있습니다: 이동, 컴퓨터가 처리할 수 있는 최대 주문 수, 분석 시스템의 최대 허용 스프레드, 보유 주문에 대한 변동성 허용 수준. 주문 및 분석할 정보가 많을수록 에러가 발생할 가능성이 높아집니다. 왜냐하면 시스템은 수신한 매 틱마다 각각의 주문을 분석하기 때문이며 동시에 많은 주문이 열릴 경우 문제가 될 수 있습니다.

많은 시나리오를 통해 데모 계정에서 테스트할 때까지는 이 시스템을 신뢰하지 않는 것이 좋습니다. 코드가 완벽해 보여도 코드가 오류 분석을 하는 것은 아닙니다.

Expert Advisor의 현재 기준의 모든 코드를 첨부합니다.


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

파일 첨부됨 |
EA.zip (12013.17 KB)
OBV로 트레이딩 시스템을 설계하는 방법 알아보기 OBV로 트레이딩 시스템을 설계하는 방법 알아보기
이 기사는 인기있는 지표 중 일부를 기반으로 거래 시스템을 설계하는 방법에 대해 초보자들에게 설명하는 시리즈에서 계속되는 새로운 기사입니다. OBV(On Balance Volume)라는 새로운 지표를 배우고 이를 어떻게 사용하고 이를 기반으로 하는 거래 시스템을 어떻게 설계하는지 알아보겠습니다.
Expert Advisor 개발 기초부터 (파트 10): 맞춤형 지표 액세스하기 Expert Advisor 개발 기초부터 (파트 10): 맞춤형 지표 액세스하기
Expert Advisor에서 어떻게 맞춤형 지표에 바로 액세스 할 수 있을까요? 트레이딩 EA는 맞춤형 지표를 사용할 수 있는 경우에 더욱 유용할 수 있습니다; 그렇지 않으면 코드와 명령의 집합일 뿐입니다.
누적/분배(Accumulation/Distribution (AD))에 기반한 거래 시스템을 설계하는 방법 누적/분배(Accumulation/Distribution (AD))에 기반한 거래 시스템을 설계하는 방법
이 글은 가장 인기 있는 기술 지표를 기반으로 거래 시스템을 설계하는 방법에 대한 시리즈의 새로운 글입니다. 이 글에서는 누적/분배 지표라는 새로운 기술 지표에 대해 배우고 간단한 AD 거래 전략을 기반으로 하여 MQL5 거래 시스템을 설계하는 방법에 대해 알아봅니다.
MetaTrader 5에서 DirectX를 사용하여 3D 그래픽을 만드는 방법 MetaTrader 5에서 DirectX를 사용하여 3D 그래픽을 만드는 방법
3D 그래픽은 숨겨진 패턴을 시각화 할 수 있습니다. 그러므로 방대한 양의 데이터를 분석하는 데 탁월합니다 이러한 작업은 MQL5에서 직접 해결할 수 있는데 DireсtX 함수를 사용하면 3차원 객체를 만들 수 있습니다. 따라서 MetaTrader 5용 3D 게임과 같은 복잡한 프로그램을 만드는 것도 가능합니다. 간단한 3차원 도형을 그리는 것으로 3D 그래픽을 배워보세요.