English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
다중 시간대 및 다중 통화 패널 구축을 위한 객체 지향 접근 방식

다중 시간대 및 다중 통화 패널 구축을 위한 객체 지향 접근 방식

MetaTrader 5 | 5 7월 2021, 11:00
61 0
Marcin Konieczny
Marcin Konieczny

소개

이 문서에서는 MetaTrader 5에 대한 다중 시간 프레임 및 다중 통화 패널을 만드는데 객체 지향 프로그래밍을 사용하는 방법을 설명합니다. 주요 목표는 패널 자체의 코드를 수정할 필요 없이 가격, 가격 변동, 지표 값 또는 맞춤형 구매/판매 조건과 같은 다양한 종류의 데이터를 표시하는 데 사용할 수 있는 범용 패널을 구축하는 것입니다. 이렇게 하면 필요한 방식으로 패널을 맞춤 설정하는데 코딩이 거의 필요하지 않습니다.

설명할 솔루션은 두 가지 모드로 작동합니다.

  1. 다중 타임 프레임 모드 - 현재 심볼에서 계산 된 테이블 내용을 다른 타임 프레임에서 볼 수 있습니다.
  2. 다중 통화 모드 - 현재 시간 프레임에서 계산 된 표 내용을 다른 기호로 볼 수 있습니다.

다음 그림은 이 두 가지 모드의 패널을 보여줍니다.

첫 번째는 다중 시간 프레임 모드에서 작동하며 다음 데이터를 표시합니다.

  1. 현재 가격;
  2. 현재 바의 가격 변동;
  3. 퍼센트로 나타낸 현재 바의 가격 변동;
  4. 화살표로 표시되는 현재 바의 가격 변동 (위/아래);
  5. RSI(14) 인디케이터 값;
  6. RSI(10) 인디케이터 값;
  7. 맞춤 조건: SMA (20) > 현재 가격.

그림 1. 다중 시간 프레임 모드

그림 1. 다중 시간 프레임 모드


두 번째는 다중 통화 모드에서 작동하며 다음을 보여줍니다

  1. 현재 가격;
  2. 현재 바의 가격 변동;
  3. 퍼센트로 나타낸 현재 바의 가격 변동;
  4. 현재 바의 가격이 화살표로 변경됩니다.
  5. RSI(10) 인디케이터 값;
  6. RSI(14) 인디케이터 값;
  7. 맞춤 조건: SMA (20) > 현재 가격.

그림 2. 다중 통화 모드

그림 2. 다중 통화 모드


1. 이행

다음 클래스 다이어그램은 패널의 구현 설계를 설명합니다.

그림 3. 패널의 클래스 다이어그램

그림 3. 패널의 클래스 다이어그램


다이어그램의 요소를 설명하겠습니다

  1. CTable. 패널의 핵심 클래스. 패널 그리기 및 구성 요소 관리를 담당합니다.
  2. SpyAgent. 다른 기호 (기기)에 대한 '스파이'를 담당하는 인디케이터입니다. 모든 에이전트가 생성되어 다른 심볼로 전송됩니다. 에이전트는 새 틱이 심볼 차트에 도착하면 OnCalculate 이벤트에 반응하고 CHARTEVENT_CUSTOM 이벤트를 전송하여 업데이트 해야 함을 CTable 개체에 알립니다. 이 접근 방식의 전체 아이디어는 "MetaTrader 5에서 다중 통화 모드 구현" 글을 기반으로 합니다. 여기에서 모든 기술 세부 정보를 찾을 수 있습니다.
  3. CRow. 패널을 만드는 데 사용되는 모든 인디케이터 및 조건의 기본 클래스입니다. 이 클래스를 확장하면 패널에 필요한 모든 구성 요소를 만들 수 있습니다.
  4. CPriceRow. 현재 입찰 가격을 표시하는 데 사용되는 CRow를 확장하는 간단한 클래스입니다.
  5. CPriceChangeRow. 현재 바의 가격 변동을 표시하는데 사용되는 CRow를 확장하는 클래스입니다. 가격 변동, 백분율 변동 또는 화살표를 표시할 수 있습니다.
  6. CRSIRow. 현재 RSI 인디케이터 값을 표시하는 데 사용되는 CRow를 확장하는 클래스입니다.
  7. CPriceMARow. 맞춤 조건을 보여주는 CRow를 확장하는 클래스: SMA> 현재 가격.

