English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
하나의 차트에 여러개의 지표 넣기(파트 03): 사용자 정의 개발

하나의 차트에 여러개의 지표 넣기(파트 03): 사용자 정의 개발

MetaTrader 5 | 27 6월 2022, 10:33
195 0
Daniel Jose
Daniel Jose

소개

의 이전 기사에서 하나의 차트에 여러 개의 지표 넣기에 대해 차트 하위 창에서 두개 이상의 지표를 사용할 수 있도록 하는 기본 코드에 대해 알아 보았습니다. 그러나 살펴본 내용은 훨씬 더 큰 시스템을 시작하기 위한 기반에 불과합니다. 이 모델을 기반으로 몇 가지 다른 작업을 수행할 수 있습니다. 그러나 이 기사의 목표 중 하나는 프로그래밍 하는 방법을 배워서 자신의 아이디어를 기반으로 자신만의 시스템을 설계할 수 있도록 하는 것이기 때문에 단계별로 진행해야 합니다. 이 기사에서는 기능을 확장할 것입니다. 시스템이 할 수 있는 기능에 만족하지만 더 많은 것을 할 수 있기를 원하는 사람들에게 흥미로운 기사일 것입니다.


계획

종종 우리가 새로운 시스템을 구현할 때 우리가 시스템을 얼마나 개선할 수 있는지에 대한 실질적인 아이디어가 없이 무작정 시작하곤 합니다. 우리는 항상 향후에 시스템을 어떻게 개선해야 하고 개선할 것이 무엇이 있는지를 염두에 두고 새로운 프로젝트를 시작해야 합니다. 이것은 처음 시작하는 사람들에게 매우 중요합니다: 끊임없이 무언가를 계획하고 향후에 무엇을 확장하고 개선해야 할지에대해 고려해야 하는 것입니다.

핵심 코드는 전혀 변경되지 않았으며 어떤 의미에서는 이미 예상된 바와 같습니다. 그러나 객체 클래스 코드는 크게 변경되었습니다. 코드의 재사용이 훨씬 더 중요하므로 훨씬 더 유연한 방식으로 새로운 기능을 구현하고 개선 사항을 만들 수 있도록 이러한 변경을 수행했습니다(이는 객체 지향 프로그래밍의 기본 아이디어 중 하나입니다. 항상 재사용하는 것입니다, 새로 생성하는 것은 필요한 경우에만 합니다). 이제 새로운 객체 클래스를 살펴보겠습니다. 이해하기 쉽도록 변경 사항을 강조 표시하겠습니다.

프라이빗 클래스 변수의 새로운 정의부터 시작하겠습니다.

struct st
{
        string  szObjName,
                szSymbol;
        int     width;
}m_Info[def_MaxTemplates];
int             m_IdSubWin,
                m_Counter,
                m_CPre,
                m_Aggregate;
long            m_Id,
                m_handle;
ENUM_TIMEFRAMES m_Period;

사용된 변수의 수가 크게 증가했다는 점에 유의하십시오. 새로운 기능을 제대로 관리하려면 더 많은 데이터가 필요하기 때문입니다. 이제 우리의 가변적인 시스템에는 구조가 있습니다. 이러한 구조는 관련 변수를 함께 그룹화하는 데 매우 좋습니다 - 이러한 구조는 데이터를 조작할 때 빠르고 쉽게 액세스할 수 있도록 합니다.

void SetBase(const string szSymbol, int iScale, int iSize)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
        if (m_IdSubWin < 0)
        {
                m_Id = ChartID();
                m_IdSubWin = (int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - 1;
                m_Aggregate = 0;
        }
        m_Info[m_Counter].szObjName = __FILE__ + (string) MathRand() + (string) ObjectsTotal(m_Id, -1, OBJ_CHART);
        ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, 0, 0);
        ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));

// ....

        macro_SetInteger(OBJPROP_PERIOD, m_Period);
        m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
        m_Aggregate += iSize;
        m_Info[m_Counter].width = iSize;
        m_CPre += (iSize > 0 ? 1 : 0);
        m_Counter++;
#undef macro_SetInteger
};


주요 변경 사항은 자산명, 객체명 및 너비를 저장하는 구조를 사용한다는 것입니다. 이제 하위 창에서 지표의 너비를 지정할 수도 있습니다. 또한 클래스의 다른 부분에서 사용할 수 있도록 메모를 작성해 보겠습니다. 다음은 가장 많이 변경된 함수입니다.

