English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
하나의 차트에 여러개의 지표 넣기(파트 05): MetaTrader 5를 RAD 시스템으로 전환하기(I)

하나의 차트에 여러개의 지표 넣기(파트 05): MetaTrader 5를 RAD 시스템으로 전환하기(I)

MetaTrader 5트레이딩 | 28 7월 2022, 11:09
180 0
Daniel Jose
Daniel Jose

소개

세상에는 프로그래밍을 할 줄은 모르지만 상당히 창의적이고 훌륭한 아이디어를 가지고 있는 사람들이 많이 있습니다. 그러나 이들은 프로그래밍 지식이 부족하여 이러한 아이디어를 구현하지 못합니다. 오늘은 시장가 주문을 보내거나 혹은 지정가 주문에 사용되는 매개변수를 설정하는 데에 쓰이는 차트 트레이드 인터페이스를 만들어 볼 것입니다. 프로그래밍 없이 Expert Advisor 내부에 있는 기능만 사용하여 이 작업을 수행할 것입니다. 우선 모니터에서 어떻게 보이는지 봅시다.


여러분들은 "그런데 어떻게 하지? 라고 생각 할지도 모르겠습니다. “나는 프로그래밍에 대해 아는 것이 없고, 내가 아는 것만으로는 지금 만들려는 것을 개발 하기에 충분하지 않은데." 라고 생각 할지도 모릅니다. 위 이미지에서 볼 수 있는 차트 트레이드는 MetaTrader 5 플랫폼 자체에서 생성되었으며 아래 이미지와 같이 설계되었습니다:


이제 이 글에서 다룰 내용을 아셨을 것입니다. 그러면 우리만의 차트를 만들기 위한 열정과 아이디어로 가득 차야 합니다. 그러나 모든 작업을 수행하려면 몇 가지 단계를 완료해야 합니다. 일단 보조 코드가 설정되면 차트 트레이드 IDE를 디자인하는 것은 우리의 창의성에 달려 있을 것입니다. 이 기사는 이전 기사의 연속이므로 완전하고 포괄적인 이해를 위해 이 시리즈의 이전 기사를 읽는 것이 좋습니다.

자, 작업을 시작하겠습니다.


계획

먼저 IDE로 사용할 차트의 속성을 편집해야 합니다. 이것은 잠재적인 에러를 줄이기 위해 수행됩니다. 요점은 차트를 깨끗하게 유지하면 차트 트레이드의 인터페이스를 구축하고 디자인하는 것이 더 쉬워진다는 것입니다. 따라서 차트 속성을 열고 아래 그림과 같이 속성을 설정합니다.

     

이렇게 하면 화면은 깨끗하고 IDE 개발을 방해할 것이 없어지게 됩니다. 이제 다음 설명에 주의하십시오. IDE는 설정 파일, 즉 TEMPLATE로 저장되므로 MetaTrader 5에서 제공하는 모든 객체를 사용할 수 있지만 현실적인 이유로 일부만 사용할 것입니다. 사용 가능한 모든 객체에 대해서는 MetaTrader 5의 객체 유형을 참조하십시오.

Object 위치 지정에 사용되는 좌표 유형 IDE에 대한 흥미 
Text 날짜와 가격  아니오
Label X 및 Y 위치  예
Button  X 및 Y 위치  예
Graph  X 및 Y 위치  예
Bitmap  날짜와 가격  아니오
Bitmap Label  X 및 Y 위치  예
Edit  X 및 Y 위치  예
Event  날짜만 사용  아니오
Retangle Label X 및 Y 위치  예

우리는 화면의 모든 영역에 위치할 수 있는 시스템을 사용할 것입니다. 그러므로 위치 지정을 위해 X 나 Y 좌표계를 사용하지 않는 객체를 사용하는 것은 좋지 않습니다. 이러한 객체는 IDE를 완전히 다르게 보이도록 만들 수 있기 때문입니다. 따라서 인터페이스를 생성하기에 충분한 6개의 객체로 시스템을 제한할 것입니다.

