English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
구글 차트 API와 표준 라이브러리 클래스를 이용하여 정보 보드 만들기

구글 차트 API와 표준 라이브러리 클래스를 이용하여 정보 보드 만들기

MetaTrader 5 | 5 7월 2021, 10:21
94 0
Евгений
Евгений


시작하며

MQL5 언어의 프로그래머들의 삶을 풍요롭게 하기 위해, 디자이너들은 거의 모든 API MQL5 기능을 다루는 표준 라이브러리를 만들었고, 그들과 함께 작업하는 것을 훨씬 더 쉽고 편리하게 만들었습니다. 이 자료에서는 표준 라이브러리에서 사용하는 최대 클래스 수가 포함된 정보 게시판을 만들려고 합니다.


1. 표준 라이브러리 클래스 개요

이 라이브러리는 그래서 무엇인가? 홈페이지의 문서 파트는 이하로 구성되어있습니다:

모든 클래스의 코드를 포함하는 파일은 MQL5/Include 폴더에 있습니다. 라이브러리 코드를 볼 때 해당 코드는 클래스만 제공하고 기능은 제공하지 않는다는 것을 알 수 있습니다. 따라서 이를 사용하려면 객체 지향 프로그래밍(OOP)에 대한 지식이 어느정도는 있어야 합니다.  

모든 라이브러리 클래스(거래 클래스 제외)는 기본 클래스 CObject에 기원을 두고 있습니다. 기본 클래스와 그 상속자들이 다 준비되어 있으니 - 지금부터 클래스 다이어그램을 만들어 볼 것입니다. MQL5 언어는 기본적으로 C++의 부분 집합이므로, IBM Rational Rose instrument를 사용하여 C++ 프로젝트의 역엔지니어링을 위한 도구를 다이어그램의 자동 구성에 사용해봅시다.

 

1번 그림. 표준 라이브러리 클래스 다이어그램

다이어그램이 많이 번잡해질 우려가 있기 때문에 클래스 특성과 메소드는 생략하겠습니다. 집계또한 중요치 않기 때문에 생략하겠습니다. 결과적으로, 우리는 클래스가 어떤 속성과 메소드를 얻을 수 있는 일반화(상속화)만 남게 됩니다.

본 다이어그램에서 볼 수 있는 것처럼 라이브러리에서 선, 파일, 차트, 그래픽 객체, 어레이를 다루는 컴포넌트는 CObject에서 상속받은 각자 고유의 기본 클래스 (각각, CString, CFile, CChart, CChartObject and CArray)를 지니고 있습니다. 인디케이터를 다루는 기본 클래스인 CIndicator 와 그 보조 CIndicators 클래스는 CArrayObj 에서 상속되었으며, 인디케이터 버퍼 클래스 CIndicatorBuffer 의 액세스는 CArrayDouble에서 상속되었습니다.

다이어그램에 선홍색으로 표시된 부분은 실제 클래스, 표시기, 배열 및 ChartObjects에 존재하지 않는 것을 표시하는데, 이는 표시기, 어레이 및 그래픽 객체로 작업하기 위한 클래스가 포함된 세트입니다. 그 수가 많고, 하나의 부모로부터 상속받은 것이기 때문에, 도표가 너무 지저분해지지 않도록 약간 간략하게 처리했습니다. 예를들어 이 인디케이터는 CiDEMA, CiStdDev, 등을 포함합니다.

Doxygen 문서화 시스템의 자동 생성을 사용하여 클래스 다이어그램을 구성할 수 있다는 점도 주목할 필요가 있습니다. Rational Rose에서 하는 것보다 이 시스템으로 하는 편이 좀 쉽네요. Doxygen에 대해 더알아보고 싶으시다면 MQL5 코드를 위한 자동생성 다큐멘테이션 문서를 참조하실 수 있습니다.


2. 문제점

표준 라이브러리 클래스의 최대 수를 가진 정보 테이블을 만들어 봅시다.

보드에 표시되는 내용은 무엇입니까? MetaTrader 5의 상세 보고서와 같은 것, 즉:

2번 그림. 상세 보고서의 외관

2번 그림. 상세 보고서의 외관

우리가 볼 수 있듯이, 그 보고서에는 잔액 차트와 몇몇 거래 수치가 표시됩니다. 이들 인디케이터를 계산하기 위한 메소드들에 대한 추가 정보는 What the Numbers in the Expert Testing Report Mean에서 확인할 수 있습니다.

이 보드는 순전히 정보 목적으로만 사용되며 어떠한 거래 작업도 수행하지 않기 때문에, 실제 차트를 닫는 것을 피하기 위해 별도의 창에서 인디케이터로 구현하는 것이 가장 좋을 것입니다. 또한 하위 창에 배치하면 쉽게 확장할 수 있고 마우스 한 번의 동작으로 보드를 닫을 수도 있습니다.

또한 전체 매매 수에 비해 기기에서 수행된 매매 수를 표시하는 파이 차트로 보고서를 보완할 수도 있습니다.


3. 인터페이스 디자인하기 

지금까지 목표를 정했습니다 - 메인 차트의 하위 창에 상세한 보고서가 있어야할 것입니다.

정보 보드를 클래스로 구현해봅시다. 시작해보죠:

//+------------------------------------------------------------------+
///The Board class
//+------------------------------------------------------------------+
class Board
  {
//protected data
protected:
///number of the sub-window where the board will be stored
   int               wnd;             
///array with the deals data   
   CArrayObj        *Data;
///array with the balance data   
   CArrayDouble      ChartData;       
///array with elements of the interface   
   CChartObjectEdit  cells[10][6];    
///object for working with the chart   
   CChart            Chart;           
///object for working with the balance chart
   CChartObjectBmpLabel BalanceChart; 
///object for working with the pie chart
   CChartObjectBmpLabel PieChart;     
///data for the pie chart
   PieData          *pie_data;
//private data and methods
private:
   double            net_profit;      //these variables will store the calculated characteristics
   double            gross_profit;
   double            gross_loss;
   double            profit_factor;
   double            expected_payoff;
   double            absolute_drawdown;
   double            maximal_drawdown;
   double            maximal_drawdown_pp;
   double            relative_drawdown;
   double            relative_drawdown_pp;
   int               total;
   int               short_positions;
   double            short_positions_won;
   int               long_positions;
   double            long_positions_won;
   int               profit_trades;
   double            profit_trades_pp;
   int               loss_trades;
   double            loss_trades_pp;
   double            largest_profit_trade;
   double            largest_loss_trade;
   double            average_profit_trade;
   double            average_loss_trade;
   int               maximum_consecutive_wins;
   double            maximum_consecutive_wins_usd;
   int               maximum_consecutive_losses;
   double            maximum_consecutive_losses_usd;
   int               maximum_consecutive_profit;
   double            maximum_consecutive_profit_usd;
   int               maximum_consecutive_loss;
   double            maximum_consecutive_loss_usd;
   int               average_consecutive_wins;
   int               average_consecutive_losses;

   ///method of obtaining data about the deals and the balance
   void              GetData();

   ///method of calculating the characteristics
   void              Calculate();
   
   ///method of chart construction
   void              GetChart(int X_size,int Y_size,string request,string file_name);
   
   ///method of request to Google Charts API
   string            CreateGoogleRequest(int X_size,int Y_size,bool type);
   
  ///method of obtaining the optimum font size
   int               GetFontSize(int x,int y);
   string            colors[12];  //array with text presentation of colors
//public methods
public:
///constructor
   void              Board();    
///destructor         
   void             ~Board();    
///method for board update
   void              Refresh();  
///method for creating interface elements   
   void              CreateInterface(); 
  };

보호된 클래스 데이터는 인터페이스 요소 및 거래, 균형 및 파이 차트 데이터입니다(PieData 클래스는 아래에서 설명). 거래 인디케이터와 일부 방법은 비공개입니다. 사용자가 직접 접근해서는 안 되고, 클래스 내에서 계산되어야하며, 적절한 퍼블릭 메소드를 호출해야만 계산되기 때문에 때문에 프라이빗입니다.

또한 인터페이스 생성 및 인디케이터 계산 메소드는 프라이빗입니다. 여기서는 일련의 엄격한 메소드 콜을 견뎌야 하기 때문입니다. 예를 들어, 계산을 위한 데이터가 없는 상태로 인디케이터를 계산하거나 인터페이스를 미리 만들지 않고 업데이트할 수는 없습니다. 따라서 유저가 "자폭하는 것"을 미연에 방지합니다. 

한 클래스의 생성자와 소멸자를 즉시 처리하도록 합니다. 그러면 나중에 해당 클래스로 돌아갈 필요가 없습니다.

//+------------------------------------------------------------------+
///Constructor
//+------------------------------------------------------------------+
void Board::Board()
  {
   Chart.Attach();                               //attach the current chart to the class instance
   wnd=ChartWindowFind(Chart.ChartId(),"IT");    //find the indicator window
   Data = new CArrayObj;                         //creating the CArrayObj class instance
   pie_data=new PieData;                         //creating the PieData class instance
   //fill colors array
   colors[0]="003366"; colors[1]="00FF66"; colors[2]="990066";
   colors[3]="FFFF33"; colors[4]="FF0099"; colors[5]="CC00FF";
   colors[6]="990000"; colors[7]="3300CC"; colors[8]="000033";
   colors[9]="FFCCFF"; colors[10]="CC6633"; colors[11]="FF0000";
  }
//+------------------------------------------------------------------+
///Destructor
//+------------------------------------------------------------------+
void Board::~Board()
  {
   if(CheckPointer(Data)!=POINTER_INVALID) delete Data;   //delete the deals data
   if(CheckPointer(pie_data)!=POINTER_INVALID) delete pie_data;
   ChartData.Shutdown();    //and balance data
   Chart.Detach();          //detach from the chart
   for(int i=0;i<10;i++)    //delete all interface elements
      for(int j=0;j<6;j++)
         cells[i][j].Delete();
   BalanceChart.Delete();   //delete the balance chart
   PieChart.Delete();       //and pie chart
  }

생성자 내에서 Attach() 메소드의 도움을 받아 CChart 타입의 객체를 현재의 차트에 연결시킬 것입니다. 소멸자라고 불리는 Detach() 메소드는 차트와 객체 간의 연결을 끊을 것입니다. Data object, which is a pointer to an object of CArrayObj 타입 객체를 가리키는 포인터인 데이터 객체는 객체의 주소를 받아 능동적으로 오퍼레이션으로 생성 된 후 소멸자 내에서 삭제 오페라토를 이용하여 삭제됩니다. 삭제 전에 CheckPointer()를 이용하여객체가 존재하는지 확인하는 것을 잊지 마세요. 그렇지 않으면 에러가 발생할 것입니다.

CArrayObj 클래스에 대한 보다 자세한 정보 는 나중에 다시 나올 것입니다. CArrayDouble 클래스의 Shutdown() 메소드는 CArray 클래스 (클래스 다이어그램 참조) 에서 계승되는 다른 메소드들하고 다를 바 없이 객체가 차지하고있던 메모리를 비웁니다. CChartObject 클래스의 Delete() 메소드는 차트에서 객체를 삭제합니다.

따라서 생성자는 메모리를 할당하고 소멸자는 메모리를 해제하고 클래스에 의해 생성된 그래픽 개체를 제거합니다. 

이제 인터페이스를 살펴보겠습니다. 위에서 설명한 대로 CreateInterface() 메소드는 보드의 인터페이스를 생성합니다.

//+------------------------------------------------------------------+
///CreateInterface function
//+------------------------------------------------------------------+
void Board::CreateInterface()
  {
   //retrieve the width
   int x_size=Chart.WidthInPixels();
   //and the height of the indicator window
   int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd);
   
    //calculate, how much space will the balance chart take up
   double chart_border=y_size*(1.0-(Chart_ratio/100.0));

   if(Chart_ratio<100)//if the balance chart is taking up the entire table
     {
      for(int i=0;i<10;i++)//create columns
        {
         for(int j=0;j<6;j++)//and rows
           {
            cells[i][j].Create(Chart.ChartId(),"InfBoard "+IntegerToString(i)+" "+IntegerToString(j),
                               wnd,j*(x_size/6.0),i*(chart_border/10.0),x_size/6.0,chart_border/10.0);
            //set selectable property to false
            cells[i][j].Selectable(false);
            //set text as read only
            cells[i][j].ReadOnly(true);
            //set font size
            cells[i][j].FontSize(GetFontSize(x_size/6.0, chart_border/10.0));
            cells[i][j].Font("Arial");    //font name
            cells[i][j].Color(text_color);//font color
           }
        }
     }

   if(Chart_ratio>0)//if the balance chart is required
     {
      //create a balance chart
      BalanceChart.Create(Chart.ChartId(), "InfBoard chart", wnd, 0, chart_border);
      //set selectable property to false
      BalanceChart.Selectable(false);
      //create a pie chart
      PieChart.Create(Chart.ChartId(), "InfBoard pie_chart", wnd, x_size*0.75, chart_border);
      PieChart.Selectable(false);//set selectable property to false
     }

   Refresh();//refresh the board
  }

