English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터 (파트 8): 개념적 도약

Expert Advisor 개발 기초부터 (파트 8): 개념적 도약

MetaTrader 5 | 6 9월 2022, 09:52
292 0
Daniel Jose
Daniel Jose

소개

때로는 어떤 프로젝트를 개발할 때 유용하면서도 우리가 만들고 있는 시스템에 큰 개선 사항을 제공할 수 있는 이상적이고 새로운 기능을 찾을 수 있습니다. 그러나 질문이 생깁니다: 새로운 기능을 구현하는 가장 쉬운 방법은 무엇일까요?

그러나 문제는 때때로 우리가 이미 개발한 모든 것을 잊어버리고 처음부터 시작해야 한다는 것입니다. 이것은 우리의 의욕을 상당히 떨어뜨립니다. 20년이 넘는 C++ 프로그래밍을 통해 저는 특정한 사고 방식을 발전시켰습니다. 우리는 계획을 세우고 변경 작업은 최소한으로 하면서 일을 해 나아가지만 때로는 상황이 변경되어 처음 예상했던 것보다 훨씬 더 복잡해질 수 있습니다.

지금까지 우리는 현재의 기능을 잃지 않고 새 코드를 사용할 수 있는 방식으로 EA를 만들어 왔습니다: 우리는 단순히 클래스를 만들고 추가한 것입니다. 이제 우리는 한 발 물러서서 두 발짝 앞으로 나아가야 합니다. 이 단계를 통해 새로운 기능을 도입할 수 있습니다. 이 기능은 템플릿을 기반으로 하는 일부 정보가 있는 창과 관련한 클래스입니다; 첫 번째 부분이 될 것입니다. 우리는 현재 있는 모든 기능을 유지하면서 코드를 근본적으로 변경할 것이며 두 번째 부분에서는 IDE를 다룰 것입니다.


계획

우리의 Expert Advisor는 현재 객체 클래스로 구성되어 있습니다. 아래 다이어그램에서 확인할 수 있습니다.

시스템은 현재 잘 작동하고 있으며 매우 안정적입니다. 하지만 이제 아래와 같이 EA를 재구성 할 것입니다. C_TemplateChart와 C_SubWindow의 위치가 변경되고 추가 클래스도 있다는 것을 알 수 있었을 겁니다.


이렇게 구조를 바꾼 목적은 무엇일까요? 플로팅 창을 구현한 방식이 자산 데이터를 포함한 창에 적합하지 않아 이러한 변경이 필요한 것입니다. 하지만 이 변경은 구조적인 측면에서 좀 더 보기 좋을 뿐만 아니라 코드의 극단적인 변경이 필요하기 때문에 이전 코드와는 매우 다를 것입니다.

이제 작업을 시작하겠습니다.


실제 구현

1. Expert Advisor 내부 코드 변경

첫 번째 큰 변화는 EA 초기화 파일에서 시작됩니다. 아래 코드를 참조하세요.

input group "Window Indicators"
input string                    user01 = "";                    //Subwindow indicators
input group "WallPaper"
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Chart background type
input group "Chart Trader"
input int                       user20   = 1;                   //Leverage factor
input int                       user21   = 100;                 //Take Profit (financial)
input int                       user22   = 75;                  //Stop Loss (financial)
input color                     user23   = clrBlue;             //Price line color
input color                     user24   = clrForestGreen;      //Take Profit line color
input color                     user25   = clrFireBrick;        //Stop line color
input bool                      user26   = true;                //Day Trade?
input group "Volume At Price"
input color                     user30  = clrBlack;             //Bar color
input char                      user31  = 20;                   //Transparency (from 0 to 100 )
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26);
        VolumeAtPrice.Init(user24, user25, user30, user31);
        
   OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+