void Decode(string &szArg, int &iScale, int &iSize)
{
#define def_ScaleDefault 4
#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
                                                                
        string sz1;
        int i0, i1, c1 = StringLen(szArg);
        bool b0 = true;
        StringToUpper(szArg);
        iScale = def_ScaleDefault;
        m_Period = _Period;
        for (int c0 = 0, max = StringLen(szArg); c0 < max; c0++) switch (szArg[c0])
        {
                case ':':
                        b0 = false;
                        for (; (c0 < max) && ((szArg[c0] < '0') || (szArg[c0] > '9')); c0++);
                        iScale = (int)(szArg[c0] - '0');
                        iScale = ((iScale > 5) || (iScale < 0) ? def_ScaleDefault : iScale);
                        break;
                case ' ':
                        break;
                case '<':
                        macro_GetData('>');
                        if (sz1 == "1M") m_Period = PERIOD_M1; else

//....

                        if (sz1 == "1MES") m_Period = PERIOD_MN1;
                        break;
                case '[':
                        macro_GetData(']');
                        iSize = (int) StringToInteger(sz1);
                        break;
                default:
                        c1 = (b0 ? c0 : c1);
                        break;
        }
        szArg = StringSubstr(szArg, 0, c1 + 1);
#undef macro_GetData
#undef def_ScaleDefault
}


녹색은 코드에 추가된 사항들을 보여줍니다. 노란색은 소스 코드에 이미 존재했지만 여러 이유로 인해 이동된 행에 사용됩니다. 이제 이렇게 추가된 것들이 이 코드에서 수행하는 작업이 무엇인지 살펴볼 차례입니다. 여기서 더 중요한 점은 이러한 것들이 기능적 측면에서 원래의 시스템을 어떻게 향상시키는지를 살펴 보는 것입니다. 우리는 특정한 것들을 사용자들이 사용자 정의할 수 있도록 하는 기반을 만들고 있는 것입니다. 우리는 기존 구문에 새 규칙을 추가하여 그렇게 하려고 하는 것입니다.(아래 표 참조).

분리 기호 기능 예시  결과 
< > 사용할 그래픽의 주기를 지정합니다.  < 15m > 지표 주기를 15분으로 고정합니다. 원본 차트는 다른 주기를 사용할 수 있지만 지표는 15분 데이터로만 표시됩니다.
 [ ] 지표의 너비를 지정합니다  [ 350 ] 지표의 너비를 350픽셀로 고정합니다. 

차트의 주기를 고정하는 구분 기호 - 지표 창에서만 가능합니다. 사용자가 변경할 수 있는 사항에는 영향을 미치지 않습니다. 다른 모든 지표와 기본 차트는 사용자가 선택한 새로운 차트의 주기로 업데이트 되지만 고정 지표는 새 차트의 주기를 따르지 않습니다. 어떤 경우에는 아래 이미지와 같이 흥미로운 경우도 있습니다.

         

이를 통해 거래 화면에는 같은 자산이면서 주기는 다른 차트를 유지해야 할 때 필요한 설정을 쉽게 할수 있도록 합니다. 이제 차트 너비를 고정하는 구분 기호를 사용하면 좀 더 보기 편한 차트를 얻을 수 있습니다. 해당 구분 기호는 다른 기사에서 살펴볼 또 다른 매우 중요한 용도가 있지만 지금은 지표의 너비를 제어하기 위해 사용했습니다.

특정 규칙이 없기 때문에 모든 구분 기호를 조합하여 사용하거나 지표에 실제로 필요한 구분 기호만 사용할 수 있습니다. 유일한 규칙은 지표의 이름이 다른 어떤 것보다 먼저 배치되어야 한다는 것입니다. 코드 설명으로 돌아가 봅시다. 다음의 라인을 보십시오:

#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
           
//....                                                     
                case '<':
                        macro_GetData('>');


우리는 많은 사람들에게는 이상하게 보일 수 있는 것들을 정의하고 있습니다. 그러나 그 이름은 여기에서 도움이 될 수 있습니다:macro_GetData(A), 이것은 매크로인 코드 조각을 생성합니다. 컴파일러가 코드에서 이 정의를 찾으면 컴파일러는 선언을 매크로 코드로 바꿉니다. 이것은 여러 위치에서 특정 코드 부분을 반복하지만 하나의 선언과 다른 선언 사이에 최소한의 변경이 있는 경우에 매우 유용합니다. 이전 예에서 녹색 줄이 바뀌고 컴파일러에서 생성된 코드는 다음과 같습니다.

case '<':
	b0 = false;
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != '>'); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
//....