CTable 및 CRow 클래스와 SpyAgent 인디케이터는 패널의 핵심 부분입니다. CPriceRow, CPriceChangeRow, CRSIRow 및 CPriceMARow는 패널의 실제 내용입니다. CRow 클래스는 원하는 결과를 얻기 위해 많은 새로운 클래스로 확장되도록 설계되었습니다. 제시된 네 가지 파생 클래스는 수행 할 수 있는 작업과 방법에 대한 간단한 예입니다.


2. SpyAgent

SpyAgent 인디케이터부터 시작하겠습니다. 다중 통화 모드에서만 사용되며 다른 차트에 새 틱이 도착할 때 패널을 올바르게 업데이트하는 데 필요합니다. 이 개념에 대해서는 자세히 설명하지 않겠습니다. "The Implementation of a Multi-currency Mode in MetaTrader 5" 글에 설명되어 있습니다.

SpyAgent 인디케이터는 지정된 심볼의 차트에서 실행되며 초기화 이벤트와 새 틱 이벤트라는 두 가지 이벤트를 보냅니다. 두 이벤트 모두 CHARTEVENT_CUSTOM 유형입니다. 이러한 이벤트를 처리하려면 OnChartEvent(...) 핸들러를 사용해야 합니다 (이 글의 뒷부분에 표시됨).

SpyAgent의 코드를 살펴 보겠습니다

//+------------------------------------------------------------------+
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // chart id
input ushort custom_event_id=0; // event id
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // sends initialization event
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // sends new tick event

   return(rates_total);
  }
아주 간단합니다. 그것이 하는 유일한 일은 새로운 틱을 받고 CHARTEVENT_CUSTOM 이벤트를 보내는 것입니다.


3. CTable

CTable은 패널의 핵심 클래스입니다. 패널 설정에 대한 정보를 저장하고 구성 요소를 관리합니다. 필요한 경우 패널을 업데이트 (다시 그리기).

CTable의 선언을 살펴 보겠습니다

//+------------------------------------------------------------------+
//| CTable class                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // distance from right border of the chart
   int               yDistance;    // distance from top of the chart
   int               cellHeight;   // table cell height
   int               cellWidth;    // table cell width
   string            font;         // font name
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // list of row objects
   bool              tfMode;       // is in multi-timeframe mode?

   ENUM_TIMEFRAMES   timeframes[]; // array of timeframes for multi-timeframe mode
   string            symbols[];    // array of currency pairs for multi-currency mode

   //--- private methods
   //--- sets default parameters of the table
   void              Init();
   //--- draws text label in the specified table cell
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- returns textual representation of given timeframe
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- multi-timeframe mode constructor
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- multi-currency mode constructor
                     CTable(string &symb[]);
   //--- destructor
                    ~CTable();
   //--- redraws table
   void              Update();
   //--- methods for setting table parameters
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- appends CRow object to the of the table
   void              AddRow(CRow *row);
  };

보시다시피 모든 패널 구성 요소 (행)는 CRow 포인터 목록으로 저장되므로 패널에 추가하려는 모든 구성 요소는 CRow 클래스를 확장해야 합니다. CRow는 패널과 해당 구성 요소 간의 계약으로 볼 수 있습니다. CTable에는 셀 계산을 위한 코드가 없습니다. CRow를 확장하는 클래스의 책임입니다. CTable은 CRow 구성 요소를 유지하고 필요할 때 다시 그리기 위한 구조일 뿐입니다.

CTable의 방법을 살펴 보겠습니다. 클래스에는 두 개의 생성자가 있습니다. 첫 번째는 다중 시간 프레임 모드에 사용되며 매우 간단합니다. 표시할 시간 프레임 배열만 제공하면 됩니다.

//+------------------------------------------------------------------+
//| Multi-timeframe mode constructor                                 |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- copy all timeframes to own array
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;
   