이제 로드할 템플릿을 나타내는 변수가 하나만 있다는 점에 유의하십시오. 강조 표시된 부분 이외의 나머지 코드는 이전과 그대로인 것 같습니다. 여기에서 수행하는 작업 또는 강조 표시된 코드가 EA 초기화 부분에 배치된 이유가 명확하지 않을 수 있습니다. EA를 차트에 로드 하면 몇 가지 항목이 생성되고 일반적인 사용시 수정될 수 있습니다. 이전에는 강조 표시된 코드를 추가하는 것은 의미가 없었습니다. 왜냐하면 모든 것이 함께 작동하도록 의도되었으며 변경 사항이 EA의 동작이나 모양에 영향을 미치지 않았기 때문입니다. 그러나 플로팅 지표를 추가하면 짜증나는 일이 발생합니다. 시간 프레임을 변경할 때마다 EA가 다시 시작되고 창이 원래 상태로 돌아갑니다. 이것들을 제거하지 않으면 차트에 쓸모없는 것들이 쌓이고 잘못 제거하면 원래 있던 자리에 다시 만들어지는 것도 큰 불편입니다. 사용자가 필요한 템플릿을 변경하지 않으면 강조 표시된 코드는 플로팅 창이 잘못 제거되는 것을 방지하지만 템플릿에 변경 사항이 있는 경우 EA가 정상적으로 다시 시작됩니다. 이것은 매우 간단하면서도 매우 효율적입니다.

다음으로 주의해야 할 사항은 내부 메시징 시스템과 관련이 있습니다. 이전에는 추가 변수가 있었지만 이제는 제거했으므로 코드는 다음과 같습니다.

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


시스템은 이제 MQL5 메시지 교환 시스템을 보다 효율적으로 사용하는 반면 메시지 전송은 OnChartEvent 함수와 매우 유사합니다. 이를 통해 객체 클래스에 스트레스를 주지 않고 매개변수를 전달할 수 있으므로 각 클래스는 MetaTrader 5 시스템에서 생성된 이벤트 메시지를 가장 좋은 방식으로 처리할 수 있습니다. 이러한 방식으로 우리는 각 객체 클래스를 더 격리하므로 EA는 각 유형의 사용자에 대해 더 다양한 형태를 취할 수 있습니다.


2. 하위 창 지원 코드 변경

여기까지는 하위창 코드가 매우 간단했지만 문제가 있었습니다: 이런저런 이유로 EA가 생성된 하위창을 삭제할 수 없었다는 것이었습니다. Expert Advisor가 다시 열리면 새로운 하위 창이 더 생성되어 시스템을 제어하는 것이 불가능해졌습니다. 이는 의외로 쉽게 고칠 수 있었습니다. 먼저 아래에 표시된 지원 파일의 조각을 살펴보십시오.

int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "SubWinSupport");
        
        return INIT_SUCCEEDED;
}

강조 표시된 줄은 지원 파일의 별칭을 만듭니다. EA는 이 별칭 EA를 보고 하위 창 시스템이 로드되었는지 여부를 확인합니다. EA는 별칭만 확인하므로 파일 이름에 관계없이 수행됩니다. 동일한 코드 유형은 나중에 EA의 다른 항목을 지원하는 데 사용됩니다. 여기서 너무 자세히 설명하지는 않겠지만 강조 표시된 코드를 활용하는 방법에 대해 서는 나중에 다른 기사에서 다루도록 하겠습니다.

이제 이 작업이 완료되었으므로 하위 창을 로드하고 생성하는 코드를 살펴보겠습니다. 이 코드는 다음과 같이 표시됩니다.

void Init(void)
{
        int i0;
        if ((i0 = ChartWindowFind(Terminal.Get_ID(), def_Indicador)) == -1)
                ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource));
        m_IdSubWinEA = i0;
}


보시다시피 훨씬 간단하지만 이 코드는 퍼블릭이 아니며 다른 퍼블릭 코드를 통해 액세스됩니다:

inline int GetIdSubWinEA(void)
{
        if (m_IdSubWinEA < 0) Init();
        return m_IdSubWinEA;
}


그런데 왜 이렇게 구현했을까요? EA가 하위창의 어떠한 지표도 사용하지 않는 현상이 발생할 수 있으며 시스템이 이를 인지하면 차트에서 하위창을 제거하고 필요한 경우에만 생성합니다. 그러나 이러한 결정은 EA 코드가 아니라 C_TemplateChart 클래스에 의해 결정됩니다.


3. 새 클래스 C_TemplateChart

다음 움짤을 살펴보십시오:

이제 분석하는 위치를 나타내는 수직선이 생겼습니다. 이러한 선은 서로 독립적입니다. 이전에는 이들을 사용할 수 없었기 때문에 차트에 따라 지표의 일부 포인트를 분석하기가 어려웠습니다. 이것은 C_TemplateChart 클래스에 포함된 개선 사항 중 하나입니다. 추가 변경 사항이 상당하므로 클래스 내부의 코드를 살펴보겠습니다.

