English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
차트에서 거래 아이디어의 빠른 테스트

차트에서 거래 아이디어의 빠른 테스트

MetaTrader 5트레이딩 시스템 | 2 9월 2021, 16:37
332 0
Vladimir Kustikov
Vladimir Kustikov

소개

제6회 자동거래선수권대회가 드디어 시작되었습니다. 모든 초반의 흥분감은 끝났고 마침내 약간의 긴장을 풀고 제출된 거래 로봇을 검토할 수 있게 되었습니다. 저는 현대 거래 로봇의 가장 눈에 띄는 기능을 찾고 그들의 거래 활동에서 기대할 수 있는 것을 정의하기 위해 약간의 조사를 하기로 결정했습니다.

그건 충분히 어려운 것으로 판명되었지만요. 따라서 Expert Advisor의 설명과 개발들의 드문 의견만 가지고 있었기 때문에 내 계산은 완벽하게 정확하거나 완전하다고 할 수 없습니다. 그러나 우리는 여전히 몇 가지 결론을 내릴 수 있으며 아래는 저의 계산 결과입니다. 451명의 Expert Advisor이 챔피언십에 참여하지만 그 중 316명만이 의미 있는 설명을 포함하고 있습니다. 나머지 개발자들은 친구와 가족에 대한 인사, 외계 문명에 대한 메시지 또는 자화자찬으로 설명을 채운 것으로 보입니다.

ATC 2012에서 가장 인기 있는 전략:

  • 다양한 그래픽 구성을 사용한 거래(중요한 가격 수준, 지원 저항 수준, 채널) – 55;
  • 가격 변동 분석(다양한 기간 동안) – 33;
  • 추세 추적 시스템(이 큰 단어는 이동 평균의 과도하게 최적화된 조합을 숨기지만 틀릴 수 있습니다.) :) ) – 31;
  • 통계적 가격 패턴 – 10:
  • 중재, 기호 상관 분석 – 8;
  • 변동성 분석 – 8;
  • 신경망 – 7;
  • 촛대 분석 - 5;
  • 평균 - 5;
  • 전략 번들 – 5;
  • 거래 세션 시간 - 4;
  • 난수 생성기 - 4;
  • 뉴스 거래 – 3,
  • 엘리엇 웨이브 - 2.

물론 지표 전략은 전통적으로 가장 인기 있는 전략입니다. 특정 Expert Advisor에서 각 특정 지표의 역할을 정의하는 것은 어렵지만 절대적인 사용 횟수는 추정할 수 있습니다.

  • 이동 평균 – 75;
  • MACD – 54;
  • 스토캐스틱 오실레이터 – 25;
  • RSI – 23;
  • 볼린저 밴드 – 19;
  • 프랙탈 – 8;
  • CCI, ATR - 각각 7개 지표;
  • 지그재그, 포물선 SAR – 각각 6개 지표;
  • ADX – 5;
  • 모멘텀 – 4;
  • 사용자 지정 지표(흥미로운 정도 :) ) – 4;
  • Ichimoku, AO – 각각 3개의 지표;
  • ROC, WPR, StdDev, Volumes – 각각 2개의 지표.

데이터는 다음과 같은 결론을 제시합니다. 대부분의 참가자는 지표와 함께 거래 추적 전략을 사용합니다. 아마도 데이터를 수집할 때 제가 놓친 것이 있고 자동화된 거래 분야에서 뛰어난 인물이 출현하는 것을 보게 될 것이지만 현재로서는 그럴 것 같지 않습니다. 제 생각에 가장 주요한 문제는 시장에 이끌리는 신입들이 대부분의 경우 지식보다는 규칙을 수용한다는 것입니다.

예를 들어 다음은 MACD 사용 규칙입니다. 신호는 다음과 같습니다. 이제 매개변수를 최적화하고 돈을 벌 수 있습니다. 두뇌를 조금 사용해보는 게 어떻습니까? 넌센스죠! 표준은 이미 개발되었습니다! 왜 바퀴를 재발명하려 합니까? 그러나 우리는 현재 매우 인기 있는 지표가 저와 당신과 같은 트레이더에 의해 발명되었다는 사실을 종종 잊습니다. 그들에게도 표준과 권위가 있었습니다. 아마도 당신의 이름을 딴 새로운 지표가 10년 안에 표준 지표가 될 지도 모르죠.

거래 아이디어를 검색하는 방법과 이러한 아이디어를 빠르게 테스트하는 데 사용하는 방법을 공유하고 싶습니다.


방법 설명