아이디어는 객체의 정렬을 화면에 무언가를 그리는 것과 유사한 논리적 순서로 하는 것입니다. 먼저 배경을 만드는 것으로 시작한 다음 인터페이스를 개발하면서 객체를 배치하고 조정하고 객체를 서로 위에 놓습니다. 진행 방법은 다음과 같습니다:

    

    

모든 것이 매우 간단합니다. 이 방법은 자신만의 IDE를 설계하고 생성하는 방법이며 전부 마스터 하려면 약간의 연습이 필요합니다. 지금 보여드리는 아이디어는 코드를 통한 사용자 인터페이스 개발이 매우 복잡할 경우 프로그래밍 인터페이스를 만드는 데 사용되는RAD프로그램에서 사용되는 것과 매우 유사합니다. 코드를 통해 직접 인터페이스를 만들 수 없다는 것은 아닙니다. 그러나 이 방법을 사용하면 추가적인 수정이 필요할 때 훨씬 빠르고 쉬워집니다. 그러므로 자신의 스타일로 인터페이스를 원하는 사람들에게 이상적입니다.

완료되면 아래와 같거나 더 멋진 인터페이스가 나올 수도 있습니다. 하지만 여기에서는 여러분이 사용해 볼 수 있도록 최대한 많은 객체를 사용하려고 했습니다. 여러분은 언제나 자신이 선호하는 인터페이스를 만들 수 있습니다.

이것은 IDE를 만드는 첫 번째 단계입니다. 이제 우리는 이 인터페이스를 실제로 지원하고 작동하게 만드는 코드를 만들어야 합니다. 자신만의 사용자 인터페이스를 만들 수 있다는 단순한 사실도 동기 부여의 원천이 되고 이러한 동기 부여는 코드에 구현될 것입니다.

다음 단계는 이 인터페이스를 설정 파일로 저장하는 것입니다. 이제 파일을 저장하고 이전 버전의 코드를 사용하여 포인터로 표시할 수 있습니다. 즉, 소스 코드를 크게 변경할 필요가 없습니다. 그러나 이벤트를 수신하거나 IDE에 이벤트를 보낼 가능성을 테스트하려는 경우에는 이것이 불가능하다는 것을 알 수 있습니다. 그러나 인터페이스가 MetaTrader 5의 객체를 사용하여 생성된 경우 이러한 객체에서 이벤트를 보내고 받을 수 없는 이유는 무엇일까요? 이 질문에 대한 답은 설명하는 것보다 보여주는 것이 더 쉽습니다. EA의 오리지날 버전에 다음 코드를 추가하여 확인할 수 있습니다.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        Print(sparam);
                        break;
// .... The rest of the code...
        }
}

이 코드는 클릭을 수신하고 이벤트를 생성하는 객체의 이름을 리포트 합니다. 이 경우 이벤트는CHARTEVENT_OBJECT_CLICK입니다. 그러나 프린트된 메시지는 IDE에 있는 객체의 이름이 아니라 EA에서 만든 객체의 이름입니다. 이것은 우리 IDE를 사용하는 것을 불가능하게 만드는 큰 문제처럼 보일 수도 있지만 아주 간단한 해결책이 있습니다: 설정 파일을 읽은 다음 이 파일에 지정된 대로 객체를 생성하는 것입니다. 그러면 차트에 바로 IDE가 생성됩니다. 따라서 설정 파일(TPL)을 분석해서 사용해야 하는 데이터를 찾을 수 있습니다.

KEY 설명
<chart> 설정 파일 시작
</chart> 설정 파일을 종료합니다.
<window> 차트에 있는 요소의 구조를 시작합니다.
</window> 차트에 있는 요소의 구조를 종료합니다.
<indicator> 일부 지표와 관련된 데이터를 제공하는 구조를 시작합니다.
</indicator> 일부 지표와 관련된 데이터를 제공하는 구조를 종료합니다.
<object> 일부 객체에 대한 데이터를 제공하는 구조를 시작합니다.
</object> 객체 데이터를 제공하는 구조를 종료합니다.

이 구조는 TPL 파일 내부에서 다음과 같이 보입니다.

<chart>

.... DATA