변수가 선언된 다음 코드를 살펴보겠습니다:

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates                8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+
                struct st
                {
                        string          szObjName,
                                        szSymbol,
                                        szTemplate,
                                        szVLine;
                        int             width,
                                        scale;
                        ENUM_TIMEFRAMES timeframe;
                        long            handle;
                }m_Info[def_MaxTemplates];
                int     m_Counter,
                        m_CPre,
                        m_Aggregate;
                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


주의해야 할 첫 번째 사항은 C_TemplateChart 클래스가 C_SubWindow 클래스를 확장한다는 것입니다. 코드의 이 부분은 특별해 보이지 않지만 강조 표시된 부분을 잘 살펴 보십시오: 강조 표시된 부분은 사용자가 요청한 지표를 적절하게 생성하고 표시할 수 있는 내부 데이터 분석 시스템을 가리킵니다. 이제 사용자가 항목을 표시하는 방법을 설명하는 시스템이 표준화되기 시작했습니다. 혼란스러워 보일지라도 시간이 지나면 분명해집니다. 새로운 형식을 설명하려면 사용자의 요청을 분석하는 역할을 하는 다음의 부분을 분석해야 합니다.

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}


먼저 이전에 구조에 있던 모든 데이터를 지웁니다. 그런 다음 매개변수가 있는 경우 매개변수별로 분석과 수신을 시작합니다. 필수 사항은 아니지만 사용자에게 어떻게 표시되는지 나타냅니다. 이 시스템은 자체적으로 확장됩니다. 따라서 더 많은 정보를 추가하려면 eParameter열거형에서 지정하기만 하면 됩니다. 현재 시스템에는 5개의 매개변수가 있으며 eParameters열거형에 표시된 것과 동일한 순서로 지정해야 합니다. 이 열거형은 변수 선언 부분에서 강조 표시됩니다. 올바른 순서의 매개변수와 설명은 아래에 나와 있습니다.

Parameter Result
1. TEMPLATE or ASSET 보려는 템플릿 또는 자산을 지정합니다.
2. PERIOD 지정된 경우 이전에 사용된 것처럼 지표를 지정된 고정 기간으로 설정합니다.
3. SCALE 지정하면 지표가 고정 스케일에 바인딩됩니다.
4. WIDTH 지정된 경우 매개변수는 지표가 창에 표시되는 너비를 설정합니다.
5. HEIGHT 이 새 매개변수는 이 문서의 뒷부분에서 설명합니다 - 이는 플로팅 창의 사용을 나타냅니다.

현재로서는 매개변수 5의 혜택을 받지 못하는 유일한 구조는 IDE이지만 이것은 수정될 것이며 다음 기사에서 IDE를 유용하게 사용하는 방법을 보여 드리겠습니다. 이 기사는 시스템의 다른 부분에 초점을 맞출 것입니다.

사용자가 지표의 수직선의 색상을 제어할 수 있게 하고 싶다고 가정합니다. 매개변수 분석 코드를 변경할 필요는 없습니다. 다음과 같이 변경하기만 하면 됩니다.

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates        8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, COLOR_VLINE, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+

// ... Internal code ....

                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


시스템은 한 번의 호출에 6개의 매개변수가 있을 수 있다는 것을 자동으로 인식합니다. 이제 또 다른 문제가 발생했습니다. 위의 GetCommand 코드는 잘 작동하지만 버그가 있습니다. 종종 시스템을 만들 때 우리는 이 버그를 알아차리지 못합니다. 그러나 다른 사람들이 시스템을 사용하게 되면 오류가 분명해지고 경험이 부족한 프로그래머 중 일부는 문제를 해결하는 방법에 대해 확신하지 못할 수도 있습니다. 이것이 객체 지향 프로그래밍이 매우 높은 가치를 지니는 이유입니다 - 가장 관심 있는 프로그램에서 사용하기에 가장 적합한 모델을 생성할 수 있게 합니다. OOP의 전제 조건 중 하나는 데이터 및 클래스의 변수가 올바르게 초기화되었는지 확인하는 것입니다. 이를 보장하기 위해서는 모든 것을 테스트하는 것이 중요합니다. GetCommand 코드는 맞는 것처럼 보이지만 버그가 포함되어 있습니다 - 이 코드는 최대 매개변수의 제한을 확인하지 않고 잇습니다. 원래 모델이 5개의 매개변수만 허용하는 경우 사용자가 6개의 매개변수를 설정하면 어떻게 될까요? 이것은 피해야 할 사항입니다 모든 것이 작동할 것이라고 가정하지 말고 모든 것이 작동할 것이라고 보장해야 합니다. 따라서 다음과 같이 코드를 수정해야 합니다(수정 사항이 강조 표시됩니다).

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        if (m_Params.counter == HEIGHT) return StringLen(szArg) + 1;
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}

