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

Daniel Jose | 5 10월, 2022

소개

이전 파트에서는 플로팅 창 내에서 템플릿을 사용할 수 있는 기본 시스템을 만들었습니다. 많은 변경을 가했지만 코드는 아직 완성되지 않았습니다. 왜냐하면 설명을 단순하게 하기 위해 의도적으로 한 것입니다. 플로팅 창에서 템플릿을 사용하는 것은 간단하지만 객체를 사용하는 것은 훨씬 더 복잡하기 때문입니다. 따라서 이제 완전히 새로운 직업을 할 준비를 하십시오.

사실 플로팅 창에서 차트 트레이드(CHART TRADE) 인터페이스를 생성하기 위해 객체를 사용할때 가장 큰 어려움은 MetaTrader 5가 실제로는 이러한 목적을 위해 만들어진 것은 아니라는 것입니다. 일부 독자는 표준 라이브러리를 사용하여 차트 트레이드 창을 만들 수 있다고 말할지도 모릅니다. 하지만 저는 복잡한 것을 좋아하고 모든 사람들이 이전의 기사에서 다룬 내용과 같은 방식으로 자신만의 인터페이스를 만들 수 있도록 하고 싶습니다. 그러나 그 기사에서 다룬 것은 간단했습니다. 이제 우리는 MetaTrader 5의 제한 사항을 이해해야 합니다.


계획

기본부터 시작하겠습니다. 다음 코드는 예상대로 작동합니다:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID();
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand();
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+


효과가 정확히 예상대로였기 때문에 여기에 복잡한 것은 없습니다. 그러나 MQL5를 사용하면 시스템이 원래 설계된 것 이상으로 무언가를 하려고 할 때 어려움이 발생할 수 있지만 그 이상으로 조금 더 나아갈 수 있습니다. 따라서 위의 코드를 아래와 같이 변경하면 상황이 흥미로워집니다.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

강조 표시된 줄을 코드에 추가했습니다. 차트에서 실행하면 결과는 다음과 같습니다.


무슨 일이 일어난 것일까요? 차트에 차트를 배치한 것입니다. 여기에 모든 객체를 배치할 수 있습니다. MQL5에서 허용합니다. 그러나 장점과 단점이 모두 있습니다. 이 단계의 장점을 이해하려면 다음과 같은 코드의 변경 사항을 살펴보십시오.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

강조 표시된 라인을 추가하면 다음과 같은 결과가 생성됩니다:


이것은 객체 내부에 있는 모든 것이 객체 내부에 남아 있다는 것을 의미합니다. 이것은 제어 논리를 크게 단순화하기 때문에 플로팅 창을 사용할 때 필요합니다. 그러나 모든 것이 완벽하지는 않습니다. MetaTrader 5는 원래 이를 위해 설계되지 않았습니다. 따라서 한 객체가 다른 객체 안에 있을 때 문제가 발생합니다. - 내부 객체에 이벤트를 보낼 수 없습니다. 이를 이해하기 위해 코드에 몇 가지 변경 사항을 더 구현해 보겠습니다. 최종 코드는 다음과 같을 것입니다:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true);
        ChartRedraw(handle);    
  
        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)
{
        if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam);
}
//+------------------------------------------------------------------+

다음은 플랫폼에서 실행되는 코드의 결과입니다:


내부 객체를 클릭하면 실제로는 외부 객체에 클릭이 이루어지며 이 부분에서 상황이 복잡해집니다. 그러나 프로그래머는 항상 문제 해결의 전문가가 되기 위해 노력합니다: 원하는 결과를 얻으려면 문제를 해결해야 합니다. 플로팅 윈도우에서 차트 트레이드를 생성하고 기능을 하면서도 각각의 외관을 갖도록 시스템을 구성해야 합니다.

이 계획 부분의 과정에는 마지막 단계가 하나 있습니다. 이 부분은 최신 컴퓨터에서는 그렇게 심각하지 않지만 우리는 처리 시간의 최적화라는 점을 여전히 고려해야 합니다. 문제는 정보를 처리하는 데 걸리는 시간이 아니라 프로세서가 수행해야 하는 작업의 수와 관련이 있습니다. 제안된 플로팅 윈도우 시스템은 여러분의 행동에 반응하여 움직일 수 있어야 하는 4개의 객체를 포함합니다. 따라서 뷰포트에 배치된 모든 정보는 창 자체의 수정에 영향을 받습니다. 어쨋든 차트 트레이드는 객체의 수를 증가시킵니다. 그리고 비록 해당하는 계산 비용이 드는 것은 아니지만 코드가 불편해지고 최적화되지 않은 것처럼 보입니다. 이 문제는 제어 시스템을 추가하면 간단히 해결됩니다. 그러나 더 우아한 제안이 있습니다. 시간과 노력이 더 많이 드는 것처럼 보이지만 이 제안을 따르면 유지 관리하고 조작해야 하는 객체의 수가 줄어듭니다.


