Expert Advisor 개발 기초부터(21부): 새로운 주문 시스템(IV)
소개
이전 글인 Expert Advisor 개발 기초부터(20부)에서 시각적 주문 시스템을 만들기 위해 변경해야 할 주요 사항을 살펴봤습니다. 그러나 드 다음 단계에 대해서는 더 많은 설명이 필요했기 때문에 기사를 여러 부분으로 나누기로 결정했습니다. 여기서 이제 우리는 주요 변경 사항을 다 적용할 것입니다. 꽤 많은 항목이 있지만 모두 필요한 항목입니다. 아마도 전체 작업이 꽤 흥미로울 것입니다. 그러나 시스템을 실제로 완성하기 까지 아직 할 일이 남아 있고 여기서 작업을 완료하지는 못할 것입니다. 어쨌든 이 기사가 끝날 때쯤이면 시스템에 필요한 거의 모든 기능들이 갖춰질 것입니다.
이제 구현으로 바로 넘어가 보겠습니다.
1.0. 구현
먼저 주문에 청산 혹은 취소 버튼을 추가하겠습니다. 이 버튼들을 담당하는 클래스는 아래와 같습니다.
1.0.1. C_Object_BtnBitMap 클래스
이 클래스는 아래에서 볼 수 있듯이 차트에서 비트맵 버튼을 지원하는 역할을 담당합니다.
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Base.mqh" //+------------------------------------------------------------------+ #define def_BtnClose "Images\\NanoEA-SIMD\\Btn_Close.bmp" //+------------------------------------------------------------------+ #resource "\\" + def_BtnClose //+------------------------------------------------------------------+ class C_Object_BtnBitMap : public C_Object_Base { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string szResource1, string szResource2 = NULL) { C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false); }; //+------------------------------------------------------------------+ bool GetStateButton(string szObjectName) const { return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE); } //+------------------------------------------------------------------+ };
이 코드 클래스를 작성하면서 저는 포지셔닝 클래스를 C_Object_Base 클래스로 옮길 수 있다는 사실을 깨달았습니다. 전체 C_Object_BackGround 클래스는 하위 클래스에 속합니다. 그러므로 이 코드를 제거할 수 있습니다. 이를 코드 재사용이라고 합니다. 이 접근 방식은 프로그래밍을 덜 할 수 있고 성능이 향상되지만 무엇보다도 수정 사항을 더 자주 확인하게 되므로 코드의 안정성이 높아집니다.
청산 버튼을 추가하려면 다음을 수행합니다:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_TradeLine.mqh" #include "C_Object_BtnBitMap.mqh" //+------------------------------------------------------------------+ class C_ObjectsTrade { // ... Class code ... }
다음 단계
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};
그리고 다음 단계
C_Object_BackGround m_BackGround;
C_Object_TradeLine m_TradeLine;
C_Object_BtnBitMap m_BtnClose;
그리고 다음 단계
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2; string sz0; // ... Internal function code ... 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; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); }
그리고 다음 단계
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ } 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
마지막으로, 마지막 단계는...
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { // ... Internal code... } #undef macroSetAxleX #undef macroSetAxleY
이 시스템을 실행하면 다음과 같은 결과를 얻을 수 있습니다:
MetaTrader 5에서 이 버튼에 대한 이벤트를 생성하고 Expert Advisor가 처리하지만 이 버튼은 여전히 작동하지 않습니다. 우리는 이 기능을 아직 구현하지 않았습니다. 이에 대해서는 이 글의 뒷부분에서 다시 살펴보겠습니다.
1.0.2. C_Object_Edit 클래스
거래하는 내용을 시스템이 트레이더에게 알릴 수 없다면 그 시스템은 무용지물이 될 것입니다. 이를 위해 C_Object_Edit 클래스가 있습니다. 이 클래스는 나중에 기능을 향상시키기 위해 몇 가지의 변경을 거쳐야 하지만 지금은 트레이더에게 무슨 일이 일어나고 있는지 알려주는 역할에 집중하는 것이므로 우선은 그대로 두겠습니다. 이를 구현하려면 클래스에 몇 줄의 코드를 추가해야 합니다. 새로운 코드가 포함된 첫 번째 스니펫입니다:
void Create(string szObjectName, color cor, int InfoValue) { C_Object_Base::Create(szObjectName, OBJ_EDIT); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true); SetTextValue(szObjectName, InfoValue, cor); }
강조 표시된 코드는 트레이더가 값을 변경하는 것이 불가 하지만 앞서 말씀드렸듯이 향후 변경될 예정입니다. 이를 위해서는 현재로서는 관련이 없는 몇 가지 다른 변경 사항이 필요합니다.
다음 함수는 텍스트가 표시되도록 합니다. 이 함수의 한 가지 세부 사항에 주목하세요:
void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE) { color clr; clr = (cor != clrNONE ? cor : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive)); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr); }
선택한 코드는 입력 값에 따라 텍스트 배경의 색상을 표시합니다. 값이 음수인지 양수인지 계속 추측하거나 텍스트에 음수 값이 있는지 확인하는 데 누구도 시간을 소비하고 싶지 않을 것입니다. 그러므로 여기서 이 작업을 수행하는 것이 중요합니다. 값이 양수인지 음수인지를 보고 바로 알수 있는 방식이 편리합니다. 이제 색상에 따라 값이 음수인지 양수인지 즉시 확인할 수 있습니다. 하지만 여기에는 색상이 이전에 정의되어 있지 않아야 한다는 조건이 있습니다. 이 기능은 나중에 유용하게 사용할 수 있습니다.
다음으로 이 클래스의 마지막 함수는 아래와 같습니다.
long GetTextValue(string szObjectName) const { return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1)); };
값을 표현할 때 서식 때문에 값이 항상 양수이게 됩니다. 하지만 객체의 내용을 확인할 때는 정확한 정보가 있어야 합니다. 여기가 강조 표시된 코드가 사용되는 곳입니다. 다시 말해 색상 정보가 사용되는 곳입니다. 색상이 음수 값임을 나타내는 경우 EA에 올바른 정보를 제공하기 위해 수정될 것입니다. 색상이 양수 값을 나타내면 해당 값이 저장됩니다.
색상 정의는 클래스 자체에 있습니다. 나중에 다른 색상을 설정하려는 경우 수정할 수 있지만 이전 함수가 올바르게 작동하도록 하기 위해서는 다른 색상을 사용해야 합니다. 그렇지 않으면 EA에서 모호한 값을 얻을 수 있습니다. 따라서 EA는 음수 값을 양수로 인식할 수 있게 되며 이는 EA가 수행하는 분석에 문제를 일으킬 수 있습니다.
1.0.3. C_Object_Label 클래스
이 단계에 필요한 마지막 클래스입니다. 사실 이 클래스의 동작이 C_Object_BtnBitMap 클래스와 유사하기 때문에 이 클래스를 만들지 않아도 될 것으로 생각했습니다. 하지만 저는 C_Object_Edit 클래스와 독립적으로 텍스트 정보를 추가할 수 있기를 원했기 때문에 여기에 새 클래스를 만들기로 결정했습니다.
코드는 매우 간단합니다. 아래와 같습니다.
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Edit.mqh" //+------------------------------------------------------------------+ class C_Object_Label : public C_Object_Edit { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack) { C_Object_Base::Create(szObjectName, OBJ_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); }; //+------------------------------------------------------------------+ };
나머지 모든 작업은 이미 하위 클래스 객체에 의해 구현되었으므로 이 클래스에 별도의 작업이 필요하지 않습니다.
보시다시피 OOP는 매우 강력한 도구입니다. 코드를 클래스로 더 많이 구성할수록 서로 유사한 클래스를 프로그래밍할 필요가 줄어듭니다.
하지만 구현해야 할 작은 변경이 있습니다. 저는 코드를 시험 하면서 패널 데이터를 해석하는 것이 매우 어렵다는 것을 알게 되었고 그래서 다음과 같이 변경했습니다.
이렇게 하면 큰 값을 더 쉽게 표시할 수 있습니다. 이에 따른 객체는 다음과 같이 표시됩니다. 위쪽에는 오픈 포지션의 계약 수 또는 레버리지 계수가 표시되고 아래쪽에는 포지션 결과가 표시됩니다.
이를 구현하려면 주로 오브젝트의 위치를 지정하는 역할을 담당하는 C_Object_Base 클래스를 변경해야 합니다. 변경 사항은 아래 코드에 강조 표시되어 있습니다.
virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0) { int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl))); };
이후 다음 단계로 넘어가 C_ObjectsTrade 클래스를 변경합니다.
1.0.4 C_ObjectsTrade 클래스
이제 필요한 객체를 그리는 작업을 완료해 봅시다. 원하는 결과를 차트에서 실제로 얻을 수 있습니다. 모든 정보와 연결된 모든 객체가 있게 됩니다. 그리 어렵지 않으며 우리는 각 단계별로 분석 할 수 있습니다. 사용 방법을 이해했다면 여러분은 원하는 다른 정보를 추가할 수 있으며 문제없이 사용할 수 있습니다. 가장 먼저 해야 할 일은 객체가 응답해야 하는 새로운 이벤트를 정의하는 것입니다. 이는 아래 코드에서 강조 표시되어 있습니다.
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};
이제 필요한 객체를 추가해 보겠습니다. 새 객체는 아래 코드에서도 강조 표시되어 있습니다:
C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine; C_Object_BtnBitMap m_BtnClose; C_Object_Edit m_EditInfo, m_InfoVol; C_Object_Label m_BtnMove;
그런 다음 개체를 만들고 화면에서 어떻게 보일지 정의합니다. 모든 객체가 생성될 때까지 배경 객체를 먼저 생성한 다음 배경 위에 배치할 다음 객체를 생성하는 등의 순서로 객체를 생성해야 한다는 점에 유의하세요. 순서가 잘못되어 객체 중 하나가 숨겨져 있는 경우 이 코드에서 해당 위치를 변경하기만 하면 됩니다. 이제 코드가 나왔습니다:
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; int infoValue; switch (it) { case IT_TAKE : infoValue = m_BaseFinance.FinanceTake; cor1 = clrForestGreen; cor2 = clrDarkGreen; cor3 = clrNONE; break; case IT_STOP : infoValue = - m_BaseFinance.FinanceStop; cor1 = clrFireBrick; cor2 = clrMaroon; cor3 = clrNONE; break; case IT_PENDING: infoValue = m_BaseFinance.Leverange; cor1 = clrCornflowerBlue; cor2 = clrDarkGoldenrod; cor3 = clrLightBlue; break; case IT_RESULT : default: infoValue = m_BaseFinance.Leverange; cor1 = clrDarkBlue; cor2 = clrDarkBlue; cor3 = clrSilver; 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: case IT_PENDING: m_BackGround.Size(sz0, 92, 22); break; case IT_RESULT: m_BackGround.Size(sz0, 84, 34); break; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue); m_EditInfo.Size(sz0, 60, 14); if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2); else { m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue); m_InfoVol.Size(sz0, 60, 14); } }
강조 표시된 모든 줄은 이전 문서에서 제시한 마지막 버전 이후 코드에 추가된 내용입니다. 이제 다음 함수에 대한 코드를 작성할 수 있습니다.
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT)); \ if (A != IT_RESULT) \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE)); \ else \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME)); \ } 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
매크로를 사용해서 얻는 장점에 주목하세요: 패널의 모든 객체를 삭제할 수 있도록 강조 표시된 부분만 추가해야 했습니다. 이제 우리는 4개의 패널에 6개의 객체를 사용하고 있습니다. 이를 다른 방식으로 구현하려면 너무 많은 작업이 필요하므로 오류가 발생할 가능성이 높습니다. 이제 포지셔닝 함수를 마무리해 보겠습니다.
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0)); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y); \ else \ m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3); \ m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80); \ else \ m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21); \ } 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 + 110); 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
다시 말하지만 코드가 거의 추가되지 않았습니다. 그럼에도 불구하고 이 함수는 매크로를 사용하여 요소를 올바르게 배치하기 때문에 모든 요소에서 작동할 수 있습니다. 이 단계에서 EA를 컴파일하면 다음과 같은 결과를 얻을 수 있습니다:
모든 것이 멋져 보이지만 이러한 컨트롤은 여전히 작동하지 않습니다. 각 객체에 대한 이벤트를 구현하는 것이 필요합니다. 이벤트가 없으면 이 인터페이스는 거의 쓸모가 없습니다. 인터페이스가 실제로 하는 일은 원래 사용되던 라인을 대체하는 것뿐이기 때문입니다.
2.0. 문제 해결 방법
해결 방법이 간단하다면 누구나 할 수 있습니다. 하지만 개발 과정의 일부는 항상 해결해야 할 문제가 있다는 것입니다. 제가 문제를 해결 하는 방법을 보여주는 대신 해결책을 드릴 수도 있습니다. 하지만 이 글들을 통해 여러분이 문제를 해결하고 실제로 프로그래밍하는 방법을 배우는 동기를 얻게 되기를 바랍니다. 따라서 이 섹션에는 흥미로운 내용이 있을 것입니다.
2.0.1. 차트 업데이트에 따라 조정하기
이것이 우리에게 닥친 첫 번째 문제입니다. 차트 업데이트와 함께 객체의 위치가 업데이트되지 않기 때문에 발생하는 문제입니다. 이를 이해하려면 아래 GIF를 참조하세요:
이런 상황은 당황스러울 수 있지만 해결책은 매우 간단합니다: MetaTrader 5 자체에서 차트를 업데이트해야 함을 알리는 이벤트를 생성합니다. 따라서 우리는 이벤트를 캡처하고 주문 시스템을 업데이트해야 합니다.
변경 사항은 CHARTEVENT_CHART_CHANGE 이벤트 호출에서 캡처되어야 합니다. EA 코드에 있는 UpdatePosition 함수를 사용하면 업데이트가 더 쉬워집니다. 코드에 한 줄만 추가하면 됩니다. 이 작업은 아래와 같이 C_OrderView에서 수행됩니다:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { // ... Code .... switch (id) { case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... The rest of the code...
이 간단한 솔루션에는 한 가지 문제가 있습니다: 어느 자산에 대해 많은 주문을 하는 경우 시간이 걸릴 수 있으며 이로 인해 EA가 다음 과정을 처리 하기 전에 업데이트가 중단될 수 있습니다. 솔루션은 프로세스 속도를 높여주는 복잡한 솔루션도 있습니다. 그러나 이 솔루션은 이 시스템에 충분합니다. 결과는 다음과 같습니다.
모든 것이 맞는 것 같지 않나요? 하지만 여기에 오류가 있습니다. 시스템을 테스트하기 전까지는 확인하기 어렵습니다. 그러나 이 시스템에는 실제로 수정 후에도 결함이 있습니다.
2.0.2. EA가 요소 자동 선택을 하지 말아야 합니다.
위의 GIF를 보면 우리는 스탑라인을 선택하지 않았지만 선택되어 있는 것을 확인할 수 있습니다. EA는 매번 이 작업을 수행합니다. 패널 생성 시 설정에 따라 차트를 움직일 때마다 EA가 릭절 또는 포지션 라인 등을 선택할 수 있습니다.
여러분들은 무슨 일이 일어나고 있는지 이해하려고 애를 쓰실 수도 있지만 해결 방법은 이전보다 훨씬 간단합니다. 동일한 코드에 한 줄을 추가하기만 하면 EA가 자동으로 줄 선택을 중지합니다. 수정 사항은 아래 코드에 강조 표시되어 있습니다.
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; double infoValue; // ... Internal code... Select(NULL); }
이러한 일은 개발 중에 항상 발생합니다. 쉽게 발견할 수 있는 버그와 눈에 잘 띄지 않는 버그가 있는데 처음에는 눈치채지 못할 때가 있습니다. 어쨌든 이런 일은 일어날 수 있습니다. 따라서 프로그래밍을 배우는 것은 지속적인 과정입니다. 때로는 이러한 문제를 직접 해결하고 모든 사람과 공유하여 다른 사람들이 같은 문제를 해결하도록 도울 수 있습니다. 이렇게 해 보세요. 저는 그러한 방식으로 프로그래밍을 배웠습니다. 매우 실용적입니다. 같은 방법으로 프로그램을 만드는 방법도 배울 수 있습니다. 일반적으로 소스 코드를 가져와서 수정하는 것은 학습의 일부입니다. 함수형 코드를 사용하면 코드가 어떻게 만들어졌는지를 이해하기가 더 간단해지며 각 프로그래머가 특정 문제를 어떻게 해결했는지 이해하는 데 도움이 됩니다.
하지만 이것은 여러분들이 공부를 하도록 동기를 부여하는 것일 뿐입니다. 정말 중요한 내용으로 넘어가 보겠습니다.
3.0. 이벤트 처리
이제 우리는 포지션 결과를 보고하는 시스템을 구축할 예정입니다. 시스템이 차트 트레이드의 관련 영역을 대체하지만 저는 차트 트레이딩를 그대로 두겠습니다. 차트 트레이드가 왜냐하면 포지션의 총 결과를 나타내주기 때문입니다. 계좌가 헤징을 지원하는 경우 주문 시스템에서 지정한 값과 다를 수 있습니다. 왜냐하면 하나는 로컬 값이고 다른 하나는 전체 값을 나타내기 때문입니다. 다른 유형의 계좌에는 이러한 차이가 없으므로 여러분이 원하는 경우 차트 트레이드에서 시스템을 제거해도 됩니다.
3.0.1. 위치 결과 보기
코드를 처음 보는 사람은 어디서 정보를 검색해야 할지 몰라 헤맬 수 있습니다. 또는 원래 코드에서 이미 수행하는 작업을 다시 만들게 되는 경우도 있을 것입니다. 이로 인해 원래 코드에는 없던 버그를 생성하거나 추가적인 코드가 생성되는 등 많은 문제가 발생하게 됩니다. 이는 정말 필요한 경우에만 프로그래밍을 해야 하는 재사용 규칙을 준수하지 않습니다. 따라서 MetaTrader 5의 작동 방식을 알고 EA가 이미 작동하는 방식을 알면 여러분은 차트 트레이드에서 표시된 결과가 생성되도록 하는 위치를 찾을 수 있을 것입니다. 포지션의 결과가 제시되면 우리는 이를 사용해야 하기 때문입니다. 아래 코드에 주목하세요.
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); TimesAndTrade.Update(); }
강조 표시된 지점입니다. 소스 코드는 아래와 같습니다.
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
어떤 분은 어떤 객체를 참조해야 하는지 알 방법이 없는데 어떻게 데이터를 가져와 패널에 적용하냐고 물을 수 있습니다. 객체가 어떤 기준이나 관리 없이 느슨하게 만들어진 것처럼 보일 수 있습니다... 그러나 이는 사실이 아닙니다. 만약 이런 식으로 생각하는 분이 계시다면 MetaTraderr 5 플랫폼이 실제로 어떻게 작동하는지에 대해 조금 더 알아보시기 바랍니다. 제가 생성되는 객체를 참조하는 목록이나 배열 또는 구조를 만들지 않은 것은 사실입니다. 그러나 그건 의도적으로 그렇게 한 것입니다. 그래도 작동합니다. 차트에 표시될 객체를 저장하는 구조를 사용하지 않고도 특정 객체를 참조할 수 있다는 것을 보여드리겠습니다. 정말 필요한것은 다음과 같습니다. 객체 이름을 올바르게 모델링하는 것입니다.. 이게 전부입니다.
Q: 객체 이름은 어떻게 모델링된 것일까요?
정답은 다음과 같습니다:
1 - 헤더의 순서 | 이 시퀀스는 주문 시스템에서 사용되는 객체를 다른 모든 객체와 구분합니다. |
2 - 캐릭터 제한 | 이는 다른 정보를 따라야 함을 나타냅니다. |
3 - 유형 표시 | 테이크와 스톱을 구분합니다. |
4 - 캐릭터 제한 | 2와 동일합니다. |
5 - 주문 또는 포지션 티켓 | 주문 티켓을 기억합니다 - OCO 주문 데이터를 연결하고 주문을 구분합니다. |
6 - 캐릭터 제한 | 2와 동일합니다. |
7 - 이벤트 표시 | 동일한 패널 내의 객체를 구분합니다. |
즉 모델링이 전부 입니다. 실제로 프로그래밍을 하지 않는 사람들에게는 우리가 반복적인 것을 만드는 것처럼 보이지만 사실 우리는 각 객체가 고유하고 간단한 규칙을 통해 참조되도록 하는 고유한 것을 만드는 것이므로 이 규칙은 다음 코드에 의해 생성됩니다:
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); }
따라서 이전 코드에 어떤 주문 티켓, 어떤 지표, 어떤 이벤트에 액세스하려는지 알려주면 특정 객체의 이름을 알 수 있게 될 것입니다. 이를 통해 속성을 조작할 수 있습니다. 이것이 첫 번째 단계라는 것을 알았으니 이제 우리는 어떻게 하면 코드에 혼란을 일으키지 않고 코드를 프랑켄슈타인처럼 만들지 않으면서도 이 조작을 안전하게 만들 수 있을지에 대해 한 가지 더 결정해야 합니다.
이제 해보겠습니다. C_ObjectsTrade 클래스로 이동하여 다음 코드를 추가해 보겠습니다.
inline void SetResult(ulong ticket, double dVolume, double dResult) { m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult); m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult); }
이제 C_Router 클래스로 이동하여 강조 표시된 코드를 추가해 보겠습니다.
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT)); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
이렇게 하면 문제 중 하나가 해결됩니다. 하지만 아직 해결되지 않은 다른 문제들이 남아 있습니다.
3.0.2. 펜딩 주문의 거래량을 표시합니다.
이제 펜딩 주문으로 표시된 볼륨을 해결해 보겠습니다. 이렇게 하려면 C_ObjectsTrade 클래스에 새로운 함수를 만들어야 합니다.
inline void SetVolumePendent(ulong ticket, double dVolume) { m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit); }
이 작업이 완료되고 난 후 C_Router 클래스의 UpdatePosition 함수를 사용하면 업데이트가 원활하게 이루어집니다.
void UpdatePosition(int iAdjust = -1) { // ... Internal code .... for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); take = OrderGetDouble(ORDER_TP); stop = OrderGetDouble(ORDER_SL); bTest = CheckLimits(price); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); // ... Internal code... CreateIndicatorTrade(ul, price, IT_PENDING); SetVolumePendent(ul, vol); CreateIndicatorTrade(ul, take, IT_TAKE); CreateIndicatorTrade(ul, stop, IT_STOP); } };
이제 문제가 해결되었습니다. 이제 값을 해결해야 합니다. 값은 다음과 같은 이유로 테이크 및 스톱으로 표시됩니다. 우리가 차트에서 주문한 후에 그렇게 됩니다.
3.0.3. 패널 닫기 버튼 클릭 이벤트
주문 또는 스탑 레벨 중 하나를 제거하는 유일한 안전한 방법은 각 값의 모서리에 있는 취소 버튼을 사용하는 것입니다. 하지만 여기에는 잘못 구현된 이벤트가 있습니다. 이 문제를 해결해 보겠습니다.
클릭 이벤트는 실제로 C_OrderView 클래스에서 구현되어야 합니다. 우리는 기존 시스템을 강조 표시된 코드로 교체해야 합니다.
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetInfosOrder(sparam, ticket, price, it, ev)) { switch (ev) { case EV_CLOSE: if (OrderSelect(ticket)) switch (it) { case IT_PENDING: RemoveOrderPendent(ticket); break; case IT_TAKE: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL)); break; case IT_STOP: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0); break; } if (PositionSelectByTicket(ticket)) switch (it) { case IT_RESULT: ClosePosition(ticket); break; case IT_TAKE: ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL)); break; case IT_STOP: ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0); break; } break; // ... Rest of the code...
이 클래스에 추가할 것이 하나 더 있습니다. 트레이더가 실수로 포지션 데이터를 보고하는 객체를 삭제하면 어떻게 될까요? 이를 방지하기 위해 시스템에 다음과 같은 코드가 추가되었습니다.
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { case CHART_EVENT_OBJECT_DELETE: case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... Rest of the code...
이렇게 하면 작업자가 삭제해서는 안 되는 항목을 삭제할 경우 EA가 삭제된 객체를 빠르게 대체합니다.
다음 동영상은 현재 시스템이 어떻게 작동하는지 보여줍니다. 글에서 다루지 않은 사소한 변경 사항도 몇 가지 있었습니다.
결론
시스템이 완성된 것처럼 보이고 시스템을 사용하여 거래하고 싶으실 수도 있지만 아직 완성되지 않았다는 점을 잊지 마세요. 이 글은 주문 시스템을 훨씬 더 실용적이고 사용하기 쉽게 추가하고 변경하는 방법을 보여드리기 위한 것입니다. 그러나 여전히 포지션 이동을 가능하게 하는 기능이 부족합니다. 이 부분이 완성되면 EA를 매우 유익하고 실용적이며 직관적으로 사용할 수 있습니다. 그 내용은 다음 글에서 다루도록 하겠습니다.
첨부 파일에는 현재 단계의 시스템이 포함되어 있습니다.
MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10499