<window>

... DATA

<indicator>

... DATA

</indicator>

<object>

... DATA

</object>

</window>
</chart>

우리가 관심을 갖는 부분은<object></object> 사이입니다. 이러한 구조가 여러 개 있을 수 있으며 각각 고유한 객체를 나타냅니다. 따라서 먼저 파일의 위치를 변경해야 합니다. 파일을 읽을 수 있는 위치에 추가해야 합니다. 이것은 FILES 디렉토리입니다. 위치를 변경할 수 있지만 어떤 경우에도 파일은 FILE 트리 안에 있어야 합니다.

중요한 세부 사항: 시스템이 IDE 구성 파일을 사용할 때 차트를 지울 수 있는 수정 요청을 받았지만 여러분은 Profiles\Templates 디렉터리에 같은 이름의 깨끗한 파일을 가지고 있어야 합니다. 이것은 이전 기사에서 본 것처럼 기본 템플릿에 존재할 수 있는 모든 잔여물을 최소화합니다. 주요 변경 사항은 아래에 강조 표시되어 있습니다.

#include <Auxiliar\Chart IDE\C_Chart_IDE.mqh>
//+------------------------------------------------------------------+
class C_TemplateChart : public C_Chart_IDE
{

 .... Other parts from code ....

//+------------------------------------------------------------------+
void AddTemplate(const eTypeChart type, const string szTemplate, int scale, int iSize)
{
        if (m_Counter >= def_MaxTemplates) return;
        if (type == SYMBOL) SymbolSelect(szTemplate, true);
        SetBase(szTemplate, (type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
        if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
        if (szTemplate == "IDE") C_Chart_IDE::Create(m_IdSubWin);
        ChartRedraw(m_handle);
}
//+------------------------------------------------------------------+
void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
                if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0);
        }
        ChartRedraw();
#undef macro_SetInteger
}
//+------------------------------------------------------------------+

... The rest of the code

}

우리는 IDE 인터페이스를 새 클래스로 추가하고 있으며 원래 클래스에 상속됩니다. 즉 원래 클래스의 기능이 확장되고 원래 코드에 아무런 에러가 발생하지 않습니다.

여기까지는 쉬운 부분이었습니다. 이제 IDE를 지원하는 더 복잡한 작업을 수행해야 합니다. 먼저 시스템에서 사용할 메시지 프로토콜을 생성해 보겠습니다. 이 프로토콜을 사용하면 시스템이 아래와 같이 작동할 수 있습니다:


현재는 변경이 불가능한 시스템 데이터를 변경할 수 있지만 메시징 프로토콜을 추가함으로써 IDE가 작동하도록 할 수 있습니다. 따라서 몇 가지를 정의해 보겠습니다:

메시지 목적
MSG_BUY_MARKET 시장가 BUY 주문을 보냅니다.
MSG_SELL_MARKET 시장가 SELL 주문을 보냅니다.
MSG_LEVERAGE_VALUE 데이터 레버리지
MSG_TAKE_VALUE 수익 실현 데이터 거래
MSG_STOP_VALUE 스탑로스 데이터 거래
MSG_RESULT 오픈 포지션의 현재 결과에 대한 데이터
MSG_DAY_TRADE 거래가 당일 거래가 끝날 때 청산될지 여부를 알려줍니다.

이 프로토콜은 매우 중요한 단계입니다. 이를 정의한 후 설정 파일을 변경합니다. 객체 목록을 열 때 다음과 같이 보이도록 변경해야 합니다:

제가 보여주는 인터페이스에는 이미지와 같은 객체 목록이 있습니다. 다음 사실에 주의하시기 바랍니다. 객체의NAME은 우리가 사용할 각 메시지에 해당합니다. 다른 객체의 이름은 IDE 모델링에 쓰이기 때문에 중요하지 않지만 메시지 이름을 가진 객체는 메시지를 수신하거나 전송합니다. 더 많은 메시지나 다른 유형의 메시지를 사용하려면 클래스 코드를 필요한 대로 변경하기만 하면 MetaTrader 5가 IDE와 EA 코드 간에 메시지를 교환할 수 있는 수단을 제공합니다.