모든 기술적 분석은 하나의 단순한 공리를 기반으로 합니다. 가격은 모든 것을 고려합니다. 그러나 한 가지 문제가 있습니다. 이 진술에는 역동성이 부족합니다. 차트를 보고 정적 이미지를 봅니다. 가격은 실제로 모든 것을 고려했습니다. 그러나 우리는 가격이 미래의 특정 기간 동안 무엇을 고려하고 어디로 갈 것인지 알고 싶습니다. 그래야 수익을 낼 수 있으니까요. 가격에서 파생된 지표는 가능한 미래 움직임을 정확히 예측하도록 설계되었습니다.

물리학에서 알 수 있듯이 크기의 1차 도함수는 속도입니다. 따라서 지표는 현재 가격 변동 속도를 계산합니다. 우리는 또한 상당한 크기가 상당한 외력의 개입 없이 값의 급격한 변화로부터 속도를 방지하는 관성을 가지고 있다는 것을 알고 있습니다. 그것이 우리가 점차적으로 추세의 개념에 접근하는 방법입니다 - 1차 파생 상품 (속도)이 외부 힘 (뉴스, 중앙 은행의 정책 등)이 영향을 미치지 않는 기간 동안 가치가 유지되는 가격 상태.

그러나 우리가 시작했던 곳으로 돌아가 봅시다. 가격은 모든 것을 고려합니다. 새로운 아이디어를 개발하려면 동일한 시간 간격으로 가격과 파생 상품의 행동을 조사해야 합니다. 가격 차트를 주의 깊게 조사해야만 맹신에서 진정한 이해의 수준으로 거래를 높일 수 있습니다.

이것은 거래 결과의 즉각적인 변화로 이어지지 않을 수 있지만 많은 이유 질문에 대답하는 능력은 조만간 긍정적인 역할을 할 것입니다. 게다가, 차트와 지표의 시각적 분석을 통해 개발자가 전혀 예측하지 못한 가격과 지표 간의 새로운 상관 관계를 찾을 수 있습니다.

당신에게 유리하게 작용하는 것처럼 보이는 새로운 상관 관계를 발견했다고 가정해 봅시다. 향후 계획은 무엇입니까? 가장 쉬운 방법은 Expert Advisor를 작성하고 과거 데이터에서 테스트하여 가정이 올바른지 확인하는 것입니다. 그렇지 않은 경우 매개변수를 최적화하는 일반적인 방법을 선택해야 합니다. 그것의 가장 나쁜 점은 우리가 왜-질문에 대답할 수 없었다는 것입니다. 왜 우리의 Expert Advisor가 적자/수익성으로 밝혀졌습니까? 왜 그렇게 큰 하락이 있었습니까? 답이 없으면 아이디어를 효율적으로 구현할 수 없습니다.

얻은 상관 관계의 결과를 차트에서 바로 시각화하기 위해 다음 작업을 수행합니다.

  1. 저는 필요한 지표를 생성하거나 변경하여 신호를 생성합니다. -1 매도, 1 매수.
  2. 진입점과 퇴출점이 표시되는 잔액 지표를 차트에 연결합니다. 지표는 또한 신호를 처리할 때 균형 및 자산의 변화(포인트)를 보여줍니다.
  3. 저는 어떤 경우와 상황에서 제 가정이 올바른지 분석합니다.

이 방법에는 몇 가지 장점이 있습니다.

  • 첫째, 잔액 지표는 최대 계산 속도와 입력 계산 배열의 기록 데이터 자동 가용성을 제공하는 OnCalculate 방법을 사용하여 완전히 계산됩니다.
  • 둘째, 기존 지표에 신호를 추가하는 것은 Wizard를 통해 Expert Advisor를 생성하는 것과 직접 개발하는 것 사이의 중간 단계입니다.
  • 셋째, 하나의 차트에서 아이디어와 최종 결과를 볼 수 있습니다. 물론 이 방법에는 몇 가지 제한 사항이 있습니다. 신호는 바의 종가에 연결되고 잔액은 고정 랏에 대해 계산되며 보류 주문을 사용하여 거래할 수 있는 옵션이 없습니다. 그러나 이러한 모든 제한 사항은 쉽게 수정/개선될 수 있습니다.


구현

간단한 신호 지표를 개발하여 작동 방식을 이해하고 메소드의 편의성을 평가해 보겠습니다. 저는 오랫동안 촛대 패턴에 대해 들어왔습니다. 그렇다면 어째서 실제로 그들의 작업을 확인하지 않는거죠? 저는 "해머"와 "슈팅 스타" 반전 패턴을 각각 매수 신호와 매도 신호로 선택했습니다. 아래 이미지는 개략적인 모양을 보여줍니다.

그림 1. "해머"와 "슈팅 스타" 촛대 패턴

그림 1. "해머"와 "슈팅 스타" 촛대 패턴