//--- fill symbols array with current chart symbol
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- set default parameters
   Init();
  }

두 번째 생성자는 다중 통화 모드에 사용되며 기호 배열 (도구)을 사용합니다. 이것은 또한 SpyAgents를 보냅니다. 적절한 차트에 하나씩 첨부합니다.

//+------------------------------------------------------------------+
//| Multi-currency mode constructor                                  |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- copy all symbols to own array
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;
   
//--- fill timeframe array with current timeframe
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- set default parameters
   Init();

//--- send SpyAgents to every requested symbol
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // don't send SpyAgent to own chart
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("Error in setting of SpyAgent on "+symbols[x]);
            return;
           }
  }

Init 메소드는 행 목록 (CList 객체로-CList는 CObject 유형의 동적 목록)을 생성하고 CTable 내부 변수 (글꼴, 글꼴)의 기본값을 설정합니다. 차트 오른쪽 상단 모서리에서 크기, 색상, 셀 크기 및 거리).

//+------------------------------------------------------------------+
//| Sets default parameters of the table                             |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- create list for storing row objects
   rowList=new CList;

//--- set defaults
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

소멸자는 매우 간단합니다. 행 목록을 삭제하고 패널에서 생성 한 모든 차트 개체 (레이블)를 삭제합니다.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- remove all text labels from the chart (all object names starting with nameBase prefix)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- delete list of rows and free memory
   delete(rowList);
  }

AddRow 메소드는 행 목록에 새 행을 추가합니다. rowList는 자동으로 크기가 조정되는 CList 개체입니다. 이 메소드는 또한 추가 된 모든 CRow 개체에 대해 Init 메소드를 호출합니다. 개체가 내부 변수를 적절하게 초기화하도록 허용해야 합니다. 예를 들어 Init 호출을 사용하여 인디케이터 또는 파일 핸들을 만들 수 있습니다.

//+------------------------------------------------------------------+
//| Appends new row to the end of the table                          |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

Update 메소드는 좀 더 복잡합니다. 패널을 다시 그리는 데 사용됩니다.

기본적으로 다음과 같은 세 부분으로 구성됩니다.

  • 첫 번째 열 그리기 (행 이름)
  • 첫 번째 행 그리기 (선택한 모드에 따라 시간 프레임 또는 기호 이름)
  • 내부 셀 그리기 (구성 요소 값)

모든 구성 요소가 제공된 기호 및 기간을 기반으로 자체 값을 계산하도록 요청한다는 점은 주목할 가치가 있습니다. 또한 구성 요소가 사용할 글꼴과 색상을 결정하도록 합니다.

//+------------------------------------------------------------------+
//| Redraws the table                                                |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // number of rows
   int columns;              // number of columns

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- draw first column (names of rows)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- note: we ask row object to return its name
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- draws first row (names of timeframes or currency pairs)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- draws inside table cells
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- in multi-timeframe mode use current symbol and different timeframes
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- in multi-currency mode use current timeframe and different symbols
            tf=Period();
            symbol=symbols[x];
           }

         //--- note: we ask row object to return its font, 
         //--- color and current calculated value for given timeframe and symbol
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- forces chart to redraw
   ChartRedraw();
  }

DrawLabel 메소드는 패널의 지정된 셀에 텍스트 레이블을 그리는 데 사용됩니다. 먼저 이 셀의 레이블이 이미 존재하는지 확인합니다. 그렇지 않은 경우 새로 생성됩니다.

그런 다음 필요한 모든 레이블 속성과 텍스트를 설정합니다.

//+------------------------------------------------------------------+
//| Draws text label in the specified cell of the table              |
//+------------------------------------------------------------------+  
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- create unique name for this cell
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- create label
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- set label properties
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- set label text
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

다른 방법은 매우 간단하고 덜 중요하므로 여기서는 설명하지 않습니다. 전체 코드는 글 하단에서 다운로드 할 수 있습니다.


4. CRow 확장

CRow는 패널에서 사용할 수있는 모든 구성 요소의 기본 클래스입니다.

CRow 클래스의 코드를 살펴 보겠습니다