그러나 객체 클래스를 생성하는 방법을 배우려면 TPL 파일을 공부해야 합니다. 이제 TPL 파일 내부에서 객체가 선언되는 방식을 알아보겠습니다. 터미널 인터페이스 자체가 객체 속성에 대한 액세스를 덜 제공하기 때문에 프로그래밍을 통한 것보다 TPL 파일의 객체 속성에 대한 액세스가 적은 것이 사실입니다. 그러나 우리가 가진 액세스 권한만으로도 IDE가 작동하기에 충분할 것입니다.

따라서 TPL 파일 내부에는<object>에서</object>까지 필요한 구조가 있습니다. 구조 내부의 데이터를 기반으로 보면 객체 유형을 찾는 방법이 불분명해 보일 수 있습니다. 하지만 자세히 살펴보면type변수에 의해 객체의 타입이 결정된다는 것을 알 수 있습니다. type 변수는 각 객체에 대해 다른 값을 사용합니다. 아래 표는 우리가 사용하고자 하는 객체를 보여줍니다:

TYPE 변수의 값 참조된 객체
102 OBJ_LABEL
103 OBJ_BUTTON
106 OBJ_BITMAP_LABEL
107  OBJ_EDIT
110  OBJ_RECTANGLE_LABEL

우리의 수업은 이미 형태를 갖추기 시작했습니다. 다음은 첫 번째 함수 코드입니다.

bool Create(int nSub)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                                
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
        m_SubWindow = nSub;
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        return true;
}

가장 먼저 할 일은 파일을 읽기 모드에서 바이너리 파일로 여는 것입니다. 이를 통해 놓치는 것이 없게 됩니다. HEXA 편집기를 사용할 때 TPL 파일은 다음과 같습니다. 매우 흥미로운 값으로 시작합니다.

혼란스럽게 들리나요? 사실은 그렇지 않습니다. 파일은UTF-16인코딩을 사용합니다. 우리는 데이터가 줄 단위로 구성되어 있다는 것을 알고 있습니다. 한 번에 전체 줄을 읽는 함수를 만들어 보겠습니다. 이를 위해 다음 코드를 작성해 보겠습니다.

bool FileReadLine(void)
{
        int utf_16 = 0;
        bool b0 = false;
        m_szLine = m_szValue = "";
        for (int c0 = 0; c0 < 500; c0++)
        {
                utf_16 = FileReadInteger(m_fp, SHORT_VALUE);
                if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); return true; } else
                if (utf_16 == 0x003D) b0 = true; else
                if (b0) m_szValue = StringFormat("%s%c", m_szValue, (char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (char)utf_16);
                if (FileIsEnding(m_fp)) break;
        }
        return (utf_16 == 0x003E);
}

읽기는 최대한 효율적으로 하려고 하므로 등호( = )를 만나면 읽기 중에 분리하여 이를 나중에 하지 않도록 합니다. 루프는 문자열을 최대 500자로 제한하지만 이 값은 임의적이며 필요한 경우 변경할 수 있습니다. 각각의 새 문자열이 발견되면 함수는 적절한 분석을 진행할 수 있도록 문자열의 내용을 제공하여 반환합니다.

메시지 프로토콜을 지원하려면 특정 변수가 필요합니다. 이들은 아래 코드에 표시되어 있습니다.

class C_Chart_IDE
{
        protected:
                enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};
//+------------------------------------------------------------------+
#define def_HeaderMSG "IDE_"
#define def_MaxObject eEDIT_STOP + 32
//+------------------------------------------------------------------+
        private :
                int             m_fp,
                                m_SubWindow,
                                m_CountObject;
                string          m_szLine,
                                m_szValue;
                bool            m_IsDayTrade;
                struct st0
                        {
                                string  szName;
                                int     iPosX;
                        }m_ArrObject[def_MaxObject];

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