이제 "해머" 패턴이 나타날 때의 시장 진입 규칙을 정의해 보겠습니다.

  1. 캔들스틱의 가장 낮은 값은 이전 캔들스틱 5개보다 낮아야 합니다.
  2. 캔들스틱의 몸체는 전체 높이의 50%를 초과해서는 안 됩니다.
  3. 캔들스틱의 위쪽 그림자는 전체 높이의 0%를 초과해서는 안 됩니다.
  4. 캔들스틱의 높이는 그 앞의 캔들스틱 5개 평균 높이의 100% 이상이어야 합니다.
  5. 패턴의 종가는 10 기간 이동 평균보다 낮아야 합니다.

이러한 조건이 충족되면 롱 포지션을 열어야 합니다. "슈팅 스타" 패턴의 규칙은 동일합니다. 유일한 차이점은 숏 포지션을 열어야 한다는 것입니다.

  1. 캔들스틱의 가장 높은 값은 이전 캔들스틱 중 5개보다 높아야 합니다.'
  2. 캔들스틱의 몸체는 전체 높이의 50%를 초과해서는 안 됩니다.
  3. 캔들스틱의 아래쪽 그림자는 전체 높이의 0%를 초과해서는 안 됩니다.
  4. 캔들스틱의 높이는 그 앞의 캔들스틱 5개 평균 높이의 100% 이상이어야 합니다.
  5. 패턴의 종가는 10 기간 이동 평균보다 높아야 합니다.

앞으로 최적화할 수 있는 도면을 기반으로 사용한 매개변수에 대해 굵은 글꼴을 사용했습니다(패턴이 허용 가능한 결과를 나타내는 경우). 구현하려는 제한 사항을 통해 부적절한 모양을 가진 패턴에서 패턴을 지울 수 있습니다(pp. 1-3), 신호로 받아들일 수 없는 고의로 약한 것들로부터뿐만 아니라.

게다가, 우리는 출구 순간을 결정해야 합니다. 언급된 패턴은 추세 반전 신호로 나타나기 때문에 해당 캔들이 나타나는 순간 추세가 존재합니다. 따라서 가격을 쫓는 이동 평균도 나타날 것입니다. 출구 신호는 가격과 해당 10 기간 이동 평균을 교차하여 형성됩니다.

이제 프로그래밍을 할 차례입니다. MQL5 마법사에서 새 사용자 지정 지표를 개발하고 이름을 PivotCandles로 지정하고 동작을 설명하겠습니다. 잔액 지표를 연결하기 위해 반환된 값을 정의해 보겠습니다.

  • -1 – 매도 포지션을 엽니다.
  • -2 – 매수 포지션을 마감합니다.
  • 0 - 신호 없음;
  • 1 – 매수 포지션 오픈;
  • 2 – 매도 포지션 클로즈.

아시다시피 진정한 프로그래머는 쉬운 방법을 찾지 않습니다. 그들은 가장 쉬운 것을 찾습니다. :) 저도 예외가 아니죠. 헤드폰으로 음악을 들으며 향긋한 커피를 마시며 지표와 Expert Advisor(지표를 기반으로 개발하기로 결정한 경우)에 구현하고자 하는 클래스로 파일을 만들었습니다. 아마도 다른 촛대 패턴을 위해 수정할 수도 있을 것 같습니다. 코드에는 새로운 것이 포함되어 있지 않습니다. 코드에 대해 구현된 주석은 가능한 모든 질문을 다룹니다.

//+------------------------------------------------------------------+
//|                                            PivotCandlesClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      iMaxBodySize            = 50;  // Maximum candle body, %
input int      iMaxShadowSize          = 0;   // Maximum allowed candle shadow, %
input int      iVolatilityCandlesCount = 5;   // Number of previous bars for calculation of an average volatility
input int      iPrevCandlesCount       = 5;   // Number of previous bars, for which the current bar should be an extremum
input int      iVolatilityPercent      = 100; // Correlation of a signal candle with a previous volatility, %
input int      iMAPeriod               = 10;  // Period of a simple signal moving average
//+------------------------------------------------------------------+
//| Class definition                                                 |
//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
private:
   MqlRates          m_candles[];              // Array for storing the history necessary for calculations
   int               m_history_depth;          // Array length for storing the history
   int               m_handled_candles_count;  // Number of the already processed candles
   
   double            m_ma_value;               // Current calculated moving average value
   double            m_prev_ma_value;          // Previous calculated moving average value
   bool              m_is_highest;             // Check if the current candle is the highest one
   bool              m_is_lowest;              // Check if the current candle is the lowest one
   double            m_volatility;             // Average volatility
   int               m_candle_pattern;         // Current recognized pattern
   
   void              PrepareArrayForNewCandle();        // Prepare the array for accepting the new candle
   int               CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns
   void              PrepareCalculation();
protected:
   int               DoAnalizeNewCandle();              // Calculation function