코드는 가능한 한 많이 재사용하고 새 코드는 가능한 한 적게 작성하는 것이 좋습니다. 이제 구문을 더 명확하게 하기 위해 변경 될 것들이 어떠한 항목들인지 살펴보겠습니다. 비록 작은 디테일이지만 개인의 스타일에 맞게 조정하면 훨씬 더 멋질 수 있습니다. 다음 라인을 살펴보십시오:

//.....

if (sz1 == "1M") m_Period = PERIOD_M1; else

//.....

강조 표시된 정보는 중요한 부분입니다. 제가 사용한 모델에 따르면 사용자가 1분의 주기를 사용하려면 다음과 같은 구문을 사용하여 이를 표시해야 합니다. MIN_1. 개성있는 스타일을 원하신다면 각자의 방식대로 지정하시면 됩니다. 그러나 문자는 공백이 없는 대문자여야 합니다. 예를 들어, 선택된 부분은 "1MIN", "MIN_1", "1_MINUTE" 또는 더 자세히 표현되는 것으로 대체될 수 있습니다. 예를 들면 다음과 같습니다. "LOCK_IN_1_MIN" 또는 귀하만의 표현으로- 단어 사이에 공백이 없는 한 작동합니다. 사실 이 공백의 제약을 없앨 수는 있지만 굳이 그럴 필요는 없는 것 같습니다. 프로그래밍을 아는 것이 얼마나 좋은 것인가요. 자신만의 스타일로 민들 수 있습니다. 제가 수정한 다음의 코드는 기본 소멸자입니다.

~C_TemplateChart() 
{
        for (char c0 = 0; c0 < m_Counter; c0++)
        {
                ObjectDelete(m_Id, m_Info[c0].szObjName);
                SymbolSelect(m_Info[c0].szSymbol, false);
        }
}

강조 표시된 줄이 다음과 같이 추가되었습니다. 자산이 별도의 창에서 열려 있지 않으면 더 이상 Market Watch에 표시되지 않습니다. 이렇게 하면 사용하지 않는 자산이 공간을 차지하고 창이 지저분해 지는 것을 방지할 수 있습니다. 이제 이전 기사에서 설명하기로 약속한 내용을 살펴보겠습니다. 이것은 원래 코드에는 없었지만 향후 코드의 일부가 될 것입니다.

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((type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
        if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
        ChartRedraw(m_handle);
}

강조 표시된 라인은 관찰된 모든 자산이 동일한 설정을 사용하는 경우 기본 설정 파일을 사용할 수 있도록 합니다. 일이 예상대로 되지 않을 때 실제로 어떻게 행동을 해야 하는지 제대로 이해하지 못한 채 일을 하는 것은 아무 소용이 없습니다. 그러나 설정이정확히 동일하다면 왜 사용하지 않습니까? 지정된 자산에 대한 설정 파일을 찾을 수 없는 경우 Profiles\Template 디렉토리에 있는DEFAULT.TPL파일에 정의된 기본 MetaTrader 5 설정을 사용하려는 것으로 간주합니다. 그러나 먼저 한 가지 중요한 사실을 이해해야 합니다. ChartApplyTemplate 함수에서 단일 디렉토리를 지정하지 않은 이유는 무엇일까요? MetaTrader 5는 특정 논리에 따라 검색을 수행하기 때문입니다. 이 논리가 어떻게 작동하는지 알면 상황을 더 흥미로운 방식으로 이해하는 데 도움이 될 수 있습니다.

다음과 같이 강조 표시된 라인을 이 라인으로 바꾸는 시나리오를 가정해 보십시오.

if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""MyTemplates\\Default.tpl");

MetaTrader 5는 먼저 사용자 지정 지표 실행 파일이 있는 디렉토리의MYTEMPLATES 하위 디렉토리에서 설정 파일을 검색합니다. 즉, 여러 지표 생성에 사용된 실행 파일이 있는 동일한 폴더에 MYTEMPLATES 폴더가 있는 경우 MetaTrader 5는 정확히 그곳에서 파일을 검색할 것입니다. 그러나 아무 것도 발견되지 않으면MQL5\Profiles\Templates\MyTemplates 디렉토리에서 동일한 파일을 검색합니다. 하지만 그 뿐만이 아닙니다. 동일한 코드에는 또 다른 세부 정보가 들어 있습니다.

if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""\\MyTemplates\\Default.tpl");

모든 것을 변경하는 한 가지 작은 세부 사항: 이제 MetaTrader 5는 먼저MQL5 MyTemplates 디렉토리에서 파일을 찾으려고 시도하고 파일을 찾지 못하면 위에서 설명한 단계를 따릅니다. MetaTrader 5가 어떻게 작동하는지 모르는 사람들은 ChartApplyTemplate 문서에서 이 정보를 찾을 수 있습니다. 그러나 이제 여러분은 검색이 수행되는 방식을 이해하였을 것이고 그러므로 여러 변형들을 만들고 파일을 저장할 위치를 알 수 있을 것입니다.

클래스 중 크게 변경이 있었던 함수는 다음과 같습니다.

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(m_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);
        }
        ChartRedraw();
