Expert Advisor 개발 기초부터(20부): 새로운 주문 시스템 (III)
소개
이전 글인 Expert Advisor 개발 기초부터(19부)에서는 새로운 주문 시스템을 작동하기 위해 구현한 코드의 변경에 초점을 맞췄습니다. 이러한 변경 사항이 구현된 이후에는 이제 실제 문제에 100% 집중할 수 있습니다. 이는 틱 값이나 X를 얻기 위해 어디에 주문을 넣어야 하는지, Y를 잃지 않기 위해 어디에 손절매를 설정해야 하는지를 몰라도 100% 시각적으로 이해할 수 있는 주문 시스템을 구현하기 위해서입니다.
이러한 시스템을 만들려면 MetaTrader 5 플랫폼이 실제로 어떻게 작동하고 어떤 자원을 제공하는지 이해해야 할 뿐만 아니라 MQL5를 잘 다룰 수 있어야 합니다.
1.0. 계획
1.0.1. 지표 설계
여기서 하고자 하는 것은 단순한 아이디어를 소개하는 것이 아니라 실제로 이 글에서 시스템을 구현하는 방법을 보여드리는 것입니다. 우리는 아래 이미지와 비슷한 것을 만들 것입니다:
설명이 없어도 매우 쉽게 이해할 수 있을 것입니다 닫기 버튼, 값, 포인트가 있어 드래그 하여 주문하기 쉽습니다. 하지만 그게 다가 아닙니다. 스탑로스가 스탑게인으로 바뀌면 시스템은 다음과 같이 처리합니다:
따라서 특정 포지션을 언제, 얼마나, 어디서, 보유할 가치가 있는지 여부를 쉽게 알 수 있습니다.
위의 그림은 OCO 주문의 대상 또는 OCO 포지션의 한도만 보여 주지만 시가와 관련된 부분도 중요합니다.
펜딩 주문의 경우 다음과 같이 표시됩니다:
포지션에 따라 약간 다르게 보입니다:
그러나 그 비율은 그다지 고무적이지 않습니다... 하지만 이것이 우리가 구현할 아이디어입니다. 색상은 여기에 표시된 색상을 사용하겠습니다. 여러분은 여러분이 원하는 색상을 선택할 수 있습니다.
계속 진행해 나아가면 각 지표에 기본적으로 5개의 객체가 있다는 것을 알 수 있습니다. 즉 MetaTrader 5는 각 지표에 대해 동시에 5개의 객체를 처리해야 합니다. OCO 주문의 경우 MetaTrader 5는 하나의 주문 또는 포지션당 15개의 객체를 처리해야 하며 이는 OCO 포지션도 마찬가지입니다. 즉 OCO 주문이 펜딩 상태로 4개이고 진입 된 OCO 포지션이 1개인 경우 MetaTrader 5는 차트에 있는 다른 객체를 제외하고 25개의 객체를 처리해야 합니다. 이는 플랫폼에서 하나의 에셋만 사용하는 경우에 해당합니다.
이 부분은 거래 상품을 주문하기 위해 필요한 메모리와 처리량을 아는 것이 중요하기 때문에 설명하는 것입니다. 이것은 현대의 컴퓨터에서는 문제가 되지 않지만 필요한 하드웨어가 어떠 한지 알아야 합니다. 이전에는 주문의 각 지점에 대해 화면에 하나의 객체만 표시되었습니다. 이제 각 점에는 5개의 개체가 있으며 이들은 어떻게든 연결된 상태를 유지해야 합니다. 이 연결은 플랫폼에 의해 이루어질 것입니다. 우리는 연결의 방법과 각 객체가 트리거될 때 어떤 일이 일어나야 하는지만 지시할 것입니다.
1.0.2. 객체 선택
그 다음 문제는 우리가 사용할 객체를 선택하는 것입니다. 이는 간단한 질문처럼 보일 수 있지만 이에 대한 답이 실제로 구현이 어떻게 진행될지를 결정하기 때문에 매우 중요한 질문입니다. 첫 번째 선택은 화면에서 객체가 배치되는 방식에 따라 결정됩니다.
이를 구현하는 방법에는 두 가지가 있습니다. 첫 번째는 시간 및 가격 좌표로 포지셔닝을, 두 번째는 데카르트 X 및 Y 좌표를 사용하는 것인데, MetaTrader 5는 이 두 가지를 모두 지원합니다.
그 중 하나를 자세히 알아보기 전에 시간과 가격 좌표를 사용하는 모델은 즉시 폐기하겠습니다. 그 모델은 언뜻 보기에는 이상적이지만 서로 연결되어 있고 함께 유지되어야 하는 수많은 객체를 다룰 때는 유용하지 않습니다. 우리는 데카르트 시스템을 사용해야 합니다.
우리는 이전의 글 중 하나에서 이미 이 시스템을 살펴보고 객체를 선택하는 방법에 대해 논의한 바 있습니다. 자세한 내용은 하나의 차트에 여러개의 지표 넣기(파트 05)를 참조하세요.
계획을 수립했으니 이제 코딩으로 넘어가서 실제로 여러가지를 구현할 차례입니다.
2.0. 구현
저는 여기서 단순히 시스템을 구현하는 것이 아니라 그 안에서 정확히 무슨 일이 일어나고 있는지 설명하고 이 시스템을 기반으로 여러분들이 자신 만의 시스템을 만들 수도 있도록 세부 사항을 조금씩 제공할 것입니다. 이렇게 하면 시스템이 어떻게 생성되는지 여러분들이 이해하는 데 도움이 됩니다. 펜딩 주문과 관련된 모든 기능은 시스템이 동일한 원칙을 따르고 공통 코드를 가지고 있기 때문에 포지션에서도 작동한다는 점을 잊지 마세요.
2.0.1. 인터페이스 프레임워크 만들기
이 첫 번째 단계의 결과는 아래에서 확인할 수 있습니다. 제가 이 코드를 개발하고 여러분과 공유하기로 하면서 참으로 기뻤습니다. 여러분들도 그러 하시기를 바랍니다. 프로그래밍을 배우고 싶거나 이 주제에 대해 더 깊은 지식을 쌓고 싶은 분들에게 동기부여가 되길 바랍니다.
위의 이미지를 보면 지금까지 생성한 코드를 모두 제외하고 일반적인 방식으로 기능을 생성했다고 생각하실지도 모르겠습니다. 하지만 그렇지 않습니다. 지금까지 만든 것을 그대로 사용할 것입니다.
따라서 이전 글에서 살펴본 코드를 사용하고 거기에 몇 가지 변경 사항을 적용하겠습니다. 이제 코드에서 어떤 것이 새로운 것인지 살펴보겠습니다. 먼저 세 가지 새로운 클래스를 추가합니다.
2.0.1.1. C_Object_Base 클래스
먼저 새 클래스인 C_Object_Base를 생성합니다. 이 클래스는 저희 시스템에서 가장 낮은 클래스입니다. 클래스의 첫 번째 코드는 아래와 같습니다:
class C_Object_Base { public : //+------------------------------------------------------------------+ void Create(string szObjectName, ENUM_OBJECT typeObj) { ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, 0, 0, 0); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, false); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n"); PositionAxleY(szObjectName, 9999); }; // ... The rest of the class code
우리에게는 우리의 삶을 훨씬 더 쉽게 만들어줄 보통의 코드가 있다는 점에 유의하세요. 같은 클래스에는 표준 X 및 Y 포지셔닝 코드가 있습니다.
void PositionAxleX(string szObjectName, int X) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X); }; //+------------------------------------------------------------------+ virtual void PositionAxleY(string szObjectName, int Y) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y); };
Y 포지셔닝 코드는 특정 객체에 종속적이지만 객체에 특정 코드가 없더라도 클래스에서 일반적인 코드를 제공합니다. 아래와 같이 객체의 색상을 지정하는 일반적인 방법이 있습니다.
virtual void SetColor(string szObjectName, color cor) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); }
객체의 차원을 정의하는 방법은 다음과 같습니다.
void Size(string szObjectName, int Width, int Height) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height); };
우선 C_Object_Base 클래스에 대해 여기까지 설명하고 나중에 다시 살펴보겠습니다.
2.0.1.2. C_Object_BackGround 클래스
이제 두 개의 그래픽 객체를 지원하기 위해 다른 두 개의 클래스를 만들어 보겠습니다. 그 중 첫 번째는 C_Object_BackGround로 다른 요소를 받을 배경 상자를 만듭니다. 코드는 매우 간단합니다. 아래에서 전체 내용을 확인할 수 있습니다:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Base.mqh" //+------------------------------------------------------------------+ class C_Object_BackGround : public C_Object_Base { public: //+------------------------------------------------------------------+ void Create(string szObjectName, color cor) { C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrNONE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor); } //+------------------------------------------------------------------+ virtual void PositionAxleY(string szObjectName, int Y) { int desl = (int)(ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE) / 2); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desl); } //+------------------------------------------------------------------+ };
최소한의 코드로 객체를 어셈블하 기 위해 상속을 사용하고 있다는 점에 유의하세요. 따라서 클래스가 필요에 따라 스스로 수정하고 모델링하도록 하여 나중에 이러한 조정을 할 필요가 없도록 합니다. 이는 강조 표시된 코드에서 확인할 수 있는데 클래스는 Y축의 값만 알면 자동으로 올바른 위치에 배치되며 크기를 확인하고 우리가 전달하려는 축의 가운데에 위치하도록 스스로를 배치합니다.
2.0.1.3. C_Object_TradeLine 클래스
C_Object_TradeLine 클래스는 이전에 주문 가격 선의 위치를 표시하는 데 사용되었던 수평선을 대체하는 역할을 합니다. 이 클래스는 매우 흥미로운데 코드를 살펴보면 아래 코드에서 볼 수 있듯이 프라이빗 정적 변수가 있습니다.
#property copyright "Daniel Jose" #include "C_Object_BackGround.mqh" //+------------------------------------------------------------------+ class C_Object_TradeLine : public C_Object_BackGround { private : static string m_MemNameObj; public : //+------------------------------------------------------------------+ // ... Internal class code //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+ string C_Object_TradeLine::m_MemNameObj = NULL; //+------------------------------------------------------------------+
선언하는 방법과 올바르게 초기화하는 방법을 보여주기 위해 이 프라이빗 정적 변수는 강조 표시되어 있습니다. 전역 변수를 만들어 정적 변수의 기능을 대체할 수도 있지만 저는 모든 제어를 유지하고 싶습니다. 이렇게 하면 각 객체에 필요한 모든 것이 있고 정보가 저장됩니다. 한 객체를 다른 객체로 바꾸고 싶다면 쉽게 교체할 수 있습니다.
다음으로 주의해야 할 것은 객체 생성 코드입니다.
void Create(string szObjectName, color cor) { C_Object_BackGround::Create(szObjectName, cor); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); SpotLight(szObjectName); };
이를 올바르게 구현하기 위해 실제로 라인의 역할을 할 상자를 생성하는 C_Object_BackGround 클래스를 사용합니다. 다시 말하지만 다른 유형의 객체를 사용했다면 지금과 같은 동작이 발생하지 않을 것입니다. 우리가 필요로 하는 유일한 객체는 C_Object_Background 클래스에 있는 것입니다. 따라서 우리는 필요에 맞게 수정하여 라인을 만들 것입니다.
다음으로 줄을 강조 표시하는 코드를 살펴봅니다.
void SpotLight(string szObjectName = NULL) { if (szObjectName != NULL) ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, (szObjectName != NULL ? 4 : 3)); if (m_MemNameObj != NULL) ObjectSetInteger(Terminal.Get_ID(), m_MemNameObj, OBJPROP_YSIZE, 3); m_MemNameObj = szObjectName; };
이 코드는 매우 흥미로운데 어떤 줄을 강조 표시할 때 어떤 줄이 강조 표시되었는지 알 필요가 없습니다. 객체 자체에서 이 작업을 수행합니다. 그리고 새 줄이 강조 표시되어야 하는 경우에는 강조 표시된 줄은 자동으로 자신의 상태를 잃고 새 줄이 그 자리를 대신합니다. 이제 강조 표시할 줄이 없어야 하는 경우 함수를 호출하기만 하면 모든 줄에서 강조 표시를 제거할 수 있습니다.
이와 같은 원리로 위의 코드와 다음의 코드를 함께 사용하면 이전에 선택한 코드가 사라집니다. 이러한 방식으로 MetaTrader 5는 자신이 어떤 지표를 조작하고 있는지 우리에게 알려줍니다.
string GetObjectSelected(void) const { return m_MemNameObj; }
주목할 만한 또 다른 함수가 있습니다. Y축을 따라 선을 배치합니다. 아래에서 확인할 수 있습니다.
virtual void PositionAxleY(string szObjectName, int Y) { int desly = (m_MemNameObj == szObjectName ? 2 : 1); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desly); };
백그라운드 객체에 표시된 함수와 마찬가지로 이 함수도 선이 강조 표시되었는지 여부에 따라 올바른 지점을 스스로 조정합니다.
이제 두 개의 객체가 전부 완성되었습니다. 하지만 위 그림과 같이 실제로 화면에 표시되도록 하기 전에 C_ObjectsTrade 클래스에서 몇 가지 작업을 수행해야 합니다.
2.0.2. C_ObjectsTrade 클래스 수정
언뜻 보기에는 수정해야 할 내용이 그리 복잡하지 않아 보이지만 같은 코드를 반복해야 하는 횟수가 많아서 가끔은 조금 실망스러울 수 있습니다. 저는 이 문제를 해결할 방법을 찾으려고 노력했습니다. 가장 먼저 할 일은 이벤트 열거를 만드는 것입니다. 매크로를 사용하고 있지만 매크로로 가득 찬 코드를 따라가는 것이 혼란스럽다면 매크로를 함수나 프로시저로 전환하고 극단적인 경우에는 매크로를 적절한 내부 코드로 대체하는 것도 좋습니다. 저는 수년 동안 이 작업을 해왔기 때문에 매크로를 사용하는 것을 선호합니다.
먼저 이벤트 열거형을 생성합니다.
enum eEventType {EV_GROUND = 65, EV_LINE};
객체가 생성되면서 우리는 여기에 새 이벤트를 추가해야 합니다. 여기서 이 이벤트는 중요합니다. 그러나 각 객체에는 한 가지 유형의 이벤트만 있으며 해당 이벤트는 MetaTrader 5에서 생성될 것입니다. 또는 코드는 이벤트가 올바르게 처리되는지 확인하는 데만 사용될 것입니다.
이 작업이 완료되면 각 객체에 대한 액세스를 제공하는 변수들을 생성합니다.
C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine;
변수들은 클래스의 글로벌 범위에 속하지만 프라이빗 입니다. 객체를 사용하는 모든 함수에서 선언할 수도 있지만 전체 클래스가 객체를 처리하기 때문에 큰 의미가 없습니다.
따라서 이전 기사의 해당 코드를 변경합니다.
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev) { return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev); }
강조 표시된 부분은 이전 버전에는 없었지만 이제는 무슨 일이 일어나고 있는지 MetaTrader 5에게 계속 알려주는 데 도움이 될 것입니다.
또한 새로운 함수가 추가되었습니다.
void SetPositionMinimalAxleX(void) { m_PositionMinimalAlxeX = (int)(ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) * 0.2); }
이 함수는 객체의 X 축을 따라 시작점을 만듭니다. 각 객체에는 특정 포인트가 있지만 여기서는 기초적인 참조를 제공합니다. 위의 코드에서 포인트를 변경해서 시작 위치를 변경하도록 수정할 수 있습니다.
선택 함수는 많은 변경을 거쳤지만 나중에 조금 더 변경될 예정입니다. 지금으로서는 다음과 같습니다.
inline void Select(const string &sparam) { ulong tick; double price; eIndicatorTrade it; eEventType ev; string sz = sparam; if (!GetInfosOrder(sparam, tick, price, it, ev)) sz = NULL; m_TradeLine.SpotLight(sz); }
또한 변경된 또 다른 함수는 지표를 만드는 함수입니다.
inline void CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select) { if (price <= 0) RemoveIndicatorTrade(ticket, it); else { CreateIndicatorTrade(ticket, it, select); PositionAxlePrice(price, ticket, it, -1, -1, 0, false); } }
하지만 위의 코드는 그다지 중요하지 않습니다. 많은 노력이 필요한 것은 아래 코드에 나와 있습니다.
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2; string sz0; switch (it) { case IT_TAKE : cor1 = clrPaleGreen; cor2 = clrDarkGreen; break; case IT_STOP : cor1 = clrCoral; cor2 = clrMaroon; break; case IT_PENDING: default: cor1 = clrGold; cor2 = clrDarkGoldenrod; break; } m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: m_BackGround.Size(sz0, 92, 22); break; case IT_PENDING: m_BackGround.Size(sz0, 110, 22); break; } }
이 함수에서 우리는 객체의 색상과 생성 순서를 결정하고 크기를 결정합니다. 지표에 추가되는 모든 객체는 이 함수를 사용하여 배치해야 모든 것이 중앙에 배치되고 항상 확인됩니다. 지표를 만들기 위해 함수를 만들기 시작하면 유지 관리가 어렵고 적절한 점검이 부족한 코드 유형이 만들어질 수 있습니다. 모든 것이 정상이고 작동한다고 생각하고 실제 계정에 배치한 다음 실제로 확인하면 갑자기 일부가 제대로 작동하지 않는다는 것을 알게 되곤 합니다. 제가 드리고 싶은 조언은 항상 같은 일을 하는 함수들을 합쳐 보라는 것입니다. 처음에는 무의미해 보일지 모르지만 결국 이해가 될 것입니다. 그렇지 않으면 여러분은 매일 변경 사항을 체크하고 있게 될 것입니다.
다음 변경된 함수는 아래와 같습니다.
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
그 다음 함수도 그렇겠지만 약간 지루합니다. 이 함수는 생성된 각 개체를 각각 하나씩 선택하도록 작동합니다. 이는 각 지표에 적용되어야 합니다. 생각해 보세요. 작업을 더 쉽게 만들어주는 매크로를 사용하지 않는다면 이것은 악몽이 될 것입니다. 코드 끝에 있는 각 지표에는 5개의 객체가 있으므로 이 함수를 코딩하는 것은 매우 지루할 수 있습니다. OCO 주문의 각 세트에 3개의 지표가 있다면 우리는 15개의 객체를 사용해야 하는데 이 경우 실수할 가능성이 매우 커집니다(이름만 다를 뿐이므로). 따라서 매크로를 사용하면 코드에서 강조 표시된 대로 코드가 줄어들어 마지막에 5개의 객체만 코딩하게 됩니다. 하지만 이것은 위에 표시된 결과를 얻기 위한 첫 번째 단계일 뿐입니다.
첫 번째 단계를 완료하기 위한 지루한 함수가 하나 더 있습니다. 매크로를 사용하지 않는다면 매크로 대신 프로시저를 사용할 수 있습니다. 하지만 저는 이 방법을 선택했습니다.
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { double ad; int x, y; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); macroSetAxleY(it); macroSetAxleX(it, m_PositionMinimalAlxeX); if (Leverange == 0) return; if (it == IT_PENDING) { ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal()); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y); macroSetAxleY(IT_TAKE); macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 120); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y); macroSetAxleY(IT_STOP); macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220); } } #undef macroSetAxleX #undef macroSetAxleY
이전 기능이 지루하다고 생각했다면 이 기능을 확인해 보세요. 여기서는 작업이 두 배가 되지만 강조 표시된 코드 덕분에 봐 줄만 합니다.
글쎄요 다른 작은 변경 사항이 있지만 작은 부분에 지나지 않습니다. 이 코드를 실행하면 예상 한 대로 화면에 표시되는 지표가 정확히 나타납니다.
결론
시스템이 완성되어 주문이 차트에 완전히 표시되기 까지는 꽤 많은 시간이 남아 있습니다. 하지만 이제는 한 번에 모두 끝내야 합니다. 코드의 다른 부분에서 매우 중요한 변경을 수행할 것입니다.
변경 사항이 매우 심하게 필요하므로 이 부분은 다음 글에서 다루도록 하겠습니다. 문제가 발생하면 한 단계 뒤로 돌아가서 원하는 방식으로 시스템이 돌아가도록 변경할 수 있을 때까지 다시 시도해야 합니다. 이러한 방식으로 시스템을 사용자 지정하여 마음대로 사용할 수 있습니다. 다음 글에서는 아래와 같은 시스템을 만들어 보겠습니다:
구현하기 쉬워 보이지 않나요? 하지만 변경해야 할 사항이 많이 있습니다. 그럼 다음 글에서 뵙겠습니다.
MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10497