public:
   void              CPivotCandlesClass(); 
   
   void              CleanupHistory();                  // Clean up all calculation variables  
   double            MAValue() {return m_ma_value;}     // Current value of the moving average
   int               AnalizeNewCandle(MqlRates& candle);
   int               AnalizeNewCandle( 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 );
  };
//+------------------------------------------------------------------+
//| CPivotCandlesClass                                               |
//+------------------------------------------------------------------+
//| Class initialization                                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::CPivotCandlesClass()
  {
   // History depth should be enough for all calculations
   m_history_depth = (int)MathMax(MathMax(
      iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod);
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;
   
   ArrayResize(m_candles, m_history_depth);
  }  
//+------------------------------------------------------------------+
//| CleanupHistory                                                   |
//+------------------------------------------------------------------+
//| Clean up the candle buffer for recalculation                     |
//+------------------------------------------------------------------+
void CPivotCandlesClass::CleanupHistory()
  {
   // Clean up the array
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);
   
   // Null calculation variables
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;   
  }
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on candle's separate parameter values                       |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle( 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 array for the new candle
   PrepareArrayForNewCandle();

   // Fill out the current value of the candle
   m_candles[0].time          = time;
   m_candles[0].open          = open;
   m_candles[0].high          = high;
   m_candles[0].low           = low;
   m_candles[0].close         = close;
   m_candles[0].tick_volume   = tick_volume;
   m_candles[0].real_volume   = volume;
   m_candles[0].spread        = spread;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }  
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on the received candle                                      |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle)
  {
   // Prepare the array for the new candle   
   PrepareArrayForNewCandle();

   // Add the candle 
   m_candles[0] = candle;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }
//+------------------------------------------------------------------+
//| PrepareArrayForNewCandle                                         |
//+------------------------------------------------------------------+ 
//| Prepare the array for the new candle                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareArrayForNewCandle()
  {
   // Shift the array by one position to write the new value there
   ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1);
   
   // Increase the counter of added candles
   m_handled_candles_count++;
  }
//+------------------------------------------------------------------+
//| CalcMAValue                                                      |
//+------------------------------------------------------------------+ 
//| Calculate the current values of the Moving Average, volatility   |
//|   and the value extremality                                      |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareCalculation()
  {
   // Store the previous value
   m_prev_ma_value = m_ma_value;
   m_ma_value = 0;
   
   m_is_highest = true; 	// check if the current candle is the highest one
   m_is_lowest = true;  	// check if the current candle is the lowest one
   m_volatility = 0;  	// average volatility
   
   double price_sum = 0; // Variable for storing the sum
   for (int i=0; i<m_history_depth; i++)
     {
      if (i<iMAPeriod)
         price_sum += m_candles[i].close;
      if (i>0 && i<=iVolatilityCandlesCount)
         m_volatility += m_candles[i].high - m_candles[i].low;
      if (i>0 && i<=iPrevCandlesCount)
        {
         m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high);
         m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low);
        }
     }
   m_ma_value = price_sum / iMAPeriod;
   m_volatility /= iVolatilityCandlesCount;
   
   m_candle_pattern = CheckCandleSize(m_candles[0]);
  }
//+------------------------------------------------------------------+
//| CheckCandleSize                                                  |
//+------------------------------------------------------------------+
//| Check if the candle sizes comply with the patterns               |
//| The function returns:                                            |
//|   0 - if the candle does not comply with the patterns            |
//|   1 - if "hammer" pattern is detected                            |
//|   -1 - if "shooting star" pattern is detected                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::CheckCandleSize(MqlRates &candle)
  {
   double candle_height=candle.high-candle.low;          // candle's full height
   double candle_body=MathAbs(candle.close-candle.open); // candle's body height

   // Check if the candle has a small body
   if(candle_body/candle_height*100.0>iMaxBodySize)
      return 0;

   double candle_top_shadow=candle.high-MathMax(candle.open,candle.close);   // candle upper shadow height
   double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height

   // If the upper shadow is very small, that indicates the "hammer" pattern
   if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize)
      return 1;
   // If the bottom shadow is very small, that indicates the "shooting star" pattern
   else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize)
      return -1;
   else
      return 0;
  }
//+------------------------------------------------------------------+
//| DoAnalizeNewCandle                                               |
//+------------------------------------------------------------------+
//| Real analysis of compliance with the patterns                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::DoAnalizeNewCandle()
  {
   // Prepare data for analyzing the current situation
   PrepareCalculation();
   
   // Process prepared data and set the exit signal
   int signal = 0;
   
   ///////////////////////////////////////////////////////////////////
   // EXIT SIGNALS                                                  //
   ///////////////////////////////////////////////////////////////////
   // If price crosses the moving average downwards, short position is closed
   if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value)
      signal = 2;
   // If price crosses the moving average upwards, long position is closed 
   else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value)
      signal = -2;
      
   ///////////////////////////////////////////////////////////////////
   // ENTRY SIGNALS                                                 //
   ///////////////////////////////////////////////////////////////////
   // Check if the minimum volatility condition is met
   if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility)
     {
      // Checks for "shooting star" pattern
      if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value)
         signal = -1;
      // Checks for "hammer" pattern
      else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value)
         signal = 1;
     }
     
   return signal;
  }