한 줄만 추가해서 시스템이 예기치 않은 결과를 생성하는 것을 방지할 것입니다. 예상되는 마지막 매개변수가 HEIGHT이지만 마지막 매개변수가 아닌 경우 이는 논리적으로 무언가 잘못되었음을 의미하므로 나중에 선언된 모든 것을 무시합니다. 이렇게 문제를 피할 수 있습니다.

만약 여러분 중에서 시스템이 패턴과 매개변수를 인식하는 방법을 이해하지 못하는 분이 계시는 경우 구문은 다음과 같습니다.

Parameter_00(Parameter_01, Parameter_02, Parameter_03, Parameter_04)

여기서 Parameter_00은 사용할 템플릿을 지정하고 나머지는 쉼표( , )로 구분하고eParameter열거형에 정의된 값을 나타냅니다. Parameter_03만 변경하려는 경우 아래 그림과 같이 나머지는 비워 둘 수 있습니다. 이 그림은 시스템이 사용자가 원하는 방식으로 작동함을 보여줍니다.

       

함수 호출과 매우 유사한 표준화된 지표가 있지만 이는 혼란스러워 보일 수 있습니다. 실제로 일어난 일은 다음과 같습니다: RSI 템플릿을 지정한 다음 기간이나 스케일을 지정하지 않습니다. 이 값은 시스템이 기본 차트를 따르도록 공백으로 남겨둡니다. 그러나 우리는 너비와 높이를 지정해야 하고 시스템은 이것이 플로팅 창에 표시되어야 함을 알고 있으므로 RSI 지표가 플로팅 창에 나타납니다. ADX 템플릿에서는 시스템이 하위 창에 정의된 너비를 표시하게 하도록 너비만 표시합니다. Stoch 지표는 나머지 하위 창 전체를 사용하여 ADX와 공간을 공유합니다. 사용자가 무언가를 변경하려는 경우 어렵지 않을 것입니다. ADX의 높이를 지정할 때 어떻게 되는지 확인해 보십시오.

시스템은 ADX가 표시되는 방식을 즉시 변경합니다. 전체 하위 창은 Stoch에 남겨두고 플로팅 창에 배치합니다. 모든 플로팅 창은 서로 완전히 독립적입니다. 하지만 라인은 보이는 것 이상입니다. 다음 사진을 참조하십시오.

하위 창은 더 이상 필요하지 않으므로 삭제되었습니다. 그러나 이 모든 것을 가능하게 하는 함수는 무엇일까요? 아래에 나와 있습니다 - 몇 가지만 수정하면 많은 흥미로운 작업을 수행할 수 있습니다.

void AddTemplate(void)
{
        ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
        string sz0 = m_Params.Param[PERIOD];
        int w, h, i;
        bool bIsSymbol;

        if (sz0 == "1M") timeframe = PERIOD_M1; else
        if (sz0 == "2M") timeframe = PERIOD_M2; else
        if (sz0 == "3M") timeframe = PERIOD_M3; else
        if (sz0 == "4M") timeframe = PERIOD_M4; else
        if (sz0 == "5M") timeframe = PERIOD_M5; else
        if (sz0 == "6M") timeframe = PERIOD_M6; else
        if (sz0 == "10M") timeframe = PERIOD_M10; else
        if (sz0 == "12M") timeframe = PERIOD_M12; else
        if (sz0 == "15M") timeframe = PERIOD_M15; else
        if (sz0 == "20M") timeframe = PERIOD_M20; else
        if (sz0 == "30M") timeframe = PERIOD_M30; else
        if (sz0 == "1H") timeframe = PERIOD_H1; else
        if (sz0 == "2H") timeframe = PERIOD_H2; else
        if (sz0 == "3H") timeframe = PERIOD_H3; else
        if (sz0 == "4H") timeframe = PERIOD_H4; else
        if (sz0 == "6H") timeframe = PERIOD_H6; else
        if (sz0 == "8H") timeframe = PERIOD_H8; else
        if (sz0 == "12H") timeframe = PERIOD_H12; else
        if (sz0 == "1D") timeframe = PERIOD_D1; else
        if (sz0 == "1S") timeframe = PERIOD_W1; else
        if (sz0 == "1MES") timeframe = PERIOD_MN1;
        if ((m_Counter >= def_MaxTemplates) || (m_Params.Param[TEMPLATE] == "")) return;
        bIsSymbol = SymbolSelect(m_Params.Param[TEMPLATE], true);
        w = (m_Params.Param[WIDTH] != "" ? (int)StringToInteger(m_Params.Param[WIDTH]) : 0);
        h = (m_Params.Param[HEIGHT] != "" ? (int)StringToInteger(m_Params.Param[HEIGHT]) : 0);
        i = (m_Params.Param[SCALE] != "" ? (int)StringToInteger(m_Params.Param[SCALE]) : -1);
        i = (i > 5 || i < 0 ? -1 : i);
        if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
                if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
                ChartRedraw(m_Info[m_Counter - 1].handle);
        }
}