//+------------------------------------------------------------------+
//| CRow class                                                       |
//+------------------------------------------------------------------+
//| Base class for creating custom table rows                        |
//| one or more methods of CRow should be overriden                  |
//| when creating own table rows                                     |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- default initialization method
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- default method for obtaining string value to display in the table cell
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- default method for obtaining color for table cell
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- default method for obtaining row name
   virtual string GetName() { return("-"); }

   //--- default method for obtaining font for table cell
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

CObject 만 CList 구조에 저장 될 수 있기 때문에 CObject를 확장합니다. 거의 비어있는 다섯 가지 방법이 있습니다. 더 정확하게 말하면 대부분은 기본값만 반환된다는 뜻이죠. 이러한 메소드는 CRow를 확장 할 때 재정의되도록 설계되었습니다. 저희는 그들 모두를 재정의 할 필요가 없으며 저희가 원하는 것만 재정의 할 필요가 있습니다.

예를 들어 가능한 가장 간단한 패널 구성 요소인 현재 입찰 가격 구성 요소를 만들어 보겠습니다. 다중 통화 모드에서 다양한 상품의 현재 가격을 표시하는 데 사용할 수 있습니다.

이를 위해 다음과 같은 CPriceRow 클래스를 만듭니다

//+------------------------------------------------------------------+
//| CPriceRow class                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

  };
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- gets current price
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

여기서 재정의하기로 선택한 메소드는 GetValue 및 GetName입니다. GetName은 패널의 첫 번째 열에 표시 될 이 행의 이름을 반환합니다. GetValue는 지정된 기호에 대한 최신 틱을 가져오고 최신 입찰 가격을 반환합니다. 그게 저희에게 필요한 전부입니다.

이것은 아주 간단했습니다. 뭔가 다른 걸 해봅시다. 이제 현재 RSI 값을 보여주는 구성 요소를 빌드합니다.

코드는 이전 코드와 유사합니다

//+------------------------------------------------------------------+
//| CRSIRow class                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // RSI period
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of RSI handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CRSIRow(int period);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets RSI handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- gets RSI indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets current RSI value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

여기에 몇 가지 새로운 방법이 있습니다. 생성자는 RSI 기간을 제공하고 이를 멤버 변수로 저장합니다. Init 메소드는 RSI 인디케이터 핸들을 만드는데 사용됩니다. 이러한 핸들은 handles[] 배열에 저장됩니다. GetValue 메소드는 RSI 버퍼에서 마지막 값을 복사하여 반환합니다. private GetHandle 메소드는 handles[] 배열에서 적절한 인디케이터 핸들을 찾는 데 사용됩니다. GetName은 따로 설명이 필요 없습니다.

보시다시피 패널 구성 요소를 만드는 것은 매우 쉽습니다. 같은 방식으로 거의 모든 사용자 지정 조건에 대한 구성 요소를 만들 수 있습니다. 지표 값일 필요는 없습니다. 아래에서는 SMA를 기반으로 한 맞춤 조건을 제시합니다. 현재 가격이 이동 평균 이상인지 확인하고 '예' 또는 '아니오'를 표시합니다.

//+------------------------------------------------------------------+
//| CPriceMARow class                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // period of moving average
   int               maShift;  // shift of moving average
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA or LWMA
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of MA handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- overrides default GetValue(..) method of CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // overrides default GetName() method CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| CPriceMARow class constructor                                    |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets MA handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method of CRow                    |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- obtains MA indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets the last MA value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- gets the last price
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- checking the condition: price > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method of CRow                       |
//+------------------------------------------------------------------+
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

이동 평균에는 기간, 시프트 및 유형의 세 가지 매개 변수가 있기 때문에 코드가 더 깁니다. GetName은 MA 유형 및 기간을 기반으로 이름을 작성하므로 조금 더 복잡합니다. GetValue는 CRSIRow의 경우와 거의 동일한 방식으로 작동하지만 인디케이터 값을 반환하는 대신 가격이 SMA보다 높으면 '예'를 반환하고 그보다 낮으면 '아니오'를 반환합니다.

