English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터(25부): 시스템 견고성 확보(II)

Expert Advisor 개발 기초부터(25부): 시스템 견고성 확보(II)

MetaTrader 5 | 23 2월 2024, 13:17
137 0
Daniel Jose
Daniel Jose

소개

이전 기사 시스템 견고성 확보(I)에서 우리는 시스템을 더욱 안정적이고 강력하게 만들기 위해 EA의 일부를 변경하는 방법을 살펴보았습니다.

이는 이 기사에서 수행할 작업에 대한 소개였을 뿐입니다. 이제 당신이 알고, 계획하고, 바랐던 모든 것을 잊어버리십시오. 여기서 가장 어려운 점은 분리를 할 수 있다는 것입니다. 이 시리즈가 시작된 후로 EA는 거의 지속적으로 발전해 왔습니다: 우리는 몇 가지 사항을 추가하고, 변경하고, 심지어 제거하기도 했습니다. 이번에는 우리가 해왔던 일의 극단으로 가보겠습니다.

그러나 보이는 것과는 달리 큰 문제가 있습니다: 잘 설계된 EA에는 내부에 어떤 종류의 지표도 없으며 앞으로도 없을 것입니다. EA는 그저 표시된 주문 위치가 준수되는지 관찰하고 확인합니다. 완벽한 EA는 본질적으로 가격이 어떤 역할을 하는지에 대해 통찰력을 제공하는 마법사와 같은 것입니다. EA는 지표는 보지 않고 차트에 있는 포지션이나 주문만을 보는 것입니다.

여러분은 제가 말도 안 되는 소리를 하고 있고 제가 무슨 말을 하는지도 모르겠다고 생각할 수도 있습니다. 하지만 여러분은 MetaTrader 5가 왜 서로 다른 것들에 대해 서로 다른 클래스를 제공하는지 생각해 본 적이 있습니까? 왜 플랫폼에는 지표, 서비스, 스크립트 및 Expert Advisor가 하나의 블록이 아니라 각각으로 포함되어 있는 것일까요? 바로............

이것이 요점입니다. 이것들이 본래 각각의 일을 수행하는 것이라면 따로 작업하는 것이 더 좋기 때문입니다.

지표는 일반적인 목적으로 사용됩니다. 지표의 디자인을 잘 설계하여 지표의 종합적인 성과가 나빠지지 않도록 하면 좋습니다 - 저는 MetaTrader 5 플랫폼이 아닌 이외 지표가 나빠지는 것을 말하는 것입니다. 지표가 서로 다른 스레드에서 실행되기 때문에 지표는 매우 효율적으로 병렬로 작업을 수행할 수 있습니다.

서비스는 다양한 방식으로 이를 지원합니다. 예를 들어 이 시리즈의 웹에서 데이터 액세스(II)웹에서 데이터 액세스(III)라는 글에서 우리는 서비스를 사용하여 매우 흥미로운 방식으로 데이터에 액세스했습니다. 사실 이 작업은 EA에서 직접 수행할 수 있기는 하지만 이미 다른 기사에서 설명한 것처럼 가장 적합한 방법은 아닙니다.

스크립트는 특정 시간 동안만 존재하고 매우 구체적인 작업을 수행한 다음 차트에서 사라집니다. 그러므로 매우 독특한 방식으로 우리에게 도움을 줍니다. 또는 기간과 같은 차트의 일부 설정을 변경할 때까지 그대로 유지될 수도 있습니다.

이는 그 활용성을 조금 제한하지만 이는 우리가 있는 그대로 받아들여야 하는 부분입니다. 반대로 Expert Advisor는 트레이딩 시스템 작업에만 특화되어 있습니다. EA에서는 트레이딩 시스템의 일부가 아닌 함수와 코드를 추가할 수 있지만 이는 고성능이나 고신뢰성 시스템에서는 그다지 적합하지 않습니다. 그 이유는 트레이딩 시스템의 일부가 아닌 모든 것이 EA에 있으면 안 되기 때문입니다: 즉 모든 것이 올바른 위치에 배치되고 올바르게 처리되어야 합니다.

따라서 신뢰성을 높이기 위해 가장 먼저 해야 할 일은 트레이딩 시스템의 일부가 아닌 코드에서 모든 것을 완전히 제거하고 이러한 항목을 지표 등으로 바꾸는 것입니다. EA 코드에는 주문이나 포지션을 관리, 분석, 처리하는 부분만 남게 됩니다. 다른 모든 항목은 제거됩니다.

이제 시작하겠습니다.


2.0. 구현

2.0.1. EA 배경 제거

이는 EA에 해를 끼치거나 문제를 일으키지는 않지만 때로는 특정 항목만 표시되고 화면이 비어 있기를 원하는 사람들도 있습니다. 그래서 이 부분을 EA에서 없애고 지표로 바꾸겠습니다. 구현하기는 매우 쉽습니다. 어떤 클래스도 건드리지 않고 다음 코드를 생성하겠습니다:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Background image type
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

보시다시피 모든 것이 매우 자연스럽고 명확합니다. 우리는 단순히 EA에서 코드를 삭제하고 이를 차트에 추가할 수 있는 지표로 변환했습니다. 배경, 투명도 수준, 심지어 차트에서 제거하는 등의 모든 변경 사항이 EA 작업에 영향을 미치지 않습니다.

