MQL5 Cookbook: 트리플 스크린 전략에 기반한 거래 시스템을 위한 프레임워크 개발
소개
거래 시스템을 검색하거나 개발할 때 많은 거래자는 Alexander Elder가 도입한 Triple Screen 전략에 대해 들어봤을 것입니다. 인터넷에는 그 전략에 대해 부정적인 판단을 하는 사람들이 많이 있습니다. 그러나 많은 사람들은 그것이 이익을 얻는 데 도움이 될 수 있다고 믿습니다. 두 가지 의견 중 하나를 신뢰할 필요는 없습니다. 모든 것은 항상 직접 확인해야 합니다. 프로그래밍을 공부한다면 백 테스팅을 사용하여 거래 전략의 성과를 확인할 수 있으므로 모든 것이 당신의 손에 달려 있습니다.
이 글에서는 MQL5의 Triple Screen 전략을 기반으로 하는 거래 시스템의 프레임워크를 개발할 것입니다. Expert Advisor는 처음부터 개발되지 않습니다. 대신 이전 글 "MQL5 Cookbook: 지표를 사용하여 Expert Advisor의 거래 조건 설정"에서 프로그램을 수정하기만 하면 됩니다. 따라서 이 글에서는 기성 프로그램의 패턴을 쉽게 수정할 수 있는 방법도 보여줍니다.
이전 글의 Expert Advisor는 이미 Stop Loss/Take Profit 및 Trailing Stop 레벨, 포지션 볼륨 증가 및 반대 신호에 대한 포지션 반전을 활성화/비활성화할 수 있는 가능성을 제공합니다. 필요한 모든 기능이 제자리에 설정되었습니다. 따라서 우리의 작업은 추가 옵션을 추가하고 일부 기존 기능을 수정하여 외부 매개변수 목록을 변경하는 데 중점을 둡니다.
설명을 위해 이동 평균 지표를 사용하여 생성될 3개의 시간 프레임에 대한 신호를 정렬합니다. 나중에 개발된 프레임워크를 계속 실험하면서 코드를 약간 수정하여 다른 지표를 사용할 수 있습니다. 우리는 또한 각 화면에 대한 시간 프레임을 설정할 수 있는 기회를 구현할 것입니다. 표시 기간을 담당하는 매개변수의 값이 0이면 해당 화면을 사용하지 않음을 나타냅니다. 즉, 시스템은 하나 또는 두 개의 시간 프레임을 갖도록 설정할 수 있습니다.
시작하기 전에 이전 글의 Expert Advisor 파일이 있는 폴더를 복사하고 이름을 바꿉니다.
Expert Advisor 개발
외부 매개변수부터 시작하겠습니다. 아래는 업데이트된 목록의 코드입니다. 새로운 라인이 선택됩니다. 시간 프레임은 ENUM_TIMEFRAMES 열거 유형으로 선언됩니다. 드롭다운 목록에서 시간 프레임을 선택할 수 있습니다.
//--- External parameters of the Expert Advisor sinput long MagicNumber=777; // Magic number sinput int Deviation=10; // Slippage //--- input ENUM_TIMEFRAMES Screen01TimeFrame=PERIOD_W1; // Time frame of the first screen input int Screen01IndicatorPeriod=14; // Indicator period of the first screen //--- input ENUM_TIMEFRAMES Screen02TimeFrame=PERIOD_D1; // Time frame of the second screen input int Screen02IndicatorPeriod=24; // Indicator period of the second screen //--- input ENUM_TIMEFRAMES Screen03TimeFrame=PERIOD_H4; // Time frame of the third screen input int Screen03IndicatorPeriod=44; // Indicator period of the third screen //--- input double Lot=0.1; // Lot input double VolumeIncrease=0.1; // Position volume increase input double VolumeIncreaseStep=10; // Step for position volume increase input double StopLoss=50; // Stop Loss input double TakeProfit=100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse=true; // Position reversal sinput bool ShowInfoPanel=true; // Display of the info panel
예제를 단순화하기 위해 IndicatorSegments 매개변수와 AllowedNumberOfSegments 변수 및 CorrectInputParameters() 함수가 제거되었습니다. 이 조건에 관심이 있는 분들은 직접 구현해 볼 수 있습니다. 또한 이 Expert Advisor는 하나의 지표만 사용하므로 Enums.mqh 파일에서 지표 열거를 제거해야 합니다.
각 시간 프레임에 별도의 지표가 있으므로 각 지표의 핸들을 가져오려면 별도의 변수가 필요합니다.
//--- Indicator handles int Screen01IndicatorHandle=INVALID_HANDLE; // Indicator handle on the first screen int Screen02IndicatorHandle=INVALID_HANDLE; // Indicator handle on the second screen int Screen03IndicatorHandle=INVALID_HANDLE; // Indicator handle on the third screen
새 바는 최소 시간 프레임을 사용하여 확인됩니다. 외부 매개변수에서 최소 시간 프레임을 설정할 때 특정 순서, 즉 최대, 중간, 최소를 따를 필요는 없습니다. 역순과 기본적으로 모든 순서가 가능합니다. 따라서 지정된 모든 시간 프레임 중에서 최소 시간 프레임을 식별하는 함수가 필요합니다.
Expert Advisor는 3개의 시간대와 1~2개의 시간대에 작동하도록 설정할 수 있으므로 최소 시간 범위를 결정할 때 모든 옵션을 고려해야 합니다. GetMinimumTimeframe() 함수의 코드가 아래와 같은 경우:
//+------------------------------------------------------------------+ //| Determining the minimum time frame for the new bar check | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES GetMinimumTimeframe(ENUM_TIMEFRAMES timeframe1,int period1, ENUM_TIMEFRAMES timeframe2,int period2, ENUM_TIMEFRAMES timeframe3,int period3) { //--- Default minimum time frame value ENUM_TIMEFRAMES timeframe_min=PERIOD_CURRENT; //--- Convert time frame values to seconds for calculations int t1= PeriodSeconds(timeframe1); int t2= PeriodSeconds(timeframe2); int t3= PeriodSeconds(timeframe3); //--- Check for incorrect period values if(period1<=0 && period2<=0 && period3<=0) return(timeframe_min); //--- Conditions for a single time frame if(period1>0 && period2<=0 && period3<=0) return(timeframe1); if(period2>0 && period1<=0 && period3<=0) return(timeframe2); if(period3>0 && period1<=0 && period2<=0) return(timeframe3); //--- Conditions for two time frames if(period1>0 && period2>0 && period3<=0) { timeframe_min=(MathMin(t1,t2)==t1) ? timeframe1 : timeframe2; return(timeframe_min); } if(period1>0 && period3>0 && period2<=0) { timeframe_min=(MathMin(t1,t3)==t1) ? timeframe1 : timeframe3; return(timeframe_min); } if(period2>0 && period3>0 && period1<=0) { timeframe_min=(MathMin(t2,t3)==t2) ? timeframe2 : timeframe3; return(timeframe_min); } //--- Conditions for three time frames if(period1>0 && period2>0 && period3>0) { timeframe_min=(int)MathMin(t1,t2)==t1 ? timeframe1 : timeframe2; int t_min=PeriodSeconds(timeframe_min); timeframe_min=(int)MathMin(t_min,t3)==t_min ? timeframe_min : timeframe3; return(timeframe_min); } return(WRONG_VALUE); }
최소 시간 프레임 값을 저장하기 위해 다른 전역 범위 변수를 생성합니다.
//--- Variable for determining the minimum time frame ENUM_TIMEFRAMES MinimumTimeframe=WRONG_VALUE;
GetMinimumTimeframe() 함수는 OnInit() 함수에서 Expert Advisor를 초기화할 때 호출해야 합니다.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Determine the minimum time frame for the new bar check MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- Get indicator handles GetIndicatorHandles(); //--- Initialize the new bar CheckNewBar(); //--- Get the properties GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); }
MinimumTimeframe 변수 값은 CheckNewBar() 및 GetBarsData() 함수에서 사용됩니다.
GetIndicatorHandle() 함수는 이제 아래와 같이 보입니다. 기간과 기간은 각 지표에 대해 지정됩니다.
//+------------------------------------------------------------------+ //| Getting indicator handles | //+------------------------------------------------------------------+ void GetIndicatorHandles() { //--- Get handles of the indicators specified in the parameters if(Screen01IndicatorPeriod>0) Screen01IndicatorHandle=iMA(_Symbol,Screen01TimeFrame,Screen01IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen02IndicatorPeriod>0) Screen02IndicatorHandle=iMA(_Symbol,Screen02TimeFrame,Screen02IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen03IndicatorPeriod>0) Screen03IndicatorHandle=iMA(_Symbol,Screen03TimeFrame,Screen03IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); //--- If the indicator handle for the first time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 1!"); //--- If the indicator handle for the second time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 2!"); //--- If the indicator handle for the third time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 3!"); }
또한 지표 값을 가져오기 위한 배열을 추가해야 합니다(각 시간 프레임에 대해 별도로).
//--- Arrays for values of the indicators double indicator_buffer1[]; double indicator_buffer2[]; double indicator_buffer3[];
이제 지표 값을 가져오는 GetIndicatorsData() 함수가 아래와 같이 표시됩니다. 얻은 핸들의 정확성을 확인하고 모든 것이 정상이면 배열이 지표 값으로 채워집니다.
//+------------------------------------------------------------------+ //| Getting indicator values | //+------------------------------------------------------------------+ bool GetIndicatorsData() { //--- Number of indicator buffer values for determining the trading signal int NumberOfValues=3; //--- If indicator handles have not been obtained if((Screen01IndicatorPeriod>0 && Screen01IndicatorHandle==INVALID_HANDLE) || (Screen02IndicatorPeriod>0 && Screen02IndicatorHandle==INVALID_HANDLE) || (Screen03IndicatorPeriod>0 && Screen03IndicatorHandle==INVALID_HANDLE)) //--- try to get them again GetIndicatorHandles(); //--- If the time frame of the first screen is used and the indicator handle has been obtained if(Screen01TimeFrame>0 && Screen01IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer1,true); //--- Get indicator values if(CopyBuffer(Screen01IndicatorHandle,0,0,NumberOfValues,indicator_buffer1)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer1 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- If the time frame of the second screen is used and the indicator handle has been obtained if(Screen02TimeFrame>0 && Screen02IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer2,true); //--- Get indicator values if(CopyBuffer(Screen02IndicatorHandle,0,0,NumberOfValues,indicator_buffer2)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer2 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- If the time frame of the third screen is used and the indicator handle has been obtained if(Screen03TimeFrame>0 && Screen03IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer3,true); //--- Get indicator values if(CopyBuffer(Screen03IndicatorHandle,0,0,NumberOfValues,indicator_buffer3)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer3 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- return(true); }
GetTradingSignal() 및 GetSignal() 함수는 당면한 작업에 따라 수정되어야 합니다. 다음은 이러한 기능의 코드입니다.
//+------------------------------------------------------------------+ //| Determining trading signals | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- If there is no position if(!pos.exists) { //--- A Sell signal if(GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); //--- A Buy signal if(GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); } //--- If the position exists if(pos.exists) { //--- Get the position type GetPositionProperties(P_TYPE); //--- Get the last deal price GetPositionProperties(P_PRICE_LAST_DEAL); //--- A Sell signal if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_SELL && close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_SELL); //--- A Buy signal if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_BUY && close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_BUY); } //--- No signal return(WRONG_VALUE); }
GetSignal() 함수는 최소 시간 프레임을 결정할 때와 마찬가지로 포지션 개방 조건과 관련된 외부 매개변수 상태의 가능한 모든 변형을 고려합니다. 함수 코드는 아래와 같습니다.
//+------------------------------------------------------------------+ //| Checking the condition and returning a signal | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetSignal() { //--- A SELL SIGNAL: the current value of the indicators on completed bars is lower than on the previous bars //--- Conditions for a single time frame if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } //--- if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- Conditions for two time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- Conditions for three time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2] ) return(ORDER_TYPE_SELL); } //--- A BUY SIGNAL: the current value of the indicators on completed bars is higher than on the previous bars //--- Conditions for a single time frame if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- Conditions for two time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- Conditions for three time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2] ) return(ORDER_TYPE_BUY); } //--- No signal return(WRONG_VALUE); }
이제 OnInit() 및 OnDeinit() 함수를 약간만 변경하면 됩니다. 아래 코드에서 강조 표시된 변경 사항을 볼 수 있습니다.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Determine the minimum time frame for the new bar check MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- Get indicator handles GetIndicatorHandles(); //--- Initialize the new bar CheckNewBar(); //--- Get the properties GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) { //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); //--- Delete the indicator handles IndicatorRelease(Screen01IndicatorHandle); IndicatorRelease(Screen02IndicatorHandle); IndicatorRelease(Screen03IndicatorHandle); } }
트리플 스크린 전략에 기반한 거래 시스템을 위한 프레임워크가 준비되었습니다. 지표를 변경하거나 필요한 경우 몇 가지 추가 조건을 추가하여 언제든지 수정할 수 있습니다.
매개변수 최적화 및 Expert Advisor 테스트
파라미터 최적화를 진행하여 결과를 확인해보자. 전략 테스터는 아래와 같이 설정됩니다(세 시간 프레임 중 가장 낮은 시간을 지정해야 함).
그림 1. 전략 테스터 설정.
최적화를 위한 Expert Advisor 매개변수는 아래와 같이 설정되었습니다. 최적화를 위해 시간 프레임을 설정할 수 있지만 수동으로 설정하는 것을 선호합니다.
그림 2. Expert Advisor의 설정.
최적화는 듀얼 코어 프로세서에서 약 30분 만에 완료되었습니다. The Optimization Graph is provided below:
그림 3. 최적화 그래프.
최대 균형 테스트 결과는 최대 회복 계수 테스트 결과보다 더 적은 드로다운을 보여주므로 최대 균형 테스트 결과가 데모 목적으로 사용되는 이유입니다.
그림 4. 최대 균형 테스트 결과.
그림 5. 최대 균형 테스트 그래프.
결론
이 글에서는 주요 기능을 사용할 수 있는 경우 Expert Advisor를 상당히 빠르게 수정할 수 있음을 보여주었습니다. 시그널 블록과 지표만 변경하면 새로운 거래 시스템을 얻을 수 있습니다. 글에 첨부된 다운로드 가능한 아카이브에는 추가 자가 학습을 위해 위에 설명된 Expert Advisor의 소스 코드와 입력 매개변수 설정이 포함된 세트 파일이 포함되어 있습니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/647