구현

먼저 코드의 재사용을 지원하기 위해 플로팅 창의 생성을 여러 단계로 분할합니다. 다음으로 C_ChartFloating 객체 클래스에 두 개의 새로운 함수를 만듭니다:

//+------------------------------------------------------------------+
bool StageLocal01(string sz0, 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;
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
	m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
                                
        return true;
}
//+------------------------------------------------------------------+
void StageLocal02(int x, int y, int w, int h)
{
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);                            
        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++;
}
//+------------------------------------------------------------------+

플로팅 창을 추가하는 새로운 코드는 다음과 같습니다:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
	if (!StageLocal01(sz0, TimeFrame, Scale)) return false;
        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);
        StageLocal02(x, y, w, h);

        return true;
}

이것은 기존의 시스템에 영향을 미치지 않지만 더 좋게 사용하도록 해 줍니다. 강조 표시된 줄에 주의하십시오. 이제 IDE를 사용하는 함수를 만들 차례입니다. 시작은 아래와 같습니다:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        StageLocal02(x, y, w, h);
        return true;
}

강조 표시된 줄은 이전 코드에서 사용한 것과 동일한 줄입니다. 즉 우리는 코드를 재사용하고 있으며 변경이 필요한 부분만 다릅니다. 이제 IDE를 관리할 도구가 있다는 사실을 시스템에 알릴 수 있습니다. 이는 C_TemplateChart 객체 클래스를 수정하여 수행합니다. 아래 코드는 함수에서 변경될 사항을 명확히 보여줍니다. 필요한 모든 지원이 이미 올바르게 작동하고 있기 때문에 이제부터 IDE를 사용하여 플로팅 창을 구현하는 데 집중할 수 있습니다.

void AddTemplate(void)
{

// .... Function code....

        if (h == 0)
        {
                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)
        {
                if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); 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);
                }
        }
}

코드가 얼마나 유연하게 구성되는지 살펴봅시다. 이렇게 하면 시스템이 프랑켄슈타인으로 변하는 것을 방지할 수 있습니다. 코드를 수정할 때 항상 이것을 염두에 두십시오. 그러면 코드를 처음부터 다시 작성하고 동일한 것을 여러 번 확인할 필요가 없습니다. 항상 한 번만 확인하기 위해 노력하십시오. 그러면 새로운 테스트를 수행하기 전에 가능한 한 많은 것들을 사용하고 탐색할 수 있습니다. 따라서 시스템은 좋은 전제와 함께 성장하는 반면 코드는 시간이 지남에 따라 지속 가능하고 확장 가능합니다.

지금 시스템을 실행하면 차트에 무언가가 표시됩니다. 그러나 우리는 적어도 우리가 이전에 작성한 인터페이스를 보여주기 위해 이 차트가 필요한 것입니다. 따라서 우리는 코드를 추가로 변경해야 합니다. 이제 다음과 같은 코드가 있습니다:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}

앱 실행 결과는 다음과 같습니다:


템플릿에 있는 객체에 액세스할 수 있다면 매우 훌륭하고 아름다울 것입니다(템플릿은 위의 코드에서 강조 표시된 줄에 로드 됩니다). 그러나 그렇게 하는 것은 불가능합니다. 그리고 여기 중요한 세부 사항이 있습니다: 앞에서 고려한 방식으로 객체를 만드는 대신 우리는 조작을 해야 하는 객체만을 만듭니다! 이렇게 하면 창을 이동 할 때 처리 시간을 많이 절약할 수 있습니다. 아직 다른 문제들이 있지만 여기서는 먼저 처리 문제를 해결하고 시스템이 작동하도록 합시다. 사실 이 부분은 이미 완료되었으며 작동하려면 약간의 조정만 필요합니다.

먼저 클래스 간의 상속 순서를 변경하는 것으로 시작하겠습니다. 다중 상속이 없기 때문에 이 작업을 수행해야 합니다. 새로운 구조는 다음과 같습니다:


그러나 이 변경은 문제가 되지 않습니다: 상속 시퀀스의 변경은 코드를 전혀 변경하지 않지만 코드가 거의 사용 가능하게 준비될 수 있도록 합니다. 변경된 부분은 아래의 코드에서 강조 표시됩니다.

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
	if ((w <= 0) || (h <= 0)) return false;
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}
void AddTemplate(void)
{
// ..... Código ....
        if (h == 0)
        {
                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(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h));
                m_Info[m_Counter - 1].szVLine = "";
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); 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);
                }
        }
}
bool Create(bool bFloat)
{
        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 = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
        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);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, 0, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}

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++);
                        if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);