우리는 이제 실제로 EA 성능 저하를 일으키는 것들을 삭제하기 시작하겠습니다. 이는 때때로 또는 모든 가격 변동에 따라 작동하므로 때로는 EA의 속도를 늦추어 EA가 실제 작업, 즉 차트의 주문이나 포지션에 무슨 일이 일어나고 있는지 관찰하는 것을 방해할 수 있습니다.


2.0.2. 가격별 볼륨을 지표로 변환

그렇게 보이지 않을 수도 있지만 가격별 볼륨 시스템은 시간이 걸리며 이는 종종 EA에 매우 중요한 순간입니다. 가격이 특정 방향으로 움직이는 것이 아니면서 크게 변동하는 변동성이 높은 순간이 그 예입니다. 이때 EA는 작업을 완료하기 위해 사용 가능한 모든 자원을 활용할 필요가 있습니다. 어떤 다른 지표가 그 일을 대신하기로 한다면 별로 좋아 보이지는 않습니다. 그럼 아래 코드를 생성하여 EA에서 이러한 부분을 제거하고 제거한 부분을 지표로 바꿔보겠습니다.

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Bar color
input   char            user1   = 20;                                   //Transparency (from 0 to 100 )
input color     user2 = clrForestGreen; //Buying
input color     user3 = clrFireBrick;   //Selling
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

이것이 가장 쉬운 작업이었습니다. 우리는 EA에서 코드를 제거하여 지표에 넣었습니다. 코드를 다시 EA에 넣으려면 지표 코드를 복사하여 EA에 다시 넣으면 됩니다.

우리는 간단한 것부터 시작한 것입니다. 그러나 이제 상황은 좀더 복잡해 집니다 - 우리는 EA에서 Times & Trade를 제거할 예정입니다.


2.0.3. Times & Trade을 지표로 변환하기

만약 EA와 지표 모두에서 사용할 수 있는 코드를 생성하려는 경우 이는 그리 간단하지 않습니다. 서브 윈도우에서 동작하는 지표이기 때문에 지표로 변환하는 것이 쉬워 보일지 모릅니다. 하지만 정확히 말하면 그리 쉽지 않습니다. 왜냐하면 이 지표가 서브 윈도우에서 동작하기 때문입니다. 문제는 우리가 이전의 예와 같이 작업을 수행하면 지표 창에 다음과 같은 결과가 표시된다는 것입니다.

지표 창에 이러한 항목을 배치하는 것은 권장되지 않습니다. 왜냐하면 화면에서 지표를 제거하려는 경우 사용자에게 혼란을 줄 수 있기 때문입니다. 따라서 이 작업은 다른 방식으로 수행되어야 합니다. 그리고 꽤 혼란스러워 보일 수 있지만 실제로는 간단한 지시문 세트와 몇몇 편집인 이 경로의 끝에서 다음과 같은 결과를 지표 창에서 얻게 됩니다.

이것이 바로 사용자가 기대하는 것입니다 - 위의 그림에 보이는 혼란스러움이 아닙니다.

다음은 Times & Trade 지표의 전체 코드입니다:

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Scale
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

코드는 EA 코드에는 없는 강조 표시된 줄을 제외하면 EA에서 사용된 코드와 유사해 보입니다. 그렇다면 문제는 무엇일까요? 아니면 아무 문제도 없는 것일까요? 실제로 몇 가지 문제가 있습니다: 코드가 정확히 동일하지 않습니다. 지표나 EA 코드가 아니라 클래스 코드에 차이가 있습니다. 그러나 차이점을 고려하기 전에 다음 사항에 대해 생각해 봅시다: 컴파일할 항목과 컴파일하지 않을 항목을 컴파일러에 우리가 어떻게 알릴 수 있을까요? 아마도 여러분은 프로그래밍을 할 때 이것에 대해 전혀 걱정하지 않을 것입니다. 아마도 여러분은 단순히 코드를 작성하고 마음에 드는 것이 없으면 간단히 삭제할 것입니다.

숙련된 프로그래머에게는 규칙이 있습니다. 확실히 작동하지 않는 경우에만 무언가를 제거합니다. 그렇지 않으면 실제로 컴파일 되지 않은 경우에도 조각을 유지합니다. 하지만 선형 코드에서 우리는 이를 어떻게 수행해야 할까요? 작성된 함수가 항상 작동하게 하려면 말입니다. 여기 질문이 있습니다: 여러분은 컴파일러에게 컴파일할 항목과 컴파일하지 않을 항목을 알려주는 방법을 알고 있나요? 여러분의 대답이 "아니오"라면 괜찮습니다. 저는 혼자 처음 시작할 때는 이를 어떻게 해야 할지 몰랐습니다. 하지만 이 작업은 많은 도움이 됩니다. 그럼 어떻게 하는지 알아봅시다.