def_MaxObject정의는 유지할 수 있는 최대의 객체 수를 나타냅니다. 이 숫자는 메시지 수와 사용할 추가 객체 수를 기반으로 합니다. 우리의 경우 최대 40개의 객체가 있지만 필요한 경우 변경할 수 있습니다. 처음 8개 객체는 IDE와 MetaTrader 5 간에 메시지를 보내는 데 사용됩니다. 이러한 메시지의 별칭은eObjectsIDE 열거형에서 볼 수 있습니다. 시스템을 확장하거나 다른 용도로 적용하려는 경우 이를 염두에 두는 것이 중요합니다.

이것은 지원 시스템의 첫 번째 부분일 뿐입니다. 주의해야 할 또 다른 사항이 있습니다: 바로 메시지 시스템을 다루는 상수입니다. 사실 MQL5가 상수를 다루는 방식은 C/C++로 프로그래밍하는 사람들에게 약간 혼란스러울 수 있습니다. C/C++에서 상수는 변수 선언 자체에 선언됩니다. MQL5에는 상수가 생성되는 방식이 코드를 조금 더 복잡하게 만들 수 있습니다. 그러나 상수는 거의 사용되지 않기 때문에 별 문제 없을 것입니다. 이 작업을 수행하는 방법은 아래에 굵게 표시되어 있습니다.

        public  :
                static const string szMsgIDE[];

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

};
//+------------------------------------------------------------------+
static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                             };
//+------------------------------------------------------------------+

정의된 상수는 인터페이스의 객체 이름에 사용된 것과 정확히 동일한 값입니다. 시스템은 대소문자를 구분하지 않도록 설계되었습니다. 원하는 경우 이 동작을 변경할 수 있지만 그렇게 하지 않는 것이 좋습니다.

이 모든 단계를 완료했으면 다음 단계로 넘어갈 차례입니다. 이제 TPL 파일로 돌아가 보겠습니다. 아래 파일 조각을 보십시오:


사용할 객체의 유형을 정의하면 이름, 위치, 색상, 글꼴 등과 같은 객체의 속성을 나타내는 일련의 데이터를 가지게 됩니다. 이러한 속성은 내부 객체에 전달되어야 합니다. 이는 반복적으로 발생하는 일이기 때문에 이를 위한 함수를 만들 수 있습니다. 다음과 같을 것입니다:

bool LoopCreating(ENUM_OBJECT type)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
        int c0;
        bool b0;
        string sz0 = m_szValue;
        while (m_szLine != "</object>") if (!FileReadLine()) return false; else
        {
                if (m_szLine == "name")
                {
                        b0 = false;
                        StringToUpper(m_szValue);
                        for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
                        c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);
                        ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName);
                        ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, 0, 0);
                }
                if (m_szLine == "pos_x"                 ) m_ArrObject[c0].iPosX = (int) StringToInteger(m_szValue);
                if (m_szLine == "pos_y"                 ) macro_SetInteger(OBJPROP_YDISTANCE    , StringToInteger(m_szValue));
                if (m_szLine == "size_x"                ) macro_SetInteger(OBJPROP_XSIZE        , StringToInteger(m_szValue));
                if (m_szLine == "size_y"                ) macro_SetInteger(OBJPROP_YSIZE        , StringToInteger(m_szValue));
                if (m_szLine == "offset_x"              ) macro_SetInteger(OBJPROP_XOFFSET      , StringToInteger(m_szValue));
                if (m_szLine == "offset_y"              ) macro_SetInteger(OBJPROP_YOFFSET      , StringToInteger(m_szValue));
                if (m_szLine == "bgcolor"               ) macro_SetInteger(OBJPROP_BGCOLOR      , StringToInteger(m_szValue));
                if (m_szLine == "color"                 ) macro_SetInteger(OBJPROP_COLOR        , StringToInteger(m_szValue));
                if (m_szLine == "bmpfile_on"            ) ObjectSetString(Terminal.Get_ID()     , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue);
                if (m_szLine == "bmpfile_off"           ) ObjectSetString(Terminal.Get_ID()     , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue);
                if (m_szLine == "fontsz"                ) macro_SetInteger(OBJPROP_FONTSIZE     , StringToInteger(m_szValue));
                if (m_szLine == "fontnm"                ) macro_SetString(OBJPROP_FONT          , m_szValue);
                if (m_szLine == "descr"                 ) macro_SetString(OBJPROP_TEXT          , m_szValue);
                if (m_szLine == "readonly"              ) macro_SetInteger(OBJPROP_READONLY     , StringToInteger(m_szValue) == 1);
                if (m_szLine == "state"                 ) macro_SetInteger(OBJPROP_STATE        , StringToInteger(m_szValue) == 1);
                if (m_szLine == "border_type"           ) macro_SetInteger(OBJPROP_BORDER_TYPE  , StringToInteger(m_szValue));
        }
        m_CountObject += (b0 ? 0 : (m_CountObject < def_MaxObject ? 1 : 0));
        return true;
                        