모든 요소를 촘촘하게 배열하려면 먼저 CChart 클래스의 WidthInPixels() GetInteger() 메소드를 사용하여 보드가 놓인 인디케이터 하위 창의 길이와 너비를 알아냅니다. 그 후 CChartObjectEdit 클래스 ("입력 창" 생성) 의 Create() 메소드를 이용하여 인디케이터의 값을 포함하는 셀을 만듭니다. 모든 자손이 CChartObject클래스의 본 메소드를 지니고 있습니다.

이 유형의 작업에 표준 라이브러리 를 사용하는 것이 얼마나 편리한지 알아두세요. 그것이 없다면, 우리는 ObjectCreate 함수를 사용하여 각 객체를 생성하고, ObjectSet과 같은 함수를 사용하여 객체의 속성을 설정해야 할 것이며, 이는 코드 이중화로 이어질 것입니다. 그리고 나중에 객체의 속성을 변경하고 싶을 때 혼란을 피하기 위해 객체의 이름을 신중하게 제어할 필요가 있습니다. 이제 우리는 단지 일련의 그래픽 객체를 만들어 우리가 원하는 대로 훑어볼 수 있습니다.

또한 CChartObject 클래스의 Color() 메소드처럼 클래스의 크리에이터가 오버로드된 경우 하나의 함수를 사용하여 개체의 속성을 가져오거나 설정할 수 있습니다. 패러미터를 사용하여 호출하면 패러미터 없이 해당 패러미터를 설정합니다. 객체 색을 반환합니다. 파이 차트를 밸런스 차트 옆에 두면 전체 화면 너비의 1/4을 차지하게 됩니다.

새로 고침 method()가 보드를 업데이트합니다. 이 업데이트는 무엇으로 구성됩니까? 인디케이터를 세어 그래픽 객체에 입력하고 보드의 크기를 조정해야 합니다. 만약 인디케이터가 위치한 창의 크기가 변경되었다면 말이죠. 보드가 창의 빈 공간 전체를 차지해야 합니다.

//+------------------------------------------------------------------+
///Function of the board updating
//+------------------------------------------------------------------+
void Board::Refresh()
  {
   //check the server connection status
   if(!TerminalInfoInteger(TERMINAL_CONNECTED)) {Alert("No connection with the trading server!"); return;}
   //check the permission for importing functions from DLL
   if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Alert("DLLs are prohibited!"); return;}
   //calculate the characteristics
   Calculate();
   //retrieve the width
   int x_size=Chart.WidthInPixels();
   //and the height of the indicator window
   int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd);
   //calculate how much space the balance chart will take up
   double chart_border=y_size*(1.0-(Chart_ratio/100.0));

   string captions[10][6]= //array with signatures of interface elements
     {
        {"Total Net Profit:"," ","Gross Profit:"," ","Gross Loss:"," "},
        {"Profit Factor:"," ","Expected Payoff:"," ","",""},
        {"Absolute Drawdown:"," ","Maximal Drawdown:"," ","Relative Drawdown:"," "},
        {"Total Trades:"," ","Short Positions (won %):"," ","Long Positions (won %):"," "},
        {"","","Profit Trades (% of total):"," ","Loss trades (% of total):"," "},
        {"Largest","","profit trade:"," ","loss trade:"," "},
        {"Average","","profit trade:"," ","loss trade:"," "},
        {"Maximum","","consecutive wins ($):"," ","consecutive losses ($):"," "},
        {"Maximal","","consecutive profit (count):"," ","consecutive loss (count):"," "},
        {"Average","","consecutive wins:"," ","consecutive losses:"," "}
     };

   //put the calculated characteristics into the array
   captions[0][1]=DoubleToString(net_profit, 2);
   captions[0][3]=DoubleToString(gross_profit, 2);
   captions[0][5]=DoubleToString(gross_loss, 2);

   captions[1][1]=DoubleToString(profit_factor, 2);
   captions[1][3]=DoubleToString(expected_payoff, 2);

   captions[2][1]=DoubleToString(absolute_drawdown, 2);
   captions[2][3]=DoubleToString(maximal_drawdown, 2)+"("+DoubleToString(maximal_drawdown_pp, 2)+"%)";
   captions[2][5]=DoubleToString(relative_drawdown_pp, 2)+"%("+DoubleToString(relative_drawdown, 2)+")";

   captions[3][1]=IntegerToString(total);
   captions[3][3]=IntegerToString(short_positions)+"("+DoubleToString(short_positions_won, 2)+"%)";
   captions[3][5]=IntegerToString(long_positions)+"("+DoubleToString(long_positions_won, 2)+"%)";

   captions[4][3]=IntegerToString(profit_trades)+"("+DoubleToString(profit_trades_pp, 2)+"%)";
   captions[4][5]=IntegerToString(loss_trades)+"("+DoubleToString(loss_trades_pp, 2)+"%)";

   captions[5][3]=DoubleToString(largest_profit_trade, 2);
   captions[5][5]=DoubleToString(largest_loss_trade, 2);

   captions[6][3]=DoubleToString(average_profit_trade, 2);
   captions[6][5]=DoubleToString(average_loss_trade, 2);

   captions[7][3]=IntegerToString(maximum_consecutive_wins)+"("+DoubleToString(maximum_consecutive_wins_usd, 2)+")";
   captions[7][5]=IntegerToString(maximum_consecutive_losses)+"("+DoubleToString(maximum_consecutive_losses_usd, 2)+")";

   captions[8][3]=DoubleToString(maximum_consecutive_profit_usd, 2)+"("+IntegerToString(maximum_consecutive_profit)+")";
   captions[8][5]=DoubleToString(maximum_consecutive_loss_usd, 2)+"("+IntegerToString(maximum_consecutive_loss)+")";

   captions[9][3]=IntegerToString(average_consecutive_wins);
   captions[9][5]=IntegerToString(average_consecutive_losses);

   if(Chart_ratio<100) //if the balance chart doesn't take up the entire table
     {
      for(int i=0;i<10;i++) //go through the interface elements
        {
         for(int j=0;j<6;j++)
           {
            //specify the position
            cells[i][j].X_Distance(j*(x_size/6.0));
            cells[i][j].Y_Distance(i*(chart_border/10.0));
            //the size
            cells[i][j].X_Size(x_size/6.0);
            cells[i][j].Y_Size(chart_border/10.0);
            //the text
            cells[i][j].SetString(OBJPROP_TEXT,captions[i][j]);
            //and font size
            cells[i][j].FontSize(GetFontSize(x_size/6.0,chart_border/10.0));
           }
        }
     }

   if(Chart_ratio>0)//if the balance chart is required
     {
      //refresh the balance chart
      int X=x_size*0.75,Y=y_size-chart_border;
      //get the chart
      GetChart(X,Y,CreateGoogleRequest(X,Y,true),"board_balance_chart");
      //set its position
      BalanceChart.Y_Distance(chart_border);
      //specify file names
      BalanceChart.BmpFileOn("board_balance_chart.bmp");
      BalanceChart.BmpFileOff("board_balance_chart.bmp");
      //refresh the pie chart
      X=x_size*0.25;
      //get the chart
      GetChart(X,Y,CreateGoogleRequest(X,Y,false),"pie_chart");
      //set its new position
      PieChart.Y_Distance(chart_border);
      PieChart.X_Distance(x_size*0.75);
      //specify file names
      PieChart.BmpFileOn("pie_chart.bmp");
      PieChart.BmpFileOff("pie_chart.bmp");
     }

   ChartRedraw(); //redraw the chart
  }