//+------------------------------------------------------------------+

전체 계산 부분이 CPivotCandlesClass 클래스에 의해 수행되는 것을 볼 수 있습니다. 계산 부분과 시각적 부분을 분리하는 것이 좋은 프로그래밍으로 간주되어 수행하려고 합니다. 이 권장 사항을 따르기 위해 최선을 다합니다. 이점은 늦지 않았습니다. 다음은 지표 자체의 코드입니다.

//+------------------------------------------------------------------+
//|                                                 PivotCandles.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

// Use four buffers, while drawing two
#property indicator_buffers 4
#property indicator_plots   2
//--- plot SlowMA
#property indicator_label1  "SlowMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAliceBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot ChartSignal
#property indicator_label2  "ChartSignal"
#property indicator_type2   DRAW_COLOR_ARROW
#property indicator_color2  clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  3

#include <PivotCandlesClass.mqh>
//+------------------------------------------------------------------+
//| Common arrays and structures                                     |
//+------------------------------------------------------------------+
//--- Indicator buffers                                                
double   SMA[];            // Values of the Moving Average
double   Signal[];         // Signal values
double   ChartSignal[];    // Location of signals on the chart
double   SignalColor[];    // Signal color array
//--- Calculation class
CPivotCandlesClass PivotCandlesClass;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,SMA,INDICATOR_DATA);
   SetIndexBuffer(1,ChartSignal,INDICATOR_DATA);
   SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS);

//--- set 0 as an empty value
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
   // If there have not been calculations yet or (!) the new history is uploaded, clean up the calculation object
   if (prev_calculated == 0)
      PivotCandlesClass.CleanupHistory();
   
   int end_calc_edge = rates_total-1;   
   if (prev_calculated >= end_calc_edge)
      return end_calc_edge;
   
   for(int i=prev_calculated; i<end_calc_edge; i++)
     {
      int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]);
      Signal[i] = signal;
      SMA[i] = PivotCandlesClass.MAValue();
      
      // Signals are processed, display them on the chart
      // Set the location of our signals...
      if (signal < 0)
         ChartSignal[i]=high[i];
      else if (signal > 0)
         ChartSignal[i]=low[i];
      else
         ChartSignal[i]=0;
      // .. as well as their color
      // Signals have a range of [-2..2], while color indices - [0..4]. Align them 
      SignalColor[i]=signal+2;
     }
   
   // Set the Moving Average value similar to the previous one to prevent it from sharp fall
   SMA[end_calc_edge] = SMA[end_calc_edge-1];

//--- return value of prev_calculated for next call
   return(end_calc_edge);
  }
//+------------------------------------------------------------------+

지표가 준비되었습니다. 이제 아무 차트에서나 테스트해 보겠습니다. 이렇게하려면 차트에 컴파일 된 지표를 설치하세요. 그 후, 우리는 아래 이미지에 표시된 것과 유사한 것을 보게 될 것입니다.

그림 2. "해머" 및 "슈팅 스타" 촛대 패턴의 지표

그림 2. "해머" 및 "슈팅 스타" 촛대 패턴의 지표

컬러 포인트는 가능한 시장 진입 및 퇴장을 나타냅니다. 색상은 다음과 같이 선택됩니다.

  • 진한 빨간색 - 매도;
  • 진한 파란색 – 매수;
  • 연한 빨간색 – 롱 포지션 마감;
  • 연한 빨간색 - 마감 숏 포지션.

종가 신호는 가격이 이동 평균에 도달할 때마다 형성됩니다. 그 순간에 포지션이 없으면 신호는 무시됩니다.

이제 글의 주요 주제로 넘어 갑시다. 신호 버퍼가 있는 지표는 일부 특정 신호만 생성합니다. 같은 차트의 별도 창에 이러한 신호를 실제로 따를 경우 수익성/손실이 얼마나 되는지 표시해 보겠습니다. 이 경우를 위해 특별히 개발된 지표입니다. 다른 지표에 연결하고 들어오는 신호에 따라 가상 포지션을 열거나 닫을 수 있습니다.

이전 지표와 마찬가지로 코드를 계산 및 시각적 부분의 두 부분으로 분할해야 합니다. 아래는 잠 못 이루는 밤의 결과이지만 그만한 가치가 있기를 바랍니다. :)