#undef macro_SetString
#undef macro_SetInteger
}

각 객체에는 이름이 지정되고 객체는 적절한 위치에 저장되지만 강조 표시된 선은 무언가 다른 것을 보여줍니다. IDE를 만들 때 차트의 왼쪽 상단 모서리에서 시작해야 하지만 이 X 위치가 반드시 하위 창의 왼쪽 상단 모서리일 필요는 없습니다. 이 위치는 IDE가 바인딩될OBJ_CHART객체의 왼쪽 상단 모서리와 일치해야 합니다. 이 객체는 IDE 템플릿을 로드할 때 표시되므로 하위 창 내부에 어디든 있을 수 있습니다. 이를 수정하지 않으면 IDE가 올바른 위치에 나타나지 않습니다. 따라서 X 값을 저장하고 이를 나중에 올바른 위치에 객체를 표시하는 데 사용합니다. IDE를 올바르게 렌더링하는 함수는 다음과 같습니다.

객체에 사용되는 기본 정보는 이미 정의되어 있지만, 다른 정보를 추가해야 하는 경우 명령 집합에 추가하고 적절한 값으로 속성을 변경하면 됩니다.

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++)
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

메시지가 어떻게 처리되는지 살펴보기 전에 중요한 두 가지 다른 함수를 분석해 보겠습니다. 시스템은 초기화 중에 수신된 EA로부터 값을 수신할 수 있습니다. 이러한 값은 차트 트레이드를 사용할 때 EA를 콜 할 필요 없이 시장 주문 또는 보류 중인 주문을 보내고 주문을 직접 구성할 수 있도록 올바르게 표시되고 조정되어야 합니다. 두 함수는 아래와 같습니다:

void UpdateInfos(bool bSwap = false)
{
        int nContract, FinanceTake, FinanceStop;

        nContract       = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT));
        FinanceTake = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT));
        FinanceStop = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT));
        m_IsDayTrade = (bSwap ? (m_IsDayTrade ? false : true) : m_IsDayTrade);
        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade);
        NanoEA.Initilize(nContract, FinanceTake, FinanceStop, clrNONE, clrNONE, clrNONE, m_IsDayTrade);
}
//+------------------------------------------------------------------+
void InitilizeChartTrade(int nContracts, int FinanceTake, int FinanceStop, color cp, color ct, color cs, bool b1)
{
        NanoEA.Initilize(nContracts, FinanceTake, FinanceStop, cp, ct, cs, b1);
        if (m_CountObject < eEDIT_STOP) return;
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT, IntegerToString(nContracts));
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT, IntegerToString(FinanceTake));
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT, IntegerToString(FinanceStop));
        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade = b1);
}

IDE는 주문 시스템에 연결 되어 있습니다. 그러므로 시스템에 대한 변경 사항은 주문 시스템에도 반영됩니다. 이렇게 하면 이전처럼 EA에서 데이터를 변경할 필요가 없습니다. 이제 IDE 또는 차트 트레이드에서 직접 이 작업을 수행할 수 있습니다 - 이는 메시징 시스템과 관련하여 위에서 언급한 두 가지 함수로 수행됩니다.

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (StringSubstr(szArg, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG) return;
                        szArg = StringSubstr(szArg, 9, StringLen(szArg));
                        StringToUpper(szArg);
                        if ((szArg == szMsgIDE[eBTN_SELL]) || (szArg == szMsgIDE[eBTN_BUY])) NanoEA.OrderMarket(szArg == szMsgIDE[eBTN_BUY]);
                        if (szArg == szMsgIDE[eBTN_CANCEL])
                        {
                                NanoEA.ClosePosition();
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
                        }
                        if (szArg == szMsgIDE[eCHECK_DAYTRADE]) UpdateInfos(true);
                        break;
                case CHARTEVENT_OBJECT_ENDEDIT:
                        UpdateInfos();
                        break;
        }
}