#undef macro_SetInteger
}

선택한 라인은 이 함수에서 가장 중요한 계산을 보여줍니다. 이것들은 사용자가 원하는 방식으로 창을 조정하지만 고정된 크기로 설정된 창의 경우는 나머지 부분이 정의된 크기를 갖는 빈 영역을 생성합니다. 그러나 메인 창의 너비를 줄이게 되면 파란색 부분과 같이 계산한 고정 크기 창의 너비를 줄일 수 없어 문제가 발생할 수 있습니다. 그러나 고정 너비 창과 관련해서 앞으로 더 알아보게 될 다른 이점이 있으므로 지금은 그대로 두도록 합시다. 이제 클래스의 마지막 변경 사항을 살펴볼 차례입니다. 다음과 같습니다.

void AddThese(const eTypeChart type, string szArg)
{
        string szLoc;
        int i0, iSize;
//....
        Decode(szLoc, i0, iSize);
        AddTemplate(type, szLoc, i0, iSize);
//....
}

변경 사항만 강조 표시되므로 특별한 것은 없습니다.


결론

이 기사가 다른 기사와 마찬가지로 구조화된 프로그래밍이 얼마나 흥미로운지를 보여주는 글이기를 바랍니다. 편하게 약간의 조정을 해주면 시스템에 많은 기능을 추가할 수 있습니다. 코드 재사용의 경우는 완성된 코드를 더 많이 사용할수록 버그를 포함할 가능성이 적기 때문에 유지 관리가 훨씬 쉬워집니다. 다음 기사에서는 이 시스템과 관련해 많은 사람들이 더욱 흥미로워할 만한 부분들을 살펴보고 프로그래밍을 하는 것에 대한 좀 더 자세한 내용들을 알아보겠습니다. 다음에 봅시다...



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

Envelopes로 트레이딩 시스템을 설계하는 방법을 배우보세요 Envelopes로 트레이딩 시스템을 설계하는 방법을 배우보세요
이 글에서는 밴드 거래 방법 중 하나를 알려 드리겠습니다. 이번에는 Envelopes를 살펴보고 Envelopes를 기반으로 몇 가지 전략을 만드는 것이 얼마나 쉬운지 알아보겠습니다.
하나의 차트에 여러 개의 지표 넣기(파트 02): 첫 번째 실험 하나의 차트에 여러 개의 지표 넣기(파트 02): 첫 번째 실험
이전 기사 "하나의 차트에 여러 개의 지표 넣기"에서 저는 하나의 차트에 여러 개의 지표를 사용하는 방법과 관련한 개념과 기본적인 사항을 제시했습니다. 이 기사에서는 소스 코드를 제공하고 자세히 설명합니다.
Momentum 기반의 트레이딩 시스템을 설계하는 방법 알아보기 Momentum 기반의 트레이딩 시스템을 설계하는 방법 알아보기
저는 이전 글에서 추세를 파악하는 것의 중요성에 대해 언급했습니다. 추세란 곧 가격의 방향이라는 점에서 말입니다. 이 기사에서 저는 가장 중요한 개념이자 지표 중 하나인 모멘텀 지표를 여러분과 공유할 것입니다. 이 모멘텀 지표를 기반으로 트레이딩 시스템을 설계하는 방법에 대해 공유하겠습니다.
하나의 차트에 여러 개의 지표 넣기(파트 01): 개념 이해 하나의 차트에 여러 개의 지표 넣기(파트 01): 개념 이해
오늘 우리는 하나의 차트에서 여러개의 지표가 동시에 실행되면서 지표별로 각각의 영역을 차지하지 않는 차트에 지표를 추가하는 방법에 대해 알아 봅니다. 많은 트레이더들은 한 번에 여러개의 지표(예: RSI, STOCATIC, MACD, ADX 및 기타)를 모니터링하거 때로는 다른 자산을 인덱스로 만들어 모니터링할 때 거래에 대해 좀 더 자신감을 가지게 되기도 합니다.