//+------------------------------------------------------------------+
//|                                                 BalanceClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Common structures                                                |
//+------------------------------------------------------------------+
// Structure for returning calculation results 
// using only return command;
struct BalanceResults
  {
   double balance;
   double equity;
  };
//+------------------------------------------------------------------+
//| Common function                                                  |
//+------------------------------------------------------------------+
//  Function for searching for the indicator handle by its name
int FindIndicatorHandle(string _name)
  {
   // Receive the number of open charts
   int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
   
   // Search all of them
   for(int w=windowsCount-1; w>=0; w--)
     {
      // How many indicators are attached to the current chart
      int indicatorsCount = ChartIndicatorsTotal(0,w);

      // Search by all chart indicators
      for(int i=0;i<indicatorsCount;i++)
        {
         string name = ChartIndicatorName(0,w,i);
         // If such an indicator is found, return its handle
         if (name == _name)
            return ChartIndicatorGet(0,w,name);
        }
     }  
     
   // If there is no such an indicator, return the incorrect handle 
   return -1;
  }
//+------------------------------------------------------------------+
//| Base calculation class                                           |
//+------------------------------------------------------------------+
class CBaseBalanceCalculator
  {
private:
   double            m_position_volume; // Current open position volume
   double            m_position_price;  // Position opening price
   double            m_symbol_points;   // Value of one point for the current symbol
   BalanceResults    m_results;         // Calculation results
public:
   void              CBaseBalanceCalculator(string symbol_name = "");
   void              Cleanup();
   BalanceResults    Calculate( const double _prev_balance, 
                                const int    _signal,
                                const double _next_open,
                                const double _next_spread );
  };
//+------------------------------------------------------------------+
//| CBaseBalanceCalculator                                           |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "")
  {
   // Clean up state variables
   Cleanup();
   
   // Define point size (because we will calculate the profit in points)
   if (symbol_name == "")
      m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   else 
      m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT);
  }
//+------------------------------------------------------------------+
//| Cleanup                                                          |
//+------------------------------------------------------------------+
//| Clean up data on positions and prices                            |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::Cleanup()
  {
   m_position_volume = 0;
   m_position_price = 0;  
  }
