차트에서 거래 아이디어의 빠른 테스트
소개
제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 매수.
- 진입점과 퇴출점이 표시되는 잔액 지표를 차트에 연결합니다. 지표는 또한 신호를 처리할 때 균형 및 자산의 변화(포인트)를 보여줍니다.
- 저는 어떤 경우와 상황에서 제 가정이 올바른지 분석합니다.
이 방법에는 몇 가지 장점이 있습니다.
- 첫째, 잔액 지표는 최대 계산 속도와 입력 계산 배열의 기록 데이터 자동 가용성을 제공하는 OnCalculate 방법을 사용하여 완전히 계산됩니다.
- 둘째, 기존 지표에 신호를 추가하는 것은 Wizard를 통해 Expert Advisor를 생성하는 것과 직접 개발하는 것 사이의 중간 단계입니다.
- 셋째, 하나의 차트에서 아이디어와 최종 결과를 볼 수 있습니다. 물론 이 방법에는 몇 가지 제한 사항이 있습니다. 신호는 바의 종가에 연결되고 잔액은 고정 랏에 대해 계산되며 보류 주문을 사용하여 거래할 수 있는 옵션이 없습니다. 그러나 이러한 모든 제한 사항은 쉽게 수정/개선될 수 있습니다.
구현
간단한 신호 지표를 개발하여 작동 방식을 이해하고 메소드의 편의성을 평가해 보겠습니다. 저는 오랫동안 촛대 패턴에 대해 들어왔습니다. 그렇다면 어째서 실제로 그들의 작업을 확인하지 않는거죠? 저는 "해머"와 "슈팅 스타" 반전 패턴을 각각 매수 신호와 매도 신호로 선택했습니다. 아래 이미지는 개략적인 모양을 보여줍니다.
그림 1. "해머"와 "슈팅 스타" 촛대 패턴
이제 "해머" 패턴이 나타날 때의 시장 진입 규칙을 정의해 보겠습니다.
- 캔들스틱의 가장 낮은 값은 이전 캔들스틱 5개보다 낮아야 합니다.
- 캔들스틱의 몸체는 전체 높이의 50%를 초과해서는 안 됩니다.
- 캔들스틱의 위쪽 그림자는 전체 높이의 0%를 초과해서는 안 됩니다.
- 캔들스틱의 높이는 그 앞의 캔들스틱 5개 평균 높이의 100% 이상이어야 합니다.
- 패턴의 종가는 10 기간 이동 평균보다 낮아야 합니다.
이러한 조건이 충족되면 롱 포지션을 열어야 합니다. "슈팅 스타" 패턴의 규칙은 동일합니다. 유일한 차이점은 숏 포지션을 열어야 한다는 것입니다.
- 캔들스틱의 가장 높은 값은 이전 캔들스틱 중 5개보다 높아야 합니다.'
- 캔들스틱의 몸체는 전체 높이의 50%를 초과해서는 안 됩니다.
- 캔들스틱의 아래쪽 그림자는 전체 높이의 0%를 초과해서는 안 됩니다.
- 캔들스틱의 높이는 그 앞의 캔들스틱 5개 평균 높이의 100% 이상이어야 합니다.
- 패턴의 종가는 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. "해머" 및 "슈팅 스타" 촛대 패턴의 지표
컬러 포인트는 가능한 시장 진입 및 퇴장을 나타냅니다. 색상은 다음과 같이 선택됩니다.
- 진한 빨간색 - 매도;
- 진한 파란색 – 매수;
- 연한 빨간색 – 롱 포지션 마감;
- 연한 빨간색 - 마감 숏 포지션.
종가 신호는 가격이 이동 평균에 도달할 때마다 형성됩니다. 그 순간에 포지션이 없으면 신호는 무시됩니다.
이제 글의 주요 주제로 넘어 갑시다. 신호 버퍼가 있는 지표는 일부 특정 신호만 생성합니다. 같은 차트의 별도 창에 이러한 신호를 실제로 따를 경우 수익성/손실이 얼마나 되는지 표시해 보겠습니다. 이 경우를 위해 특별히 개발된 지표입니다. 다른 지표에 연결하고 들어오는 신호에 따라 가상 포지션을 열거나 닫을 수 있습니다.
이전 지표와 마찬가지로 코드를 계산 및 시각적 부분의 두 부분으로 분할해야 합니다. 아래는 잠 못 이루는 밤의 결과이지만 그만한 가치가 있기를 바랍니다. :)
//+------------------------------------------------------------------+ //| 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. 균형 지표 매개변수
이전 단계가 모두 올바르게 수행된 경우 아래 표시된 것과 매우 유사한 이미지가 표시되어야 합니다.
그림 4. PivotCandles 지표의 신호를 사용하여 생성된 균형 및 자기자본 곡선
이제 우리는 다른 시간 프레임과 기호를 시도하고 가장 수익성이 높고 손실이 큰 시장 진입을 찾을 수 있습니다. 이 접근 방식은 거래 결과에 영향을 미치는 시장 상관 관계를 찾는 데 도움이 된다는 점을 추가해야 합니다.
원래는 동일한 신호를 기반으로 Expert Advisor를 테스트하는 데 소요된 시간과 위에서 설명한 방법을 사용하는 데 소요된 시간을 비교하고 싶었습니다. 그러나 지표를 다시 계산하는 데 약 1초가 걸리기 때문에 아이디어를 포기했습니다. 이러한 짧은 시간은 아직 히스토리 업로드 및 틱 생성 알고리즘이 있는 Expert Advisor에 의해 도달할 수 없습니다.
결론
위에서 설명한 방법은 매우 빠릅니다. 또한 포지션 열림/닫힘 신호를 생성하는 지표를 테스트하는 데 명확성을 제공합니다. 이를 통해 거래자는 단일 차트 창에서 신호와 이에 대한 예금의 반응을 분석할 수 있습니다. 그러나 여전히 우리가 알아야 할 한계가 있습니다.
- 분석된 지표의 신호 버퍼는 미리 준비해야 합니다.
- 신호는 새 바의 오픈 시간에 바인딩됩니다.
- 잔액을 계산할 때 ММ가 없습니다.
그러나 이러한 단점에도 불구하고 이점이 더 중요하고 이 테스트 방법이 시장 행동을 분석하고 시장에서 생성된 신호를 처리하기 위해 설계된 다른 도구 중에서 자리를 잡기를 바랍니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/505