강조 표시된 부분은 데이터가 화면에 표시되는 방식을 선택하는 것을 보여줍니다. 코드는 기존의 코드와 크게 다르지 않습니다. 그러나 이러한 테스트는 시스템이 사용자가 원하는 방식으로 작동할 것임을 보장합니다. 지금까지는 이 모든 것이 새 모델에서 코드를 재구성 하는 것을 필요로 하지 않았지만 하위 창의 크기를 조정하는 함수를 보면 그렇지 않습니다.

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0, x1, y;
        if (!ExistSubWin()) return;
        x0 = 0;
        y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA()));
        x1 = (int)((Terminal.GetWidth() - 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
}

강조 표시된 라인은 시스템이 전에 만든 시스템을 기반으로 만들어지는 것을 방지합니다. 이 줄은 EA에서 열고 유지하고 관리하는 하위 창이 있는지 확인하고 없으면 반환하고 함수는 다른 작업을 수행하지 않습니다. 하지만 그러한 하위창이 존재한다면 그 안에 있는 모든 것은 필요에 따라 크기를 조정해야 하며 이 테스트로 인해 시스템은 완전히 개편되었습니다.

다음은 수정된 또 다른 함수입니다.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")
                        {
                                ObjectMove(m_Info[c0].handle, m_Info[c0].szVLine, 0, dt, 0);
                                ChartRedraw(m_Info[c0].handle);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Resize();
                        for (int c0 = 0; c0 < m_Counter; c0++)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_PERIOD, m_Info[c0].timeframe);
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_CHART_SCALE,(m_Info[c0].scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Info[c0].scale));
                        }
                        break;
        }
}


강조 표시된 부분은 특별한 주의를 기울여야 합니다. 이 부분이 무엇을 할까요? 이 코드는 템플릿의 어느 위치에 세로줄을 표시합니다. 나머지 코드는 차트가 변경됨에 따라 템플릿을 유지하고 조정합니다.

이를 OnChartEvent 시스템 내부의 EA가 아닌 객체 클래스에서 수행하면 많은 이점이 있습니다. 가장 중요한 것은 각 클래스가 MetaTrader 5가 EA로 보내는 이벤트를 처리할 수 있다는 것입니다. 모든 것을 하나의 단일 기능으로 중앙 집중화 하는 대신 각 클래스가 작업을 수행하도록 하고 EA에서 클래스를 사용하지 않으려면 제거하기만 하면 나머지 코드에 대해 아무런 영향을 주지 않습니다.

프로그래밍이 아름답지 않은가요? 저는 프로그래밍을 사랑합니다...

이 기사의 다음 요점으로 넘어가기 전에 매개변수 1과 2에 사용할 수 있는 값에 대해 간략하게 설명하겠습니다. 매개변수 1에는 다음과 같은 값을 할당할 수 있습니다. 1M, 2M, 3M, 4M, 5M, 6M, 10M, 12M, 15M, 20M, 30M, 1H, 2H, 3H, 4H, 6H, 8H, 12H, 1D, 1S, 1개월 이 값은 무작위가 아닙니다. 이들을 ENUM_TIMEFRAME 열거형에서 가져오고 일반 차트를 사용하는 경우 수행할 작업을 정확히 반복합니다. 매개변수 2는 0에서 5까지의 값을 가질 수 있습니다. 여기서 0은 가장 먼 스케일이고 5는 가장 가까운 스케일입니다. 자세한 내용은CHART_SCALE를 참조하십시오.