CreateInterface() 방법과 유사한 코드가 많이 있으며, 먼저 Calculate() 함수가 표시기를 계산한 다음 그래픽 객체에 입력되며, 동시에 개체 크기가 X_Size()Y_Size() 메소드를 사용하여 창의 크기에 맞게 조정됩니다. X_Distance 및 Y_Distance 메소드는 객체의 위치를 변경합니다.

GetFontSize() 함수에 유의하여 글꼴 크기를 선택합니다. 이렇게 하면 텍스트가 크기를 조정한 후 컨테이너 테두리를 "넘치게" 하지 않으며, 반대로 너무 작아지지도 않습니다.

본 함수를 좀 더 자세히 살펴보겠습니다.

//import DLL function for string metrics
#import "String_Metrics.dll" 
void GetStringMetrics(int font_size,int &X,int &Y);
#import

//+------------------------------------------------------------------+
///Function of determining the optimum font size
//+------------------------------------------------------------------+
int Board::GetFontSize(int x,int y)
  {
   int res=8;
   for(int i=15;i>=1;i--)//go through the different font sizes
     {
      int X,Y; //here we input the line metrics
      //determine the metrics
      GetStringMetrics(i,X,Y);
      //if the line fits the set borders - return the font size
      if(X<=x && Y<=y) return i;
     }
   return res;
  }

GetStringMetrics() 함수는 위에서 설명한 DLL에서 가져오며, 이 코드는 DLL_Source.zip 아카이브에서 찾을 수 있고, 또한 필요에 따라 수정할 수 있습니다. 프로젝트에서 인터페이스를 직접 설계하면 유용할 것 같습니다.

사용자 인터페이스 관련 설명은 이상이며, 이제 거래 인디케이터 계산으로 돌아갑시다.


4. 거래 인디케이터 계산

Calculate() 메소드는 계산을 수행합니다.

그러나 필요한 데이터를 수신하는 GetData() 메소드 또한 필요합니다.

//+------------------------------------------------------------------+
///Function of receiving the deals and balance data
//+------------------------------------------------------------------+
void Board::GetData()
  {
   //delete old data
   Data.Shutdown();
   ChartData.Shutdown();
   pie_data.Shutdown();
   //prepare all the deals history
   HistorySelect(0,TimeCurrent()); 
   CAccountInfo acc_inf;   //object for work with account
   //calculate the balance
   double balance=acc_inf.Balance();
   double store=0; //balance
   long_positions=0;
   short_positions=0;
   long_positions_won=0;
   short_positions_won=0;
   for(int i=0;i<HistoryDealsTotal();i++) //go through all of the deals in the history

     {
      CDealInfo deal;  //the information about the deals will be stored here
      deal.Ticket(HistoryDealGetTicket(i));//get deal ticket
      //if the trade had a financial result (exit of the market)
      if(deal.Ticket()>=0 && deal.Entry()==DEAL_ENTRY_OUT)
        {
         pie_data.Add(deal.Symbol()); //add data for the pie chart
         //check for the symbol 
         if(!For_all_symbols && deal.Symbol()!=Symbol()) continue;
         double profit=deal.Profit(); //retrieve the trade profit
         profit+=deal.Swap();         //swap
         profit+=deal.Commission();   //commission
         store+=profit;               //cumulative profit
         Data.Add(new CArrayDouble);  //add new element to the array
         ((CArrayDouble *)Data.At(Data.Total()-1)).Add(profit);  //and data
         ((CArrayDouble *)Data.At(Data.Total()-1)).Add(deal.Type());
        }
     }

   //calculate the initial deposit
   double initial_deposit=(balance-store);
   for(int i=0;i<Data.Total();i++) //go through the prepared trades
     {
      //calculate the balance value
      initial_deposit+=((CArrayDouble *)Data.At(i)).At(0);
      ChartData.Add(initial_deposit); //and put it to the array
     }
  }