마지막 예는 좀 더 복잡합니다. 현재 바의 가격 변동을 보여주는 CPriceChangeRow 클래스입니다. 세 가지 모드로 작동합니다

  • 화살표 표시 (녹색 위쪽 또는 빨간색 아래쪽);
  • 가격 변동을 값으로 표시 (녹색 또는 빨간색);
  • 가격 변동을 백분율로 표시 (녹색 또는 빨간색).

코드는 다음과 같습니다

//+------------------------------------------------------------------+
//| CPriceChangeRow class                                            |
//+------------------------------------------------------------------+
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- constructor
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default GetFont() method from CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetColor(..) method from CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| CPriceChangeRow class constructor                                |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Overrides default GetFont() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- we use Wingdings font to draw arrows (up/down)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- current bar price change
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // returns up arrow code
      if(change < 0) return(CharToString(234)); // returns down arrow code
      return(" ");
        }else{
      if(percentChange)
        {
         //--- calculates percent change
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Overrides default GetColor(..) method from CRow                  |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

생성자에는 두 개의 매개 변수가 있습니다. 먼저 화살표 표시 여부를 결정합니다. 참이면 두 번째 매개 변수가 삭제됩니다. 거짓이면 두 번째 매개 변수는 퍼센트 변경을 표시할지 아니면 가격 변경만 표시 할지를 결정합니다.

이 클래스에서는 GetName, GetValue, GetColor 및 GetFont의 네 가지 CRow 메소드를 재정의하기로 결정했습니다. GetName은 가장 단순하며 이름만 반환합니다. GetFont는 Wingdings 글꼴에서 화살표 또는 기타 문자를 표시 할 수 있기 때문에 사용됩니다. GetColor는 가격이 상승하면 라임색을, 하락하면 빨간색을 반환합니다. 흰색이 제자리에 머무르거나 오류가 발생하면 반환됩니다. GetValue는 마지막 바의 시가 및 종가를 가져와서 차이를 계산하여 반환합니다. 화살표 모드에서는 위쪽 및 아래쪽 화살표의 Wingdings 문자 코드를 반환합니다.


5. 모든 것을 사용하는 방법

패널을 사용하려면 새 인디케이터를 만들어야 합니다. 이것을 TableSample이라고 합시다.

처리해야 하는 이벤트는 다음과 같습니다

OnInit()에서 동적으로 생성될 CTable 개체에 대한 포인터도 필요합니다. 우선 사용할 모드 (다중 시간 프레임 또는 다중 통화)를 결정해야 합니다. 아래 코드 샘플은 다중 통화 모드를 보여 주지만 다중 시간 프레임 모드에 필요한 정보도 여기에 주석으로 표시됩니다. 다중 통화 모드의 경우 기호 배열을 만들고 이를 CTable 생성자에 전달해야 합니다. 다중 시간 프레임 모드의 경우 시간 프레임 배열을 만들고 두 번째 CTable 생성자에 전달합니다.

그런 다음 필요한 모든 구성 요소를 만들고 AddRow 메소드를 사용하여 패널에 추가해야 합니다. 선택적으로 패널 매개 변수를 조정할 수 있습니다. 결국 패널을 처음으로 그려야하므로 OnInit() 끝에서 Update를 호출합니다. OnDeinit는 간단합니다. CTable 개체를 삭제하는 것은 CTable 소멸자가 호출되는 원인입니다.

OnCalculate(...)OnChartEvent(...) 은 동일합니다. Update 메소드만 호출합니다. OnChartEvent(...) 는 패널이 다중 통화 모드에서 작동하는 경우에만 필요합니다. 이 모드에서는 SpyAgent에서 발생한 이벤트를 처리합니다. 다중 타임 프레임 모드에서는 현재 차트의 심볼만 모니터링 해야하므로 OnCalculate(...)만 필요합니다.

//+------------------------------------------------------------------+
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // pointer to CTable object
//+------------------------------------------------------------------+
//| Indicator initialization function                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- timeframes used in table (in multi-timeframe mode)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- symbols used in table (in multi-currency mode)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- CTable object creation 
//   table = new CTable(timeframes); // multi-timeframe mode
   table=new CTable(symbols); // multi-currency mode

//--- adding rows to the table
   table.AddRow(new CPriceRow());               // shows current price
   table.AddRow(new CPriceChangeRow(false));     // shows change of price in the last bar
   table.AddRow(new CPriceChangeRow(false,true)); // shows percent change of price in the last bar
   table.AddRow(new CPriceChangeRow(true));      // shows change of price as arrows
   table.AddRow(new CRSIRow(14));                // shows RSI(14)
   table.AddRow(new CRSIRow(10));                // shows RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // shows if SMA(20) > current price

//--- setting table parameters
   table.SetFont("Arial",10,clrYellow);  // font, size, color
   table.SetCellSize(60, 20);           // width, height
   table.SetDistance(10, 10);           // distance from upper right chart corner

   table.Update(); // forces table to redraw

   return(0);
  }
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- calls table destructor and frees memory
   delete(table);
  }
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- update table: recalculate/repaint
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnChartEvent handler                                             |
//| Handles CHARTEVENT_CUSTOM events sent by SpyAgent indicators     |
//| nedeed only in multi-currency mode!                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // update table: recalculate/repaint
  }