3.4 플로팅 창 지원

이제 플로팅 창을 만들고 유지하고 관리하는 방법에 대해 알아 보겠습니다. 이것을 이해하지 않고는 시스템을 실제로 활용하는 것이 불가능하기 때문입니다. 이를 담당하는 객체 클래스는 C_ChartFloating입니다. 다음과 같이 생각할 수 있습니다: 왜 표준 MQL5 라이브러리의Control클래스를 사용하지 않는걸까요? 이유는 간단합니다. 제어 클래스를 사용하면 시스템에 있는 운영 체제와 매우 유사한 기능을 가진 창을 만들고 유지 관리할 수 있지만 목적에 비해 너무 큰 작업입니다. 우리는 훨씬 더 간단한 것이 필요합니다. 제어 클래스를 사용하여 원하는 작업을 수행하는 것은 파리를 죽이기 위해 바주카포를 사용하는 것과 같습니다. 그래서 우리는 C_ChartFloating 클래스를 사용합니다. 이 클래스에는 플로팅 창을 지원하는 데 필요한 최소한의 요소가 포함되어 있으며 우리는 이를 제어할 수 있습니다.

클래스 자체는 많은 설명이 필요하지 않습니다. 왜냐하면 우리가 하는 유일한 일은 4개의 그래픽 객체를 생성하는 것이기 때문입니다. 그러나 내부 함수 중 특별히 주목해야 할 두 가지가 있습니다. 창을 만드는 함수부터 시작해 보겠습니다:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
        m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        m_Win[m_MaxCounter].PosX = -1;
        m_Win[m_MaxCounter].PosY = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
        return true;
}

이 코드는CHART 객체에 템플릿을 적용할 수 있도록 필요한 모든 지원을 만듭니다. 강조 표시된 코드 부분에서 구현되며 이 함수를 호출하는 데 실제로 필요한 유일한 매개변수는 템플릿 이름입니다. 다른 모든 값은 미리 초기화되지만 필요한 값을 지정하는 데 방해가 되는 것은 없습니다. 생성된 각각의 새 창에 대해 다음 창은 기본적으로 다른 창과 겹치지 않도록 약간 오프셋됩니다. 이것은 다음 줄에 표시됩니다:

y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);


이 클래스에서 또다른 흥미로운 함수는 메시지를 처리합니다. 코드는 다음과 같습니다:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        if ((((int)sparam) & 1) == 1)
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)
                                        sic = (((mx > m_Win[c0].PosX) && (mx < (m_Win[c0].PosX + m_Win[c0].Width)) && (my > m_Win[c0].PosY) && (my < (m_Win[c0].PosY + def_SizeBarCaption))) ? c0 : -1);
                                if (sic >= 0)
                                {
                                        if (six < 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                        six = (six < 0 ? mx - m_Win[sic].PosX : six);
                                        siy = (siy < 0 ? my - m_Win[sic].PosY : siy);
                                        SetPosition(mx - six, my - siy, sic);
                                }
                        }else
                        {
                                if (six > 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                six = siy = sic = -1;
                        }
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                                ObjectMove(m_Win[c0].handle, m_Win[c0].szVLine, 0, dt, 0);
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        for (int c0 = 0; c0 < m_MaxCounter; c0++) if (sparam == m_Win[c0].szBtnMaxMin)
                        {
                                SwapMaxMin((bool)ObjectGetInteger(Terminal.Get_ID(), m_Win[c0].szBtnMaxMin, OBJPROP_STATE), c0);
                                break;
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        for(int c0 = 0; c0 < m_MaxCounter; c0++)
                        {
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_PERIOD, m_Win[c0].TimeFrame);
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_CHART_SCALE,(m_Win[c0].Scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Win[c0].Scale));
                        }
                        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
                        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
                        break;
        }
        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                ChartRedraw(m_Win[c0].handle);
}