먼저, 데이터를 저장하는 메소드를 생각해 봅시다. 표준 라이브러리는 어레이를 쓰지 않아도 되도록 데이터 구조 클래스들을 제공합니다. 우리는 수익과 거래 유형에 대한 과거 데이터 또한 저장하는 2차원 어레이가 필요합니다. 하지만 표준 라이브러리로는 2차원 어레이를 정리하기 위한 외적 클래스가 지원되지 않습니다. 대신에 CArrayDouble (double 타입 어레이) 및 CArrayObj 클래스 ( CObject 클래스 인스턴스와 승계자들을 가리키는 포인터의 능동 어레이) 가 있습니다. 즉, double 타입의 어레이의 어레이를 만들 수 있습니다. 정확히 그렇게 하죠. 

((CArrayDouble *) Data.At (Data.Total () - 1 )). Add (profit) 와 같은 코드는 data [i] [j] = profit 만큼 깔끔해보이진 않습니다만, 어디까지나 첫 눈에만 그런 겁니다. 결국 표준 라이브러리 클래스를 사용하지 않고 단순히 어레이를 선언함으로써 우리는 내장 메모리 관리자, 다른 어레이를 삽입할 수 있는 기능, 어레이 비교, 항목 찾기 등과 같은 이점을 잃게 됩니다. 그러므로 메모리 구성의 클래스를 사용하면 어레이의 오버플로를 제어할 필요로부터 해방되고 많은 유용한 기기를 제공할 수 있습니다.  

CArray (1번 그림 참조) 의 Total() 메소드는 어레이 내의 요소 수를 반환하며, Add() 메소드는 그것들을 합치고 At() 메소드는 요소를 반환합니다.

파이 차트를 만들기로 결정했기 때문에 기호에 대한 거래 수를 표시하려면 필요한 데이터를 수집해야 합니다.

다음 데이터를 수집하기 위한 보조 클래스를 작성합니다:

//+------------------------------------------------------------------+
///The Pie chart class
//+------------------------------------------------------------------+
class PieData
  {
protected:
///number of deals per symbol
   CArrayInt         val;   
///symbols
   CArrayString      symb;  
public:
///delete the data
   bool Shutdown()          
     {
      bool res=true;
      res&=val.Shutdown();
      res&=symb.Shutdown();
      return res;
     }
///search for a sting in the array
   int Search(string str)   
     {  //check all array elements
      for(int i=0;i<symb.Total();i++)
         if(symb.At(i)==str) return i;
      return -1;
     }
///add new data
   void Add(string str)    
     {
      int symb_pos=Search(str);//determine symbol position in the array
      if(symb_pos>-1)
         val.Update(symb_pos,val.At(symb_pos)+1);//update the deals data
      else //if there isn't such a symbol yet
        {
         symb.Add(str); //add it
         val.Add(1);
        }
     }

   int Total() const {return symb.Total();}
   int Get_val(int pos) const {return val.At(pos);}
   string Get_symb(int pos) const {return symb.At(pos);}
  };

표준 라이브러리 클래스가 항상 작업에 필요한 방법을 제공할 수 있는 것은 아닙니다. 본례에서는 CArrayString 클래스의 Search() 메소드는 적합하지 않습니다. 왜냐하면 이 클래스를 적용하려면 먼저 어레이를 분류해야 하고, 이는 데이터 구조를 위반하기 때문입니다. 그래서 우리는 우리만의 메소드를 써야합니다.

그 거래 특성의 계산은 Calculate() 메소드를 통해 이루어집니다:

//+------------------------------------------------------------------+
///Calculation of characteristics
//+------------------------------------------------------------------+
void Board::Calculate()
  {
   //get the data
   GetData();
   //zero all characteristics
   gross_profit=0;
   gross_loss=0;
   net_profit=0;
   profit_factor=0;
   expected_payoff=0;
   absolute_drawdown=0;
   maximal_drawdown_pp=0;
   maximal_drawdown=0;
   relative_drawdown=0;
   relative_drawdown_pp=0;
   total=Data.Total();
   long_positions=0;
   long_positions_won=0;
   short_positions=0;
   short_positions_won=0;
   profit_trades=0;
   profit_trades_pp=0;
   loss_trades=0;
   loss_trades_pp=0;
   largest_profit_trade=0;
   largest_loss_trade=0;
   average_profit_trade=0;
   average_loss_trade=0;
   maximum_consecutive_wins=0;
   maximum_consecutive_wins_usd=0;
   maximum_consecutive_losses=0;
   maximum_consecutive_losses_usd=0;
   maximum_consecutive_profit=0;
   maximum_consecutive_profit_usd=0;
   maximum_consecutive_loss=0;
   maximum_consecutive_loss_usd=0;
   average_consecutive_wins=0;
   average_consecutive_losses=0;

   if(total==0) return; //there isn't deals - return from the function
   double max_peak=0,min_peak=0,tmp_balance=0;
   int max_peak_pos=0,min_peak_pos=0;
   int max_cons_wins=0,max_cons_losses=0;
   double max_cons_wins_usd=0,max_cons_losses_usd=0;
   int avg_win=0,avg_loss=0,avg_win_cnt=0,avg_loss_cnt=0;

   for(int i=0; i<total; i++)
     {
      double profit=((CArrayDouble *)Data.At(i)).At(0); //get profit
      int deal_type=((CArrayDouble *)Data.At(i)).At(1); //and deal type
      switch(deal_type) //check deal type
        {
         //and calculate number of long and short positions
         case DEAL_TYPE_BUY: {long_positions++; if(profit>=0) long_positions_won++; break;}
         case DEAL_TYPE_SELL: {short_positions++; if(profit>=0) short_positions_won++; break;}
        }

      if(profit>=0)//the deal is profitable
        {
         gross_profit+=profit; //gross profit
         profit_trades++;      //number of profit deals
         //the largest profitable trade and the largest profitable series
         if(profit>largest_profit_trade) largest_profit_trade=profit;

         if(maximum_consecutive_losses<max_cons_losses || 
            (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd))
           {
            maximum_consecutive_losses=max_cons_losses;
            maximum_consecutive_losses_usd=max_cons_losses_usd;
           }
         if(maximum_consecutive_loss_usd>max_cons_losses_usd || 
            (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses))
           {
            maximum_consecutive_loss=max_cons_losses;
            maximum_consecutive_loss_usd=max_cons_losses_usd;
           }
         //average profit per deal
         if(max_cons_losses>0) {avg_loss+=max_cons_losses; avg_loss_cnt++;}
         max_cons_losses=0;
         max_cons_losses_usd=0;
         max_cons_wins++;
         max_cons_wins_usd+=profit;
        }
      else //deal is losing
        {
         gross_loss-=profit; //cumulative profit
         loss_trades++;      //number of losing deals
         //the most unprofitable deal and the most unprofitable series
         if(profit<largest_loss_trade) largest_loss_trade=profit;
         if(maximum_consecutive_wins<max_cons_wins || 
            (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd))
           {
            maximum_consecutive_wins=max_cons_wins;
            maximum_consecutive_wins_usd=max_cons_wins_usd;
           }
         if(maximum_consecutive_profit_usd<max_cons_wins_usd || 
            (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins))
           {
            maximum_consecutive_profit=max_cons_wins;
            maximum_consecutive_profit_usd=max_cons_wins_usd;
           }
         //average lose per deal
         if(max_cons_wins>0) {avg_win+=max_cons_wins; avg_win_cnt++;}
         max_cons_wins=0;
         max_cons_wins_usd=0;
         max_cons_losses++;
         max_cons_losses_usd+=profit;
        }

      tmp_balance+=profit; //absolute drawdown calculation
      if(tmp_balance>max_peak) {max_peak=tmp_balance; max_peak_pos=i;}
      if(tmp_balance<min_peak) {min_peak=tmp_balance; min_peak_pos=i;}
      if((max_peak-min_peak)>maximal_drawdown && min_peak_pos>max_peak_pos) maximal_drawdown=max_peak-min_peak;
     }
   //maximal drawdown calculation
   double min_peak_rel=max_peak;
   tmp_balance=0;
   for(int i=max_peak_pos;i<total;i++)
     {
      double profit=((CArrayDouble *)Data.At(i)).At(0);
      tmp_balance+=profit;
      if(tmp_balance<min_peak_rel) min_peak_rel=tmp_balance;
     }
   //relative drawdown calculation
   relative_drawdown=max_peak-min_peak_rel;
   //net profit
   net_profit=gross_profit-gross_loss;
   //profit factor
   profit_factor=(gross_loss!=0) ?  gross_profit/gross_loss : gross_profit;
   //expected payoff
   expected_payoff=net_profit/total;
   double initial_deposit=AccountInfoDouble(ACCOUNT_BALANCE)-net_profit;
   absolute_drawdown=MathAbs(min_peak); 
   //drawdowns
   maximal_drawdown_pp=(initial_deposit!=0) ?(maximal_drawdown/initial_deposit)*100.0 : 0;
   relative_drawdown_pp=((max_peak+initial_deposit)!=0) ?(relative_drawdown/(max_peak+initial_deposit))*100.0 : 0;
   
   //profit and losing trade percentage
   profit_trades_pp=((double)profit_trades/total)*100.0;
   loss_trades_pp=((double)loss_trades/total)*100.0;
   
   //average profitable and losing deals
   average_profit_trade=(profit_trades>0) ? gross_profit/profit_trades : 0;
   average_loss_trade=(loss_trades>0) ? gross_loss/loss_trades : 0;
   
   //maximum consecutive losses
   if(maximum_consecutive_losses<max_cons_losses || 
      (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd))
     {
      maximum_consecutive_losses=max_cons_losses;
      maximum_consecutive_losses_usd=max_cons_losses_usd;
     }
   if(maximum_consecutive_loss_usd>max_cons_losses_usd || 
      (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses))
     {
      maximum_consecutive_loss=max_cons_losses;
      maximum_consecutive_loss_usd=max_cons_losses_usd;
     }

   if(maximum_consecutive_wins<max_cons_wins || 
      (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd))
     {
      maximum_consecutive_wins=max_cons_wins;
      maximum_consecutive_wins_usd=max_cons_wins_usd;
     }
   if(maximum_consecutive_profit_usd<max_cons_wins_usd || 
      (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins))
     {
      maximum_consecutive_profit=max_cons_wins;
      maximum_consecutive_profit_usd=max_cons_wins_usd;
     }
   //average loss and profit
   if(max_cons_losses>0) {avg_loss+=max_cons_losses; avg_loss_cnt++;}
   if(max_cons_wins>0) {avg_win+=max_cons_wins; avg_win_cnt++;}
   average_consecutive_wins=(avg_win_cnt>0) ? round((double)avg_win/avg_win_cnt) : 0;
   average_consecutive_losses=(avg_loss_cnt>0) ? round((double)avg_loss/avg_loss_cnt) : 0;
   
   //number of profitable long and short positions
   long_positions_won=(long_positions>0) ?((double)long_positions_won/long_positions)*100.0 : 0;
   short_positions_won=(short_positions>0) ?((double)short_positions_won/short_positions)*100.0 : 0;
  }

5. 구글 차트 API를 통해 잔고 차트 만들기

구글 차트 API 는 개발자들이 즉석에서 다양한 타입의 다이어그램을 만들 수 있게 해줍니다. 구글 차트 API는 Google 웹 서버의 리소스(URL) 링크에 저장되며, 올바른 형식의 링크(URL)를 수신하면 다이어그램을 이미지로 반환합니다.

다이어그램 특성(색상, 헤더, 축, 차트의 점 등)은 링크(URL)로 지정됩니다. 결과 이미지는 파일 시스템 또는 데이터베이스에 저장할 수 있습니다. 가장 좋은 점은 구글 차트 API는 공짜이며 계정을 딱히 가입하지 않아도 된다는 점입니다.  

GetChart() 메소드는 구글에서 차트를 받아 디스크에 저장합니다.

#import "PNG_to_BMP.dll"//import of DLL with the function of conversion of PNG images to BMP
bool Convert_PNG(string src,string dst);
#import

#import "wininet.dll"//import DLL with the function for working with the internet
int InternetAttemptConnect(int x);
int InternetOpenW(string sAgent,int lAccessType,
                  string sProxyName="",string sProxyBypass="",
                  int lFlags=0);
int InternetOpenUrlW(int hInternetSession,string sUrl,
                     string sHeaders="",int lHeadersLength=0,
                     int lFlags=0,int lContext=0);
int InternetReadFile(int hFile,char &sBuffer[],int lNumBytesToRead,
                     int &lNumberOfBytesRead[]);
int InternetCloseHandle(int hInet);
#import