//+------------------------------------------------------------------+

이 지표를 차트에 연결하면 업데이트가 시작되고 마침내 패널이 작동하는 것을 볼 수 있습니다.


6. 설치

모든 파일을 컴파일해야 합니다. SpyAgent 및 TableSample은 인디케이터이며 terminal_data_folder\MQL5\Indicators에 복사해야 합니다. 나머지 파일은 포함 파일이며 terminal_data_folder\MQL5\Include 안에 있어야 합니다. 패널을 실행하려면 TableSample 인디케이터를 차트에 연결하십시오. SpyAgent를 연결할 필요가 없습니다. 자동으로 실행됩니다.


결론

이 글은 MetaTrader 5에 대한 다중 시간 프레임 및 다중 통화 패널의 객체 지향 구현을 제공합니다. 쉽게 확장 할 수 있고 적은 노력으로 맞춤형 패널을 만들 수있는 디자인을 달성하는 방법을 보여줍니다.

이 글에 제시된 모든 코드는 아래에서 다운로드 할 수 있습니다.


MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/357

세마포어 인디케이터를 사용하는 간단한 거래 시스템 세마포어 인디케이터를 사용하는 간단한 거래 시스템
복잡한 거래 시스템을 철저히 살펴보면 일련의 간단한 거래 신호를 기반으로 한다는 것을 알 수 있습니다. 따라서 초보 개발자가 복잡한 알고리즘 작성을 즉시 시작할 필요가 없습니다. 이 글은 거래를 수행하기 위해 세마포어 인디케이터를 사용하는 거래 시스템의 예를 제공합니다.
객체 지향 프로그래밍의 기초 객체 지향 프로그래밍의 기초
객체 지향 프로그래밍 (OOP)을 사용하기 위해 다형성, 캡슐화 등이 무엇인지 알 필요가 없습니다. 단순히 이러한 기능을 사용할 수 있습니다. 이 글에서는 실습 예제를 통해 OOP의 기본 사항을 다룹니다.
6 단계로 나만의 거래 로봇을 만드세요! 6 단계로 나만의 거래 로봇을 만드세요!
거래 클래스가 어떻게 구성되는지 모르고 "객체 지향 프로그래밍" 이라는 단어가 두렵다면 이 글이 당신에게 딱입니다. 사실, 거래 신호 모듈을 작성하기 위해 세부 사항을 알 필요가 없습니다. 몇 가지 간단한 규칙을 따르십시오. 나머지는 모두 MQL5 마법사가 수행하고 즉시 사용 가능한 거래 로봇을 얻게 됩니다!
MQL5에서 자신 만의 그래픽 패널 만들기 MQL5에서 자신 만의 그래픽 패널 만들기
MQL5 프로그램의 유용성은 풍부한 기능과 정교한 그래픽 사용자 인터페이스에 의해 결정됩니다. 빠르고 안정적인 작동보다 시각적인식이 때때로 더 중요합니다. 다음은 표준 라이브러리 클래스를 기반으로 디스플레이 패널을 만드는 방법에 대한 단계별 가이드입니다.