//... The rest of the function...

}

이것은 이상하게 보일 수 있습니다. 그러나 복잡한 것은 없습니다. 플로팅 창을 사용하지 않을 때 시스템은 이미 IDE에서 실행 중인 이벤트를 처리할 수 있음을 기억하십시오. 하지만 플로팅 창 때문에 모든 것을 다시 만들어야 합니다. 그러나 처음부터 이 작업을 수행할 필요는 없습니다. 올바른 위치에 IDE를 추가하기 위해 기존의 코드를 수정할 것입니다. 이벤트를 수신하는 객체만 추가하면 됩니다. 이러한 변경 작업을 해 보면 모든 요소를 생성해야 하는지 아니면 일부만 생성해야 하는지 이해하는데 도움이 됩니다.

이러한 변경 후에 차트에 IDE 정보가 표시됩니다. 그러나 IDE 객체는 객체가 플로팅 창과 연결되어 있지 않기 때문에 정말 엉망으로 보입니다. 이제 이 문제를 해결해야 합니다.

먼저 차트에서 플로팅 창이 있는 지점을 가져와야 합니다. 이는 창을 나타내는 객체의 포인트만 알면 됩니다. 그러나 객체 지향 프로그래밍을 고수할 것이기 때문에 일부 변경이 필요합니다. 하지만 결과는 아래 코드와 같습니다.

//+------------------------------------------------------------------+
struct IDE_Struct
{
        int     X,
                Y,
                Index;
        bool    IsMaximized;
};
//+------------------------------------------------------------------+
class C_ChartFloating
{

// ... Class code ...

//+------------------------------------------------------------------+
                void SetPosition(const int X, const int Y, const int c0)
                        {

// ... Function code ...

                                if (c0 == m_IDEStruct.Index)
                                {
                                        m_IDEStruct.X = m_Win[c0].PosX + 3;
                                        m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3;
                                        m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized;
                                }
                        }
//+------------------------------------------------------------------+

// ... Class code ...

//+------------------------------------------------------------------+
inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; }
//+------------------------------------------------------------------+
                bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
                        {
                                if ((w <= 0) || (h <= 0)) return false;
                                if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
                                ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
                                m_IDEStruct.Index = m_MaxCounter;
                                StageLocal02(x, y, w, h);
                                return true;
                        }
//+------------------------------------------------------------------+

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

솔루션은 하나의 창인 CHART TRADER에 초점을 맞추기 때문에 이 시점 이후에는 상황을 더 복잡하게 볼 필요가 없습니다. 최소한 이제는 창에서 객체를 올바르게 배치할 수 있습니다. 이는 다음 코드에서 수행됩니다.

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX);
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY);
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

위의 함수는 객체의 초기 위치가 올바르게 되도록 하며 그 결과는 아래에서 볼 수 있습니다:


올바른 초기 위치 지정 외에도 객체는 이미 이벤트에 반응합니다. 그러나 시스템에는 여전히 수정해야 할 버그가 있습니다. 변경하려는 첫 번째 사항은 다음과 같습니다:


보시다시피 창은 최소화되었지만 객체는 화면에 남아 있습니다. 이것은 객체가 창 영역의 안쪽에 있지 않기 때문입니다(이유는 앞에서 설명했습니다). 계속 진행하기 전에 이 문제를 해결해야 합니다. 다음과 같이 간단하게 코드를 수정하여 해결할 수 있습니다:

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth()));
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight()));
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

결과는 다음과 같습니다:

이제 이동과 관련한 문제를 해결해 보겠습니다. 이는 아래 코드를 사용하여 해결할 수 있습니다

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        static double AccumulatedRoof = 0.0;
        bool            b0;
        double  d0;
        static int px = -1, py = -1;
                                
        C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam);
        if (m_CountObject < eEDIT_STOP) return;
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py))
                        {
                                px = GetIDE_Struct().X;
                                py = GetIDE_Struct().Y;
                                Resize(-1);
                        }
                        break;

//... The rest of the function ...

최종 결과는 다음과 같습니다:

 


결론

프로그래밍이란 것이 얼마나 훌륭한 것인지 보십시오. 우리는 문제가 있는 상태에서 시작한 이후 코드를 크게 변경하지 않고서도 하나씩 문제를 해결하고 결국 가능한 한 적은 양의 코드로 작동하는 코드를 갖게 됩니다.


중요! 차트 트레이드 배경에 이상한 정보가 표시되면 clrNONE의 색상이 있는 객체를 불투명하게 만드는 업데이트 3228 때문입니다. 첨부된 IDE 파일을 사용하면 문제가 해결됩니다.