//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
//| Main calculation block                                           |
//+------------------------------------------------------------------+
BalanceResults CBaseBalanceCalculator::Calculate(
                                       const double _prev_balance,
                                       const int _signal,
                                       const double _next_open,
                                       const double _next_spread )
  {
   // Clean up the output structure from the previous values
   ZeroMemory(m_results);
   
   // Initialize additional variables
   double current_price = 0; // current price (bid or ask depending on position direction)
   double profit = 0;        // profit calculated value
   
   // If there was no signal, the balance remains the same 
   if (_signal == 0)
      m_results.balance = _prev_balance;
   // the signal coincides with the direction or no positions are opened yet
   else if (_signal * m_position_volume >= 0)
     {
      // Position already exists, the signal is ignored
      if (m_position_volume != 0)
         // Balance is not changed
         m_results.balance = _prev_balance;
      // No positions yet, buy signal

      else if (_signal == 1)
        {
         // Calculate current ASK price, recalculate price, volume and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1);
         m_position_volume = m_position_volume + 1;
         m_results.balance = _prev_balance;
        }
      // No positions yet, sell signal
      else if (_signal == -1) 
        {
         // Calculate current BID price, recalculate price, volume and balance
         current_price = _next_open;
         m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1);
         m_position_volume = m_position_volume - 1; 
         m_results.balance = _prev_balance;      
        }
      else
         m_results.balance = _prev_balance;
     }
   // Position is set already, the opposite direction signal is received
   else 
     {
      // buy signal/close sell position
      if (_signal > 0)
        {
         // Close position by ASK price, recalculate profit and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
          
         // If there is a signal for opening a new position, open it at once
         if (_signal == 1)
           {
            m_position_price = current_price;
            m_position_volume = 1;
           }
         else
            m_position_volume = 0;
        }
      // sell signal/close buy position
      else 
        {
         // Close position by BID price, recalculate profit and balance
         current_price = _next_open;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
         
         // If there is a signal for opening a new position, open it at once
         if (_signal == -1)
           {
            m_position_price = current_price;
            m_position_volume = -1;
           }
         else 
           m_position_volume = 0;
        }
     }
    
   // Calculate the current equity
   if (m_position_volume > 0)
     {
      current_price = _next_open;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else if (m_position_volume < 0)
     {
      current_price = _next_open + _next_spread * m_symbol_points;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else
      m_results.equity = m_results.balance;    
   
   return m_results;
  }
//+------------------------------------------------------------------+

계산 클래스가 준비되었습니다. 이제 지표 디스플레이를 구현하여 작동 방식을 확인해야 합니다.

//+------------------------------------------------------------------+
//|                                                      Balance.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots   3
#property indicator_level1  0.0 
#property indicator_levelcolor Silver 
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1  
//--- plot Balance
#property indicator_label1  "Balance"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrBlue,clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1
//--- plot Equity
#property indicator_label2  "Equity"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLime
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Zero
#property indicator_label3  "Zero"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#include <BalanceClass.mqh>
//+------------------------------------------------------------------+
//| Input and global variables                                       |
//+------------------------------------------------------------------+
input string   iParentName        = "";             // Indicator name for balance calculation
input int      iSignalBufferIndex = -1;            // Signal buffer's index number
input datetime iStartTime         = D'01.01.2012';  // Calculation start date
input datetime iEndTime           = 0;             // Calculation end date
//--- Indicator buffers 
double   Balance[];       // Balance values
double   BalanceColor[];  // Color index for drawing the balance
double   Equity[];        // Equity values
double   Zero[];          // Zero value for histogram's correct display
//--- Global variables
double   Signal[1];       // Array for receiving the current signal
int      parent_handle;   // Indicator handle, the signals of which are to be used 

CBaseBalanceCalculator calculator; // Object for calculating balance and equity
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {     
   // Binding indicator buffers
   SetIndexBuffer(0,Balance,INDICATOR_DATA);
   SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,Equity,INDICATOR_DATA);
   SetIndexBuffer(3,Zero,INDICATOR_DATA);
  
   // Search for indicator handle by its name
   parent_handle = FindIndicatorHandle(iParentName);
   if (parent_handle < 0)
     {
      Print("Error! Parent indicator not found");
      return -1;
     } 
   
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {   
   // Set the borders for calculating the indicator
   int start_index = prev_calculated;
   int end_index = rates_total-1;
   
   // Calculate balance and equity values
   for(int i=start_index; i<end_index; i++)
     {
      // Check if the balance calculation corresponds the interval
      if (time[i] < iStartTime)
        {
         Balance[i] = 0;
         Equity[i] = 0; 
         continue;
        }
      if (time[i] > iEndTime && iEndTime != 0)
        {
         Equity[i] = (i==0) ? 0 : Equity[i-1];
         Balance[i] = Equity[i]; 
         continue;
        }
      
      // Request a signal from the parent indicator
      if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Copy the indicator main line data
        {
         Print("Data copy error: " + IntegerToString(GetLastError()));
         return(0);  // Finish the function operation and send indicator for the full recalculation
        }
      
      // Initialize balance and equity calculation
      // Since the signal is formed when the candle is closing, we will be able 
      //   to perform any operation only at the next candle's opening price
      BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]);
      
      // Fill out all indicator buffers
      Balance[i] = results.balance;
      Equity[i] = results.equity; 
      Zero[i] = 0;
      if (Balance[i] >= 0)
         BalanceColor[i] = 0;
      else
         BalanceColor[i] = 1;
     }
     
   // Fill out buffers for the last candle 
   Balance[end_index] = Balance[end_index-1];
   Equity[end_index] = Equity[end_index-1]; 
   BalanceColor[end_index] = BalanceColor[end_index-1];
   Zero[end_index] = 0;
     
   return rates_total;
  }
//+------------------------------------------------------------------+

드디어 끝났네요! 컴파일하고 결과를 살펴봅시다.


사용 지침

새로 개발된 지표의 작동을 평가하려면 최소한 하나의 신호 지표를 포함하는 차트에 첨부해야 합니다. 모든 단계를 따랐다면 이미 PivotCandles와 같은 지표가 있습니다. 따라서 입력 매개변수를 구성해야 합니다. 무엇을 지정해야 하는지 봅시다.

  • 잔액 계산을 위한 지표 이름(문자열) – 잔액 지표의 바인딩은 이름으로 수행된다는 점에 유의해야 합니다. 따라서 이 필드는 필수입니다.
  • 신호 버퍼의 색인 번호(정수) – 또 다른 중요한 매개변수입니다. 신호 지시자는 미리 정의된 알고리즘에 따라 여러 신호를 생성할 수 있습니다. 따라서 균형 지표는 계산해야 하는 버퍼의 신호에 관한 데이터를 가지고 있어야 합니다.
  • 계산 시작일(날짜/시간) – 잔액 계산의 초기 날짜.
  • 계산 종료일(날짜/시간) – 잔액 계산의 종료일입니다. 날짜를 선택하지 않으면(0과 동일) 마지막 바까지 계산이 수행됩니다.

그림 3은 PivotCandles 지표의 세 번째 버퍼에 균형 지표를 연결하기 위한 처음 두 매개변수의 구성을 보여줍니다. 나머지 두 매개변수는 원하는 대로 설정할 수 있습니다.

