다중 시간대 및 다중 통화 패널 구축을 위한 객체 지향 접근 방식
소개
이 문서에서는 MetaTrader 5에 대한 다중 시간 프레임 및 다중 통화 패널을 만드는데 객체 지향 프로그래밍을 사용하는 방법을 설명합니다. 주요 목표는 패널 자체의 코드를 수정할 필요 없이 가격, 가격 변동, 지표 값 또는 맞춤형 구매/판매 조건과 같은 다양한 종류의 데이터를 표시하는 데 사용할 수 있는 범용 패널을 구축하는 것입니다. 이렇게 하면 필요한 방식으로 패널을 맞춤 설정하는데 코딩이 거의 필요하지 않습니다.
설명할 솔루션은 두 가지 모드로 작동합니다.
- 다중 타임 프레임 모드 - 현재 심볼에서 계산 된 테이블 내용을 다른 타임 프레임에서 볼 수 있습니다.
- 다중 통화 모드 - 현재 시간 프레임에서 계산 된 표 내용을 다른 기호로 볼 수 있습니다.
다음 그림은 이 두 가지 모드의 패널을 보여줍니다.
첫 번째는 다중 시간 프레임 모드에서 작동하며 다음 데이터를 표시합니다.
- 현재 가격;
- 현재 바의 가격 변동;
- 퍼센트로 나타낸 현재 바의 가격 변동;
- 화살표로 표시되는 현재 바의 가격 변동 (위/아래);
- RSI(14) 인디케이터 값;
- RSI(10) 인디케이터 값;
- 맞춤 조건: SMA (20) > 현재 가격.
그림 1. 다중 시간 프레임 모드
두 번째는 다중 통화 모드에서 작동하며 다음을 보여줍니다
- 현재 가격;
- 현재 바의 가격 변동;
- 퍼센트로 나타낸 현재 바의 가격 변동;
- 현재 바의 가격이 화살표로 변경됩니다.
- RSI(10) 인디케이터 값;
- RSI(14) 인디케이터 값;
- 맞춤 조건: SMA (20) > 현재 가격.
그림 2. 다중 통화 모드
1. 이행
다음 클래스 다이어그램은 패널의 구현 설계를 설명합니다.
그림 3. 패널의 클래스 다이어그램
다이어그램의 요소를 설명하겠습니다
- CTable. 패널의 핵심 클래스. 패널 그리기 및 구성 요소 관리를 담당합니다.
- SpyAgent. 다른 기호 (기기)에 대한 '스파이'를 담당하는 인디케이터입니다. 모든 에이전트가 생성되어 다른 심볼로 전송됩니다. 에이전트는 새 틱이 심볼 차트에 도착하면 OnCalculate 이벤트에 반응하고 CHARTEVENT_CUSTOM 이벤트를 전송하여 업데이트 해야 함을 CTable 개체에 알립니다. 이 접근 방식의 전체 아이디어는 "MetaTrader 5에서 다중 통화 모드 구현" 글을 기반으로 합니다. 여기에서 모든 기술 세부 정보를 찾을 수 있습니다.
- CRow. 패널을 만드는 데 사용되는 모든 인디케이터 및 조건의 기본 클래스입니다. 이 클래스를 확장하면 패널에 필요한 모든 구성 요소를 만들 수 있습니다.
- CPriceRow. 현재 입찰 가격을 표시하는 데 사용되는 CRow를 확장하는 간단한 클래스입니다.
- CPriceChangeRow. 현재 바의 가격 변동을 표시하는데 사용되는 CRow를 확장하는 클래스입니다. 가격 변동, 백분율 변동 또는 화살표를 표시할 수 있습니다.
- CRSIRow. 현재 RSI 인디케이터 값을 표시하는 데 사용되는 CRow를 확장하는 클래스입니다.
- 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();
- OnDeinit();
- OnCalculate(...);
- OnChartEvent(...) (다중 통화 모드를 사용하는 경우에만)
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