일부 언어에는 작성자에 따라 전처리기라고도 하는 컴파일 지시문이 있습니다. 아이디어는 동일합니다: 컴파일러에게 컴파일할 내용과 컴파일 방법을 알려주는 것입니다. 우리가 특정 항목을 테스트할 수 있게 해주는 의도적으로 코드를 격리하는 데 사용할 수 있는 매우 구체적인 유형의 지시문이 있습니다. 조건부 컴파일 지시문이라는 것이 있습니다. 이 지시문들을 바르게 사용하면 동일한 코드를 다른 방식으로 컴파일할 수 있습니다. 이것이 바로 Times & Trade 예제를 통해 수행된 작업입니다. 조건부 컴파일 생성을 담당할 것(EA 또는 지표)을 선택합니다. 이 매개변수를 정의한 후 #define지시문을 생성한 다음 조건부 지시문 #ifdef #else #endif를 사용하여 코드 컴파일 방법을 컴파일러에 알립니다.

이해하기 어려울 수도 있습니다. 어떻게 이들이 작동하는지 살펴보겠습니다.

EA 코드에서 아래에 강조 표시된 줄을 정의하고 추가합니다:

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

그러면 다음과 같은 일이 발생합니다: MQH 파일의 클래스로 EA를 컴파일하려면 Expert Advisor에 정의된 #ifdefine def_INTEGRATION_WIT_EA 지시문을 그대로 두세요. 이렇게 하면 우리가 수강하고 지표에 삽입하는 모든 클래스가 EA에 포함됩니다. 지표를 삭제하려면 코드를 삭제할 필요가 없으며 정의에 주석만 달면 됩니다. 지시문이 선언된 줄을 주석 줄로 변환하면 됩니다. 컴파일러는 지시문을 볼 수 없으며 지시문은 존재하지 않는 것으로 간주될 것입니다. 존재하지 않기 때문에 조건 지시문 #ifdef def_INTEGRATION_WITH_EA가 발견될 때마다 완전히 무시됩니다. 반면 위 예제의 #endif부분과 그 사이의 코드는 컴파일 되지 않습니다.

이것이 우리가 C_TimesAndTrade 클래스에 구현한 아이디어입니다. 새 클래스의 모습은 다음과 같습니다. 여러분의 관심을 끌기 위해 제가 한 가지 보여 드릴게 있습니다:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

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

}

컴파일 지시문을 사용하지 않는 사람에게는 코드가 이상하게 보일 수 있습니다. def_INTEGRATION_WITH_EA 지시어는 EA에서 선언됩니다. 그러면 다음 다음과 같은 일이 발생합니다. 컴파일러가 이 파일에서 객체 코드를 생성할 때 다음과 같은 관계를 가정합니다: 컴파일 중인 파일이 EA이고 선언된 지시문이 있는 경우 컴파일러는 조건부 지시문#ifdef def_INTEGRATION_WITH_EA#else사이에 있는 부분으로 객체 코드를 생성합니다. 일반적으로 이러한 경우 #else 지시문을 사용합니다. 예를 들어 def_INTEGRATION_WITH_EA 지시문이 정의되지 않은 지표와 같이 다른 파일이 컴파일되는 경우 지시문 #else #endif 사이의 모든 내용이 컴파일 됩니다. 이것이 작동하는 방식입니다.

EA 또는 지표를 컴파일할 때 이러한 각 테스트와 일반적인 작업을 이해하려면 C_TimesAndTrade 클래스의 전체 코드를 살펴보세요. MQL5 컴파일러가 모든 설정을 대신 수행하고 우리는 서로 다른 두 파일을 유지 관리해야 하는 시간과 노력을 절약할 수 있게 됩니다.


2.0.4. EA를 더욱 민첩하게 만들기

앞서 언급했듯이 EA는 주문 시스템에서만 작동해야 합니다. 지금까지 EA는 지표의 된 특징을 가지고 있었습니다. 그 이유는 EA가 수행해야 하는 계산을 보면 그렇다는 것입니다. 하지만 이 계산 방식은 수정되어 다른 방식으로 수행되게 되었습니다. 이로 인해 저는 EA가 주문을 처리하지 않고 행한 일부 작업 때문에 주문 시스템이 손상되었음을 알게 되었습니다. 가장 심각한 문제는 OnTick 이벤트에 있었습니다.

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

이제 이 이벤트는 조건부 지시문을 받았습니다 그래서 변동성이 높은 기간에 거래를 하지 않으려는 사람들은 원하는 경우에는 모든 원래의 지표가 포함된 EA를 가질 수 있게 되었습니다. 여러분은 이것이 좋은 생각이라고 생각할지도 모릅니다. 그러나 그전에 Times & Trade 업데이트 기능이 어떻게 작동하는지 여러분께 상기시켜 드리겠습니다.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

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

        }
}

위 코드는 C_TimesAndTrade 클래스에 있는 업데이트 함수의 일부입니다. 문제는 강조된 부분에 있습니다. 이 부분이 실행될 때마다 특정 시점 이후에 발생한 모든 거래 티켓을 반환하라는 요청이 서버로 전송되는데 이는 그다지 문제가 되지 않습니다. 문제는 때때로 이 호출이 다른 두 가지 이벤트와 동시에 일어난다는 것입니다.