//+------------------------------------------------------------------+
///Function of creating a balance chart
//+------------------------------------------------------------------+
void Board::GetChart(int X_size,int Y_size,string request,string file_name)
  {
   if(X_size<1 || Y_size<1) return; //too small
   //try to create connection
   int rv=InternetAttemptConnect(0);
   if(rv!=0) {Alert("Error in call of the InternetAttemptConnect()"); return;}
   //initialize the structures
   int hInternetSession=InternetOpenW("Microsoft Internet Explorer", 0, "", "", 0);
   if(hInternetSession<=0) {Alert("Error in call of the InternetOpenW()"); return;}
   //send request
   int hURL=InternetOpenUrlW(hInternetSession, request, "", 0, 0, 0);
   if(hURL<=0) Alert("Error in call of the InternetOpenUrlW()");
   //file with the result
   CFileBin chart_file;
   //let's create it
   chart_file.Open(file_name+".png",FILE_BIN|FILE_WRITE);
   int dwBytesRead[1]; //number of data read
   char readed[1000];  //the data 
   //read the data, returned by server after the request
   while(InternetReadFile(hURL,readed,1000,dwBytesRead))
     {
      if(dwBytesRead[0]<=0) break; //no data - exit
      chart_file.WriteCharArray(readed,0,dwBytesRead[0]); //write data to file
     }
   InternetCloseHandle(hInternetSession);//close connection
   chart_file.Close();//close file
   //******************************
   //prepare the paths for the converter
   CString src;
   src.Assign(TerminalInfoString(TERMINAL_PATH));
   src.Append("\MQL5\Files\\"+file_name+".png");
   src.Replace("\\","\\\\");
   CString dst;
   dst.Assign(TerminalInfoString(TERMINAL_PATH));
   dst.Append("\MQL5\Images\\"+file_name+".bmp");
   dst.Replace("\\","\\\\");
   //convert the file
   if(!Convert_PNG(src.Str(),dst.Str())) Alert("Error in call of the Convert_PNG()");
  }
  

API Windows 및 MQL5의 온라인 도구에 대한 자세한 내용은 인터넷 터미널 간 데이터 교환에 WinInet.dll 사용하기에서 확인할 수 있습니다. 그러므로 나는 이것에 시간을 할애하진 않겠습니다. 임포트한 함수 Convert_PNG()는 PNG 이미지를 BMP로 변환하기 위해 제가 작성한 것입니다.

구글차트가 PNG나 GIF 형식으로 차트를 반환하고, "그래픽 레이블" 개체는 BMP 이미지만 받아들이기 때문에 필요하죠. PNG_to_BMP.dll 라이브러리 함수의 코드는 DLL_Sources.zip 아카이브에서 찾아볼 수 있습니다.

이 함수는 표준 라이브러리를 사용하여 줄과 파일로 작업하는 몇 가지 예를 보여줍니다. CString 클래스 메소드는 스트링 함수와 동일한 작업을 수행할 수 있습니다. CFile 클래스는 CFileBin CFileTxt클래스의 베이스입니다. 그 덕택에, 우리는 각각 바이너리 파일과 텍스트 파일의 읽기 및 기록을 각각 생성할 수 있습니다. 이들 메소드는 파일 관리 함수들과 유사합니다.

마지막으로 CreateGoogleRequest () 함수를 설명하겠습니다 - 잔고 데이터에 대한 쿼리를 만듭니다.

//+------------------------------------------------------------------+
///Function for creating a request for the Google Charts server
//+------------------------------------------------------------------+
string Board::CreateGoogleRequest(int X_size,int Y_size,bool type)
  {
   if(X_size>1000) X_size=1000; //check the chart size
   if(Y_size>1000) Y_size=300;  //to make sure it is not too large
   if(X_size<1) X_size=1;       //and small//s18>
   if(Y_size<1) Y_size=1;
   if(X_size*Y_size>300000) {X_size=1000; Y_size=300;}//and fit the area
   CString res; //string with results
   if(type) //create request for the balance chart
     {
      //prepare the request
      res.Assign("http://chart.apis.google.com/chart?cht=lc&chs=");
      res.Append(IntegerToString(X_size));
      res.Append("x");
      res.Append(IntegerToString(Y_size));
      res.Append("&chd=t:");
      for(int i=0;i<ChartData.Total();i++)
         res.Append(DoubleToString(ChartData.At(i),2)+",");
      res.TrimRight(",");
      //sort array
      ChartData.Sort();
      res.Append("&chxt=x,r&chxr=0,0,");
      res.Append(IntegerToString(ChartData.Total()));
      res.Append("|1,");
      res.Append(DoubleToString(ChartData.At(0),2)+",");
      res.Append(DoubleToString(ChartData.At(ChartData.Total()-1),2));
      res.Append("&chg=10,10&chds=");
      res.Append(DoubleToString(ChartData.At(0),2)+",");
      res.Append(DoubleToString(ChartData.At(ChartData.Total()-1),2));
     }
   else //create request for the pie chart
     {
      //prepare the request
      res.Assign("http://chart.apis.google.com/chart?cht=p3&chs=");
      res.Append(IntegerToString(X_size));
      res.Append("x");
      res.Append(IntegerToString(Y_size));
      res.Append("&chd=t:");
      for(int i=0;i<pie_data.Total();i++)
         res.Append(IntegerToString(pie_data.Get_val(i))+",");
      res.TrimRight(",");
      res.Append("&chdl=");
      for(int i=0;i<pie_data.Total();i++)
         res.Append(pie_data.Get_symb(i)+"|");
      res.TrimRight("|");
      res.Append("&chco=");
      int cnt=0;
      for(int i=0;i<pie_data.Total();i++)
        {
         if(cnt>11) cnt=0;
         res.Append(colors[cnt]+"|");
         cnt++;
        }
      res.TrimRight("|");
     }
   return res.Str(); //return the result
  }

잔고 차트 및 원형 차트에 대한 요청은 별도로 수집됩니다. Append() 메소드는 현존하는 행의 끝에 새로운 행을 추가하며, TrimRight() 메소드는 행 끝에 표시되는 추가 문자를 제거하게 해줍니다.


6. 마지막 조립 및 테스팅

클래스가 완성되었으니 테스트해봅시다. OnInit () 인디케이터로 시작합니다:

Board *tablo;   //pointer to the board object
int prev_x_size=0,prev_y_size=0,prev_deals=0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   //set indicator short name
   IndicatorSetString(INDICATOR_SHORTNAME,"IT");
   //launch the timer
   EventSetTimer(1); 
   //create object instance
   tablo=new Board;
   //and the interface
   tablo.CreateInterface(); 
   prev_deals=HistoryDealsTotal(); //number of deals
   //current sizes of the window
   prev_x_size=ChartGetInteger(0,CHART_WIDTH_IN_PIXELS); 
   prev_y_size=ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
//---
   return(0);
  }

여기서는 보드 클래스 인스턴스를 동적으로 생성하고, 타이머를 시작하고, 보조 변수를 초기화합니다.  

바로 OnDeinit() 함수를 사용하여 객체(소멸자를 자동적으로 발동시키는)를 제거하고 타이머를 중지합니다:

void OnDeinit(const int reason)
{
   EventKillTimer(); //stop the timer
   delete table;    //and board
}

함수 OnCalculate()가 신규 거래의 흐름을 틱마다 모니터링하고 화면을 갱신합니다:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   //prepare the history
   HistorySelect(0,TimeCurrent());
   int deals=HistoryDealsTotal();
   //update the board if number of deals has changed
   if(deals!=prev_deals) tablo.Refresh();
   prev_deals=deals;
//--- return value of prev_calculated for next call
   return(rates_total);
  }

OnTimer() 함수는 창의 크기 변화를 모니터링하고, 필요한 경우 디스플레이 크기를 수정하며, 틱 간 간극이 1초를 넘어가는 경우 OnCalculate()처럼 거래도 모니터링합니다.

void OnTimer()
  {
   int x_size=ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int y_size=ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   //update the board if window size has changed
   if(x_size!=prev_x_size || y_size!=prev_y_size) tablo.Refresh();
   prev_x_size=x_size;
   prev_y_size=y_size;
   //update the board if number of deals has changed
   HistorySelect(0,TimeCurrent());
   int deals=HistoryDealsTotal();
   if(deals!=prev_deals) tablo.Refresh();
   prev_deals=deals;
  }

인디케이터 컴파일 및 실행:

3번 그림. 테이블의 최종 모습

3번 그림. 테이블의 최종 모습

마치며

독자님께서 이 글을 읽으시면서 무언가 새로운 정보를 얻으셨길 빕니다. 표준 라이브러리의 무궁무진한 가능성의 일단이라도 느끼실 수 있도록 노력했습니다. 표준 라이브러리는 편하고, 빠르며, 아주 높은 퍼포먼스를 선보일 수 있기 때문입니다. 물론 객체 지향 프로그래밍의 지식이 조금 있으셔야겠지만 말입니다.

행운을 빕니다. 

써보시려면 MQL5.rar 아카이브를 종단 폴더에 압축해제하시고 DLL 사용을 허가하셔야합니다. DLL_Sources.zip 아카이브엔 String_Metrics.dll PNG_to_BMP.dll와 같은 라이브러리들의 소스 코드가 포함되어있으며, 이들은 Borland C++ Builder 환경에서 설치된 GDI로 제가 쓴 것들입니다. 

MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/102

파일 첨부됨 |
infoboard.zip (200 KB)
dll_sources.zip (1.62 KB)
infoboard-doc-en.zip (1836.47 KB)
Expert Advisor에서의 자금 관리용 함수들 Expert Advisor에서의 자금 관리용 함수들
거래 전략의 개발은 주로 시장 진입과 퇴출을 위한 패턴을 찾는 것뿐만 아니라 포지션을 유지하는 것에 초점을 맞추고 있습니다. 만약 일부 패턴을 자동 트레이딩을 위한 공식으로 만들 수 있다면, 투자자는 자동 투자 모드에서 오픈 포지션을 보장하기 위해서 안전한 수준의 모기지 자금뿐만 아니라 포지션의 양, 마진의 크기를 계산해야하는 문제에 직면하게 됩니다. 이 글에서 우리는 그러한 계산을 할 수 있는 간단한 예시를 보이기 위해 MQL5 언어를 사용할 것입니다.
초보자를 위한 MQL5 Expert Advisor 코드 작성 가이드 초보자를 위한 MQL5 Expert Advisor 코드 작성 가이드
MQL5의 Expert Advisors 프로그래밍은 간단하며 쉽게 배울 수 있습니다. 이 단계별 가이드를 통해 개발된 거래 전략에 따라 간단한 Expert Advisor를 작성할 때 필요한 기본 단계를 확인할 수 있습니다. Expert Advisor의 구조, 내장 기술 인디케이터 및 거래 기능의 사용, 디버그 모드의 세부 사항 및 Strategy Tester의 사용 등이 소개되어 있습니다.
다른 인디케이터 기반으로 인디케이터를 쓰는 방법에 관하여 다른 인디케이터 기반으로 인디케이터를 쓰는 방법에 관하여
MQL5은 인디케이터를 백지로부터 만들어갈 수 있게도 해주지만, 클라이언트 터미널에 이미 빌트인 된 것이나 커스텀 인디케이터 등 이미 존재하는 다른 인디케이터 기반으로 만들 수 있는 옵션 또한 제공합니다. 고르고 나면 여기서도 두가지 선택지가 있습니다 - 새 계산이나 그래픽 스타일을 추가하는 방식으로 인디케이터를 개선하는 것, 그리고 iCustom() 이나 IndicatorCreate() 함수를 써서 클라이언트 터미널에 내장된 것이나 커스텀 인디케이터를 쓰는 것.
MetaTrader5가 주는 새로운 기회 MetaTrader5가 주는 새로운 기회
전 세계 투자자들에게 사랑 받은 MetaTrader4는 그 자체로 정말 완벽해 보였죠. 빠른 처리 속도, 안정성, 다양한 인디케이터 작성 방법, 엑스퍼트 어드바이저, 거래 시스템, 그리고 선택 가능한 브로커의 수까지, 다른 터미널과는 비교가 불가능할 정도였으니까요. 하지만 시간이 흘러 이제 우리는 MetaTrader4와 MetaTrader5 사이의 선택의 길에 놓여 있는데요. 이 글에서는 MetaTrader5의 주요 차별점에 대해 이야기하도록 하겠습니다.