이제 다음과 같은 질문이 생길 것입니다. 그게 다야? 예, MetaTrader 5 플랫폼이 IDE와 상호 작용할 수 있도록 하는 것은 메시지 시스템입니다. 매우 간단하지만 이 기능이 없으면 IDE가 작동하지 않으며 시스템을 구축할 수도 없을 것입니다. EA에서 이 작업을 수행하는 방법이 약간 복잡해 보일 수 있지만 실제로 OOP 덕분에 EA 코드는 매우 간단합니다. 조금 까다로울 수 있는 것은 IDE에 표시될 결과를 업데이트하는 것입니다. 값은OnTick함수에서 업데이트되지만 단순화를 위해 MetaTrader 5에서 제공하는 데이터를 사용할 것이므로 함수는 다음과 같습니다. 이 부분이 가장 중요합니다 - 이 함수는 가장 많이 요청되는 함수이므로 가장 빠른 함수이기도 합니다.

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

즉, 새로운 쿼트와 메시지가 클래스로 전송되고 결과 값이 업데이트됩니다. 그러나 이 기능은 잘 최적화되어야 한다는 것을 잊지 마십시오. 그렇지 않으면 심각한 문제가 발생할 수 있습니다.


결론

때로는 불가능한 일처럼 보이지만 저는 도전하는 것을 좋아합니다. 그리고 이를 위해 원래 개발되지 않은 플랫폼 내부에서 RAD 시스템을 만드는 방법을 보여주는 이 작업은 상당히 흥미로운 작업이었습니다. 단순한 것에서 시작된 이 시스템이 감히 하기 힘든 새로운 것에 도전하는 동기가 되길 바랍니다.

곧 이 Expert Advisor에 새로운 내용을 추가할 예정이니 계속 지켜봐 주십시오!



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

파일 첨부됨 |
EA_1.04.zip (3275.47 KB)
RSI 기반의 트레이딩 시스템을 설계하는 방법 알아보기 RSI 기반의 트레이딩 시스템을 설계하는 방법 알아보기
이 기사에서는 트레이딩의 세계에서 가장 인기 있고 일반적으로 사용되는 지표 중 하나인 RSI에 대해 공유합니다. 이 글에서 여러분은 이 지표를 사용하여 거래 시스템을 설계하는 방법을 배우게 됩니다.
하나의 차트에 여러 개의 지표 넣기(파트 04): Expert Advisor 만들기 하나의 차트에 여러 개의 지표 넣기(파트 04): Expert Advisor 만들기
이전 글에서 사용자 지정 지표를 사용할 때 여러 개의 하위 창으로 지표를 만드는 방법을 설명했습니다. 이번에는 Expert Advisor에 여러 개의 창을 추가하는 방법을 알아보겠습니다.
Expert Advisor가 실패하는 이유 분석 Expert Advisor가 실패하는 이유 분석
이 기사는 왜 Expert Advisor가 특정 지역에서는 좋은 성과를 보이고 다른 지역에서는 저조한 성과를 낼 수 있는지를 이해하기 위해 통화 데이터를 분석하는 것에 대해 살펴봅니다.
CCI 기반의 트레이딩 시스템을 설계하는 방법 알아보기 CCI 기반의 트레이딩 시스템을 설계하는 방법 알아보기
거래 시스템을 설계하는 방법을 배우기 위한 시리즈의 이 기사에서는 상품 채널 지수(CCI)를 제시하고 그 세부 사항을 설명하며 이 지표를 기반으로 거래 시스템을 만드는 방법을 공유합니다.