첫 번째이자 가장 분명한 이벤트는 발생할 수 있는 많은 수의 거래로 인해 OnTick 함수가 많은 수의 호출을 수신하게 된다는 것입니다. 이 함수는 C_TimesAndTrade 클래스에 있는 위 코드를 실행해야 하는 것 외에도 C_IndicatorTradeView 클래스에 있는 SecureChannelPosition 함수를 호출하는 또 다른 문제를 처리합니다. 이것은 또 하나의 작은 문제이지만 이것이 전부는 아닙니다. 저는 이미 낮은 변동성일때에도 때때로 두 가지 이벤트가 동시에 일어난다고 이미 말했습니다. 그 중 첫 번째 이벤트가 바로 이 이벤트였습니다.

두 번째는 이미 업데이트된 OnTime 이벤트에 있으며 다음과 같습니다:

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

만약 여러분이 EA가 설계된 대로 EA를 사용하려는 경우 EA가 더 많은 코드를 수신한다는 점을 생각해보면 동시 이벤트로 인해 문제가 발생할 수 있다는 것을 알수 있을 것입니다. 이런 일이 발생하면 EA는 주문 시스템과 관련되지 않은 작업을 수행하면서 (단 1초라도) 머물게 됩니다.

C_TimesAndTrade에 있는 함수와 달리 이 함수는 C_VolumeAtPrice 클래스에 있으며 주문을 관리할 때 EA 성능에 실제로 해를 끼칠 수 있습니다. 이런 일이 발생하는 이유는 다음과 같습니다.

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

그 이유는 강조된 부분에 있지만 그 중 최악의 경우는 REDRAW입니다. 지정된 값보다 높은 볼륨을 가진 수신된 각 틱에서 가격의 전체 볼륨이 화면에서 제거되고 다시 계산되어 제자리에 설정되기 때문에 EA 성능에 큰 해를 끼칩니다. 이는 약 1초마다 발생합니다. 이것이 다른 것들과 동시에 발생할 수 있으므로 모든 지표가 EA에서 제거됩니다. 저는 여러분들이 EA에서 바로 사용하실 수 있도록 이것들을 남겨두었지만 앞서 설명했듯이 그렇게 하기는 것을 권장하지는 않습니다.

이러한 변화는 필요했습니다. 그러나 우리에게는 더 상징적이고 반드시 이루어져야 할 또 다른 일이 있습니다. 이번에는 OnTradeTransaction 이벤트와 관련된 변경 사항입니다. 이 이벤트를 사용하는 것은 시스템을 최대한 유연하게 만들기 위한 시도입니다. 주문을 실행하는 EA를 프로그래밍하는 많은 사람들은 OnTrade 이벤트를 사용하여 서버에 어떤 주문이 ​​있는지, 없는지, 어떤 포지션이 아직 열려 있는지 확인합니다. 저는 그 프로그래머들이 잘못하고 있다고 말하는 것이 아닙니다. 단지 무슨 일이 일어나고 있는지 알려주는 것이 서버라는 점이 그다지 효율적이지 않다는 것입니다. 하지만 OnTrade 이벤트의 가장 큰 문제는 우리가 불필요하게 계속해서 체크를 해야 한다는 점입니다. OnTradeTransaction 이벤트를 사용하면 변동을 분석하는 측면에서 최소한 더 효율적인 시스템을 갖게 됩니다. 그러나 이것은 여기서의 목적이 아닙니다. 누구나 자신의 기준에 가장 적합한 방법을 사용합니다.

이 EA를 개발할 때 저는 어떠한 저장 구조도 사용하지 않기로 결정했고 따라서 작업할 수 있는 주문이나 포지션의 수를 제한하지 않기로 했습니다. 그러나 이러한 저의 결정이 상황을 너무 복잡하게 만듭니다. 그래서 OnTradeTransaction 이벤트를 사용할 때 찾을 수 있는 OnTrade 이벤트의 대안이 필요합니다.

이 이벤트는 구현하기가 매우 어렵기 때문에 많은 사람들이 사용하지 않는 것 같습니다. 하지만 저에게는 선택의 여지가 없었습니다. 이 이벤트는 작동하거나 작동하지 않거나 할 것이고 그렇지 않을 경우에는 상황은 복잡해 집니다. 이전 버전에서는 이 이벤트의 코드가 매우 비효율적이었습니다. 아래에서 확인할 수 있습니다:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

위의 코드는 작동하지만 끔찍합니다. 위 코드로 생성된 쓸모없는 호출의 수는 엄청납니다. 위의 코드가 수정되지 않는다면 안정성과 신뢰성 측면에서 EA를 향상시킬 수 있는 것은 없습니다.

이 때문에 저는 메시지의 패턴을 찾기 위해 데모 계정에서 몇 가지 작업을 수행했는데 실제로는 매우 어렵습니다. 패턴을 찾지는 못했지만 저는 생성된 쓸데없는 호출의 광기를 피하고 코드를 안정적이고 신뢰할 수 있으며 동시에 시장에서 언제든지 거래할 수 있을 만큼 유연하게 만드는 방법을 찾았습니다. 물론 아직 수정해야 할 몇 가지 작은 버그가 있지만 코드는 매우 좋습니다:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

여러분은 무슨 일이 일어나고 있는지 당장 알아내려고 하지 말고 이 기능의 아름다움을 즐겨보세요. 거의 완벽합니다. 제가 이렇게 말하는 것은 제가 이 일을 했기 때문이 아니라 이 일이 갖고 있는 견고함과 민첩성 때문입니다.