그림 3. 균형 지표 매개변수

그림 3. 균형 지표 매개변수

이전 단계가 모두 올바르게 수행된 경우 아래 표시된 것과 매우 유사한 이미지가 표시되어야 합니다.

그림 4. PivotCandles 지표의 신호를 사용하여 생성된 균형 및 자기자본 곡선

그림 4. PivotCandles 지표의 신호를 사용하여 생성된 균형 및 자기자본 곡선

이제 우리는 다른 시간 프레임과 기호를 시도하고 가장 수익성이 높고 손실이 큰 시장 진입을 찾을 수 있습니다. 이 접근 방식은 거래 결과에 영향을 미치는 시장 상관 관계를 찾는 데 도움이 된다는 점을 추가해야 합니다.

원래는 동일한 신호를 기반으로 Expert Advisor를 테스트하는 데 소요된 시간과 위에서 설명한 방법을 사용하는 데 소요된 시간을 비교하고 싶었습니다. 그러나 지표를 다시 계산하는 데 약 1초가 걸리기 때문에 아이디어를 포기했습니다. 이러한 짧은 시간은 아직 히스토리 업로드 및 틱 생성 알고리즘이 있는 Expert Advisor에 의해 도달할 수 없습니다.


결론

위에서 설명한 방법은 매우 빠릅니다. 또한 포지션 열림/닫힘 신호를 생성하는 지표를 테스트하는 데 명확성을 제공합니다. 이를 통해 거래자는 단일 차트 창에서 신호와 이에 대한 예금의 반응을 분석할 수 있습니다. 그러나 여전히 우리가 알아야 할 한계가 있습니다.

  • 분석된 지표의 신호 버퍼는 미리 준비해야 합니다.
  • 신호는 새 바의 오픈 시간에 바인딩됩니다.
  • 잔액을 계산할 때 ММ가 없습니다.

그러나 이러한 단점에도 불구하고 이점이 더 중요하고 이 테스트 방법이 시장 행동을 분석하고 시장에서 생성된 신호를 처리하기 위해 설계된 다른 도구 중에서 자리를 잡기를 바랍니다.

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

파일 첨부됨 |
balanceclass.mqh (8.07 KB)
balance.mq5 (5.57 KB)
pivotcandles.mq5 (4.14 KB)
MetaTrader 5의 신호 거래: PAMM 계정에 대한 더 나은 대안! MetaTrader 5의 신호 거래: PAMM 계정에 대한 더 나은 대안!
MetaTrader 5가 이제 거래 신호를 제공하여 투자자와 관리자에게 강력한 도구를 제공하게 되었음을 알려드립니다. 성공적인 거래자의 거래를 추적하는 동안 터미널은 자동으로 귀하의 계정에서 거래를 재생산합니다!
MetaTrader 5에서 MetaTrader 4로 거래를 복제하는 방법 MetaTrader 5에서 MetaTrader 4로 거래를 복제하는 방법
오늘 실제 MetaTrader 5 계정에서 거래를 할 수 있나요? 그러한 거래를 조직하는 방법은 무엇입니까? 이 글에서는 이러한 질문에 대한 이론과 MetaTrader 5 터미널에서 MetaTrader 4로 거래를 복제하는 데 사용되는 작업 코드가 포함되어 있습니다. 이 글은 Expert Advisors 개발자와 트레이더 실무자 모두에게 유용할 것입니다.
MQL5 프로그래밍 기본: 배열 MQL5 프로그래밍 기본: 배열
배열은 변수 및 함수와 함께 거의 모든 프로그래밍 언어의 필수적인 부분입니다. 이 글은 주로 초보 MQL5 프로그래머가 관심을 가져야 하는 내용으로 구성된 반면, 숙련된 프로그래머는 지식을 요약하고 체계화할 수 있는 좋은 기회가 되어 줄 것입니다.
MetaTrader 5 터미널의 Strategy Tester 내 틱 생성 알고리즘 MetaTrader 5 터미널의 Strategy Tester 내 틱 생성 알고리즘
MetaTrader 5를 사용하면 Expert Advisors와 MQL5 언어를 사용하여 임베디드 전략 테스터 내에서 자동 거래를 시뮬레이션할 수 있습니다. 이러한 유형의 시뮬레이션을 Expert Advisors 테스트라고 하며 다중 스레드 최적화를 사용하여 동시에 여러 기기에서 구현할 수 있습니다. 철저한 테스트를 제공하려면 사용 가능한 분 기록을 기반으로 하는 틱 생성을 수행해야 합니다. 이 글은 MetaTrader 5 클라이언트 터미널에서 이력 테스트를 위해 틱이 생성되는 알고리즘에 대한 자세한 설명을 제공합니다.