이 함수는 C_ChartFloating 클래스가 지원하는 모든 이벤트 처리를 구현합니다. 얼마나 많은 창이 존재하든 이 함수는 모두 같은 방식으로 처리할 것입니다. Expert Advisor의 OnChartEvent 함수 내에서 이 작업을 수행하게 되면 함수가 매우 복잡하고 안정적이지 않을 것입니다. 이 기능을 여기서 구현하여 객체 클래스에서 코드의 무결성을 보장합니다. 따라서 플로팅 창을 사용할 필요가 없을 경우 클래스와 액세스할 수 있는 지점에서 파일을 제거하기만 하면 됩니다. 이러한 구현을 통해 코드를 훨씬 빠르고 쉽게 만들 수 있습니다.

위의 코드에도 흥미로운 부분이 있습니다. 그 부분은 강조 표시되며 내부 코드는 다음과 같습니다:

void SwapMaxMin(const bool IsMax, const int c0)
{
        m_Win[c0].IsMaximized = IsMax;
        SetDimension((m_Win[c0].IsMaximized ? m_Win[c0].MaxWidth : 100), (m_Win[c0].IsMaximized ? m_Win[c0].MaxHeight : 0), false, c0);
        SetPosition((m_Win[c0].IsMaximized ? m_Win[c0].PosX_Maximized : m_Win[c0].PosX_Minimized), (m_Win[c0].IsMaximized ? m_Win[c0].PosY_Maximized : m_Win[c0].PosY_Minimized), c0);
}


위의 코드는 무엇을 할까요? 너무 혼란스러워 보이죠? 이해를 돕기 위해 아래 애니메이션을 자세히 살펴보겠습니다.


플로팅 윈도우가 생성되면 초기 앵커 포인트가 있습니다. 이는 클래스 자체의 위치 지정 시스템이나 프로그래머에 의해 지정됩니다. 이 앵커 포인트는 최대화된 창과 최소화된 창 모두에서 동일합니다. 이 값은 고정되어 있지 않습니다. 즉 사용자가 이 점을 쉽게 변경할 수 있습니다.

빈 차트에 특정한 위치를 원할 경우 최대화된 창을 쉽고 빠르게 읽을 수 있는 위치로 이동한 다음 동일한 창을 최소화하고 다른 위치(예: 화면 모서리)로 이동할 수 있습니다. 시스템은 이를 기억하고 창을 최대화하면 최소화하기 전의 마지막 앵커 포인트로 점프합니다. 창을 최소화한 반대 상황에서도 마찬가지입니다.


결론

우선 여기까지 입니다. 다음 기사에서는 이 기능을 IDE 지원 클래스로 확장할 것입니다.


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

파일 첨부됨 |
EA.zip (3616.46 KB)
파라볼릭 SAR 기반의 트레이딩 시스템을 설계하는 방법 알아보기 파라볼릭 SAR 기반의 트레이딩 시스템을 설계하는 방법 알아보기
이 기사에서는 가장 인기 있는 지표를 사용하여 거래 시스템을 설계하는 방법에 대한 시리즈를 계속할 것입니다. 이 기사에서는 파라볼릭 SAR 지표에 대해 자세히 알아보고 몇 가지 간단한 전략을 사용하여 MetaTrader 5에서 사용할 거래 시스템을 설계하는 방법을 배웁니다.
ADX 기반의 트레이딩 시스템을 설계하는 방법 알아보기 ADX 기반의 트레이딩 시스템을 설계하는 방법 알아보기
이 기사에서는 간단한 거래 시스템을 설계하는 방법을 배우는 시리즈의 연속으로 새로운 기술 도구를 배울 것입니다. 이번에는 또 다른 인기 있는 기술 지표에 대해 알아볼 것입니다: 평균 실제 범위(ATR).
Expert Advisor 개발 기초부터 (파트 9): 개념적 도약(II) Expert Advisor 개발 기초부터 (파트 9): 개념적 도약(II)
이 기사에서는 차트 트레이드를 플로팅 창에 배치합니다. 이전 파트에서는 플로팅 창 내에서 템플릿을 사용할 수 있는 기본 시스템을 만들었습니다.
Expert Advisor 개발(파트 7): 가격에 볼륨 추가 (I) Expert Advisor 개발(파트 7): 가격에 볼륨 추가 (I)
이것은 현존하는 가장 강력한 지표 중 하나입니다. 어느 정도의 확신을 갖고 거래를 하고자 하는 사람은 차트에 반드시 이 지표가 있어야 합니다. 많은 경우 이 지표는 거래하는 동안 "테이프 읽기"를 선호하는 사람들이 사용합니다. 또한 이 지표는 거래 중 가격의 움직임만을 사용하는 사람들이 활용할 수 있습니다.