복잡해 보일 수도 있지만 이 코드에는 두 가지의 체크가 있습니다. 두가지 체크 부분은 아래에 강조 표시되어 있습니다.

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... inner code ...

}

녹색으로 강조 표시된 라인은 히스토리에 거래가 발생할 때마다 해당 자산이 EA가 관찰한 자산과 동일한지를 확인합니다. 이후 만약 동일하다면 C_IndicatorTradeView 클래스는 차트에서 지표를 삭제하라는 명령을 받게 됩니다. 이는 두 가지 경우에 발생할 수 있습니다: 주문이 포지션 진입하는 경우와 포지션이 청산되는 경우 저는 HEDGING이 아닌 NETTING 모드만 사용한다는 점에 유의하세요. 따라서 무슨 일이 일어나더라도 해당 지표는 차트에서 제거됩니다.

여러분은 다음과 같이 질문할 수 있습니다: 포지션이 청산 되도 괜찮습니다. 그런데 그 주문이 포지션이 진입이 된다면 - 어떻게 되는건가요? 문제는 오류 내부가 아닌 C_IndicatorTradeView 클래스 내부에서 해결됩니다. 기사의 다음 섹션에서 이에 대해 살펴보겠습니다.

반면에 빨간색 선은 C_IndicatorTradeView 클래스로 전달된 쓸모 없는 메시지의 양을 말도 안 되게 줄입니다. 이는 서버가 요청에 대해 반환한 응답을 확인하여 수행됩니다. 그러므로 우리는 EA가 추적하는 자산과 동일한 이름으로 요청을 하여 확인을 받아야 합니다. 그런 다음에만 새로운 호출이 C_IndicatorTradeView 클래스로 전송됩니다.

이것이 제가 이 시스템에 대해 말할 수 있는 전부입니다. 하지만 이야기는 아직 끝나지 않았습니다. 우리에게는 많은 작업이 남아 있으며 지금부터는 C_IndicatorTradeView 클래스에만 집중하겠습니다. 이제 변경해야 할 몇 가지 사항부터 시작하겠습니다.


2.0.5. C_IndicatorTradeView에 의해 생성된 객체 수 줄이기

Expert Advisor 개발 기초부터(파트 23) 글에서 저는 주문 또는 스탑레벨의 이동에 대한 다소 추상적이지만 매우 흥미로운 개념을 소개했습니다. 컨셉은 포지션 고스트나 그림자를 사용하는 것이었습니다. 이는 실제 움직임이 발생할 때까지 거래 서버가 보는 것을 정의하고 차트에 표시합니다. 이 모델에는 작은 문제가 있습니다: 모델은 MetaTrader 5에서 관리될 객체를 추가하지만 추가된 객체는 대부분의 경우 필요하지 않으므로 MetaTrader 5는 종종 쓸모 없거나 거의 사용되지 않는 객체 목록을 가져옵니다.

그러나 우리는 EA가 지속적으로 객체를 생성하거나 불필요한 객체를 목록에 유지하는 것을 원하지 않습니다. 이는 EA 성능을 저하시키기 때문입니다. 우리는 MetaTrader 5를 사용하여 주문을 관리하므로 전체 시스템을 방해하는 불필요한 객체를 제거해야 합니다.

아주 간단한 해결책이 있습니다. 실제로 그렇게 간단하지는 않습니다. 이를 개선하기 위해 우리는 C_IndicatorTradeView 클래스를 좀 더 변경할 예정입니다. 우리는 화면에 고스트를 계속 등장시킬 것이며 거의 사용되지 않는 방법을 사용할 것입니다.

재미있고 흥미로울 것입니다.

먼저 선택 구조를 변경하겠습니다. 이제 다음과 같이 표시됩니다:

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

무엇이 바뀌었는지 정확히 말하지는 않겠습니다 - 여러분 스스로 이해해야 합니다. 그러나 변경 사항으로 인해 코딩의 논리가 일부 단순화되었습니다.

따라서 고스트 지표는 이제 자체 색인을 갖게 됩니다:

#define def_IndicatorGhost      2

이로 인해 이름 모델링도 변경되었습니다.

#define macroMountName(ticket, it, ev) StringFormat("%s%c%llu%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,\
                                                                        ticket, def_SeparatorInfo,              \
                                                                        (char)it, def_SeparatorInfo,            \
                                                                        (char)(ticket <= def_IndicatorGhost ? ev + 32 : ev))

작은 일인 것 같지만 곧 많은 것이 바뀔 것입니다. 계속해 나아갑시다.

이제 가격 위치 매크로는 항상 직선이며 더 이상 중복이 없으므로 코드는 이제 다음과 같습니다:

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

이러한 변경으로 인해 우리는 두 가지 다른 함수를 만들어야 했습니다. 이제 하나씩 보여드리겠습니다. 첫 번째는 지표를 생성하는 함수를 대체하는 것입니다. 이 함수는 한 지표가 다른 지표와 실제로 다른 점을 문자 그대로 명확하게 보여주었습니다. 이는 아래에서 볼 수 있습니다:

#define 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 : 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); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

여러분은 제가 코드에서 전처리 지시문을 사용하는 것을 좋아한다는 것을 눈치채셨을 것입니다. 저는 거의 항상 이렇게 합니다. 어쨋든 보시다시피 이제 지표를 구별하는 것이 매우 쉽습니다. 지표에 원하는 색상을 부여하려면 이 코드를 변경하세요. 그것들은 모두 거의 동일하므로 매크로를 사용하여 모두 동일하게 작동하고 동일한 요소를 갖도록 만들 수 있습니다. 이는 궁극적으로 코드의 재사용입니다.

이 함수와 매우 유사한 이름을 가진 또 다른 함수가 있습니다. 그러나 그 함수는 뭔가 다른 일을 하며 이에 대해서는 마지막에 자세히 이야기하겠습니다.

IndicatorAdd 함수가 변경되었습니다. 일부 조각을 삭제했습니다.

inline void IndicatorAdd(ulong ticket)
                        {
                                char ret;
                                
                                if (ticket == def_IndicatorTicket0) ret = -1; else
                                {
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if ((ret = GetInfosTradeServer(ticket)) == 0) return;
                                }
                                switch (ret)
                                {
                                        case  1:
                                                CreateIndicatorTrade(ticket, IT_RESULT);
                                                PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                                                break;
                                        case -1:
                                                CreateIndicatorTrade(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                                                break;
                                }
                                ChartRedraw();
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

제거된 조각 중 하나가 강조 표시된 조각으로 대체됩니다. 펜딩 주문과 0 지표가 더 이상 생성되지 않는다는 뜻일까요? 여전히 생성되지만 다른 위치에 있습니다. 그래서 또 다른 함수가 추가되었습니다.

그래서 여기에 — 펜딩 주문 지표와 지표 0을 생성하는 함수가 있습니다. UpdateIndicators의 코드는 다음과 같습니다:

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

이 함수에는 코드에서 강조 표시된 매우 흥미로운 검사가 있습니다. 이는 고스트 지표를 생성하는 데 도움이 되므로 IndicatorAdd 함수는 더 이상 펜딩 주문 지표 및 지표 0을 생성할 수 없습니다. 하지만 이 검사만으로는 고스트 지표를 만드는 데 충분하지 않습니다.

DispatchMessage 함수에는 이제 몇 가지 세부 정보가 포함되어 있습니다. 이는 작은 변경 사항이지만 우리의 삶을 훨씬 쉽게 만들어줍니다. 변경된 부분을 보여드리겠습니다:

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

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Code ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Code ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Code ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

CHARTEVENT_MOUSE_MOVE에 수정된 부분이 있습니다. 이 코드는 우리가 고스트를 작업하고 있는지 확인합니다. 우리가 작업하는 것이 고스트라면 조각이 차단됩니다. 그러나 그렇지 않은 경우에는 이동이 가능합니다(지표 자체가 이동할 수 있는 경우).

여러분이 지표의 새 위치를 클릭하자마자 모든 구성 요소와 함께 고스트가 객체 목록에서 제거됩니다. 저는 이것이 명확해야 한다고 생각합니다: 이제 강조 표시된 지점에 주목하세요 - 이는 CreateGhostndicator 함수의 호출입니다. 우리는 다음 섹션에서 이 코드에 대해 알아볼 것입니다.


2.0.6. CreateGhostIndicator 작동 방식

CreateGhostIndicator는 이상한 함수인 것 같습니다. 아래 코드를 살펴보겠습니다:

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

이 함수에서는 아무것도 생성되지 않는다는 점이 매우 흥미롭습니다. 어쨋든 EA가 컴파일 되고 실행되면 서버에 주문 상태를 표시하는 고스트가 생성됩니다. 이를 이해하려면 다음 비디오를 시청하십시오. 이는 시스템이 실제로 어떻게 작동하는지 보여주는 데모입니다.



고스트 지표는 실제로 차트에 생성됩니다. 그런데 실제로는 어떻게 이런 일이 발생하는 것일까요? 우리는 코드에 실제로 지표를 생성하지 않았습니다. 그런데 어떻게 우리는 지표를 생성할 수 있었을까요?

이것이 고스트입니다. 여러분은 지표가 실제로 생성되는 것을 볼 수는 없습니다. 그럼에도 불구하고 여러분이 다음과 같은 줄을 찾으려고 코드를 읽는다면 그것은 의미가 없습니다. "여기... 찾아보니... 고스트 지표가 이 시점에서 생성되는군요..." 사실은 그것들이 이미 차트에 있었지만 우리가 조작할 때까지 아무데도 표시되지 않다가 그 이후에만 표시된다는 것입니다. 이것이 어떻게 가능할까요?

이를 이해하기 위해 EA 실행 스레드를 살펴보겠습니다.

EA 초기화 후에 우리는 다음과 같은 실행 스레드가 표시되는 것을 볼 수 있습니다:

스레드 1

init_ea<<<< 시스템 초기화 스레드

주황색 영역은 EA의 일부이고 녹색 영역은 C_IndicatorTradeView 클래스의 일부입니다. 지표가 생성되어 화면에 표시되기 전에 어떤 일이 발생하는지 확인하세요. 검은색 화살표는 펜딩 주문 및 오픈 포지션에 대한 일반적인 경로입니다. 파란색 화살표는 포지션의 경로이고 보라색 화살표는 펜딩 주문이 지표를 생성하는 데 걸리는 경로를 나타냅니다. 물론 함수 내부에는 스레드를 어떤 방식으로든 지시하는 것들이 있지만 여기의 다이어그램은 일반적인 용어를 사용해서 모든 것이 어떻게 작동하는지 보여주기 위한 것입니다.

이전의 구성표는 시스템 시작 중에 한 번만 사용됩니다. 이제 차트에 펜딩 주문을 넣을 때마다 두 개의 서로 다른 실행 스레드가 있게 됩니다. 첫 번째 스레드는 지표 0을 생성하고 차트에 주문을 넣는 역할을 담당합니다. 이는 아래 그림에 나와 있습니다.

스레드 2

     <<<< 지표 0 초기화 스레드

이것이 실제로 차트에 나타나는 순서를 생성하는 클래스는 아니라는 점에 유의하세요. 그렇게 하려고 노력할 뿐입니다. 모든 것이 순조롭게 진행되면 SetPriceSelection 함수가 성공적으로 실행되고 새로운 스레드가 생성되어 차트에 주문이 표시됩니다. 따라서 우리는 다음과 같은 스레드를 얻게 될 것입니다. 실제로 거래 서버가 보고를 하는 곳에 주문이 이루어지므로 원래 지정한 곳에서 주문이 실제로 끝날 때까지 기다릴 필요가 없습니다. 변동성으로 인해 서버가 우리가 지정한 것과 다른 지점에서 주문을 이행하게 되는 경우 EA는 이를 수정하고 올바른 위치에 주문을 제시합니다. 따라서 여러분은 조건이 여러분의 트레이딩 모델에 적합한지 여부만 분석하면 됩니다.

스레드 3

     <<< 펜딩 주문 접수 스레드

이는 차트에 주문을 넣는 역할을 담당하는 부분일 뿐입니다. 여기서 저는 전체 주문에 대해 이야기하고 있습니다. 즉 진입지점, 이익 실현 및 손절매가 있는 주문입니다. 그러나 이익실현이나 손절매 등 리밋 주문 중 하나가 주문에서 제거되면 스레드는 어떻게 될까요? 이들 스레드들은 이에 응답하지 않습니다. 실제로 스레드는 여기에 있는 스레드와 상당히 다르지만 요소는 거의 동일합니다. 만약 여러분이 리밋 주문 중 하나를 청산하기 위해 버튼을 클릭하면 어떤 흐름이 나타나는지 아래에서 살펴보겠습니다.

이상하게 보일 수도 있습니다.

스레드 4

     <<< 주문 또는 스탑레벨 삭제

우리는 2개의 스레드를 가지고 있는데 나란히 있습니다. 보라색 화살표로 표시된 것이 먼저 실행됩니다. 실행되자마자 OnTradeTransaction 이벤트는 서버의 응답을 캡처하고 시스템을 트리거 하여 화면에서 지표를 제거합니다. 스탑 주문의 삭제와 포지션 또는 주문 청산 사이에는 단 하나의 차이점이 있습니다: 이 경우 SetPriceSelection 함수는 실행되지 않지만 OnTradeTransaction 이벤트 흐름은 유지됩니다.

이 모든 것이 훌륭하지만 여전히 고스트가 어떻게 나타나는지에 대해 알려주는 것이 없습니다.

고스트가 어떻게 생성되는지 이해하려면 우리는 실행 스레드가 어떻게 발생하는지를 알아야 합니다. 즉 EA가 펜딩 주문을 배치하는 방법 또는 지표 0의 생성이 실제로 어떻게 발생하는지를 알아야 합니다. 이 흐름은 위의 그림에 나와 있습니다. 여러분이 실행 스레드를 이해한다면 고스트를 더 쉽게 이해할 수 있습니다.

마지막으로 고스트가 어떻게 생성되는지 살펴보겠습니다. CreateGhostIndicator함수를 다시 한번 살펴보세요. 이 함수는 아무것도 생성하지 않고 단순히 일부 데이터를 조작합니다. 왜 그럴까요? 우리가 객체를 만들려고 하면 기존 객체 위에 오버레이 되고 그 위에 렌더링 되기 때문입니다. 따라서 필요한 객체가 숨겨지게 될 것입니다. 이 문제에 대한 두 가지 해결책이 있습니다. 첫 번째는 다른 모든 것보다 열등한 세트를 만드는 것입니다. 이 세트는 주문을 나타내는 다른 객체보다 먼저 생성됩니다. 하지만 이 해결책에는 문제가 있습니다. 우리는 쓸모없는 객체를 많이 갖게 될 것입니다. 우리는 이를 피하기 위해 전체 코드를 변경하고 있습니다. 두 번째 해결 방법은 고스트를 만든 다음 우리가 조작 중인 포인터를 삭제하고 다시 만드는 것입니다. 이러한 솔루션 중 어느 것도 그다지 실용적이지 않습니다.

저는 문서를 연구하는 동안 내 관심을 끄는 정보를 발견했습니다:ObjectSetString함수를 사용하면 언뜻 이해되지 않는 객체 속성인 OBJPROP_NAME을 조작할 수 있습니다. 저는 왜 이것이 허용되는지 궁금했습니다. 이해가 되지 않는 방법이었습니다. 객체가 이미 생성되었는데 굳이 이름을 변경하는 이유가 무엇일까요?

이것이 중요한 부분이었습니다. 객체의 이름을 바꾸면 이전 객체는 더 이상 존재하지 않고 새 이름을 갖게 됩니다. 이름을 변경한 후에는 객체가 원본 객체를 대신하므로 EA에서는 문제 없이 원본 객체를 생성할 수 있으며 그래픽에 영향을 미치지 않으면서 흔적을 남기지 않고 고스트가 나타나고 사라질 수 있습니다. 제거해야 할 유일한 객체는 지표 닫기 버튼입니다. 이 작업은 다음 줄에서 수행됩니다:

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

여기에는 작은 세부 사항이 있습니다. ObjectSetString 함수에 대한 문서를 보면 해당 작업에 대한 경고가 표시됩니다:

그래픽 객체의 이름을 바꾸면 두 개의 이벤트가 동시에 생성됩니다. 이러한 이벤트는 EA 또는 지표를 사용하여 처리할 수 있습니다. OnChartEvent()함수:

  • 이전의 이름으로 된 객체를 삭제하는 이벤트
  • 새로운 이름으로 객체를 생성하는 이벤트

이름을 바꾸려는 객체가 준비되지 않은 경우에 우리는 이 객체가 표시되는 것을 원하지 않기 때문에 이를 고려해 보는 것이 중요합니다. 따라서 이름 변경 전후에 한 가지를 더 추가합니다:

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Secure code...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

코드 내부의 어떤 항목도 객체 생성 및 삭제 이벤트를 트리거 하지 않습니다. 이제 고스트가 나타날 완전한 코드가 생겼으며 올바른 동작을 하게 됩니다.

코드가 어떻게 단순히 지표의 이름을 변경해서 실제로 고스트를 생성하는 것인지 그 방법은 아직 명확하지 않습니다. 저는 여러분에게 이를 맡길 것입니다. 여러분에게 조금이라도 도움을 드리고자 고스트 실행 스레드가 어떻게 생겼는지 보여드리겠습니다. 이는 아래 이미지에 나와 있습니다:

스레드 5

    <<<< 고스트 스레드

이것은 스레드 2의 거의 완벽한 복제이므로 여러분이 실제로 생성 코드를 작성하지 않고도 고스트가 어떻게 생성되고 파괴되는지 재미있게 알 수 있을 것입니다.


결론

저자로서 저는 이 기사가 매우 흥미로웠습니다. 우리는 EA 코드를 많이 변경해야 했습니다. 그러나 이 모든 것은 더 나은 것을 위한 작업입니다. EA를 더욱 안정적으로 만들기 위해서는 아직 수행해야 할 몇 가지 사항과 단계가 있습니다. 그러나 이미 구현된 변경 사항만으로도 전체 시스템에 큰 도움이 됩니다. 잘 설계된 프로그램은 일반적으로 여기에 구현된 특정 단계를 거친다는 점을 강조하고 싶습니다. 문서 리뷰, 실행 스레드 분석, 중요한 순간에 과부하가 발생하는지 확인하기 위해 시스템 벤치마킹하기, 그리고 무엇보다도 여러분이 차분해야 합니다. 그렇지 않으면 코드는 엉망이 됩니다. 우리의 코드를 엉망으로 만드는 것을 피하는 것이 매우 중요합니다. 왜냐하면 그렇지 않으면 코드가 더 좋아지는 것이 아니라 향후 개선, 특히 수정이 더 어려워질 뿐입니다.

이 시리즈를 팔로우 하는 모든 분들께 따뜻한 마음을 전합니다. 우리에게는 할 일이 더 남아 있습니다. 다음 기사에서 뵙기를 바랍니다.



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

클래스에서 ONNX 모델 래핑하기 클래스에서 ONNX 모델 래핑하기
객체 지향 프로그래밍을 사용하면 읽기 쉽고 수정하기 쉬운 보다 간결한 코드를 작성할 수 있습니다. 여기서는 세개의 ONNX 모델에 대한 예제를 살펴보겠습니다.
회귀 메트릭을 사용하여 ONNX 모델 평가하기 회귀 메트릭을 사용하여 ONNX 모델 평가하기
회귀는 레이블이 지정되지 않은 예제에서 실제의 값을 예측하는 작업입니다. 회귀 메트릭은 회귀 모델 예측의 정확도를 평가하는 데 사용됩니다.
모집단 최적화 알고리즘: 침입성 잡초 최적화(IWO) 모집단 최적화 알고리즘: 침입성 잡초 최적화(IWO)
다양한 조건에서 살아남는 잡초의 놀라운 능력은 강력한 최적화 알고리즘을 만들기 위한 아이디어가 되었습니다. IWO는 앞서 검토한 알고리즘 중 가장 우수한 알고리즘 중 하나입니다.
MQL5에서 ONNX 모델을 앙상블하는 방법의 예시 MQL5에서 ONNX 모델을 앙상블하는 방법의 예시
ONNX(Open Neural Network eXchange)는 신경망을 위해 만들어진 개방형 형식입니다. 이 글에서는 하나의 Expert Advisor에서 두 개의 ONNX 모델을 동시에 사용하는 방법을 소개하겠습니다.