![MQL5에서 추세를 찾는 여러 방법](https://c.mql5.com/i/articles/overlay.png)
MQL5에서 추세를 찾는 여러 방법
소개
모든 트레이더는 "추세는 친구입니다. 추세를 따르십시오"라는 규칙을 알고 있지만 거의 모든 사람이 추세가 무엇인지에 대한 자신의 아이디어를 가지고 있습니다. 거의 모든 트레이더는 추세에 반하여 트레이딩을 한 trader들이 자신을 어떻게 망치게 되었는지에 대한 끔찍한 이야기를 듣거나 읽어본 적이 있을 것입니다.
모든 트레이더는 주어진 시간에 추세를 정확하게 감지하는 기회를 많이 얻게 됩니다. 아마 이거야 말로 모두가 찾고 있는 성배 (Holy Grail)일 것입니다. 이 글에서는 추세를 감지하는 몇 가지 방법을 고려해보겠습니다. 더 정확하게 말하면 MQL5를 통해 추세를 감지하는 몇 가지 고전적인 방법을 프로그래밍하는 방법입니다.
1. 추세란 무엇이며 알아야 하는 이유
우선, 추세의 일반적인 개념을 공식화합시다.
추세란 시장에서 보이는 가격 변화의 장기적인 경향(방향)입니다. 추세에 대한 이 일반적인 정의에서 다음과 같은 결과가 나옵니다.
- 가격 변동의 방향은 가격 시계열이 고려되는 기간에 따라 달라집니다.
- 가격 변화의 방향은 추세를 식별하기 위해 시계열 분석을 시작하는 기준점에 따라 다릅니다.
이 개념을 설명하겠습니다.
그림 1. 트렌드 분석
그림을 보면 2005년 말부터 2006년 5월까지 전반적인 추세가 증가하고 있음을 알 수 있습니다(차트의 녹색 화살표). 그러나 가격 차트의 작은 부분을 고려하면 2006년 2월에 추세가 분명히 하향세(차트의 빨간색 화살표)였고 1월 거의 전체 가격이 측면 회랑(노란색 화살표)에 있었다는 것을 알 수 있습니다.
따라서 추세를 식별하기 전에 관심 있는 시간대를 결정해야 합니다. 거래의 경우 시간 프레임은 먼저 개장부터 마감까지 시장에서 포지션을 유지하는 시간을 결정합니다. 그 외에도 거래 작업의 빈도뿐만 아니라 보호 정지 및 예상 폐쇄 수준도 종속됩니다.
이 글의 목적은 새로운 트레이더가 MetaTrader 5 플랫폼에서 제공하는 추세 감지 도구를 능숙하게 사용할 수 있도록 돕는 것입니다. 이 글은 또한 이 프로세스를 자동화하는 간단한 지표 작성에 대한 기본 지식을 제공하는 것을 목표로 합니다. 궁극적인 목표는 자동화된 거래를 위해 이러한 지표를 사용하는 간단한 expert를 작성하는 것입니다.
명확성을 위해 Forex 시장에서 가장 유동성이 높은 상품인 EURUSD의 일일 가격 차트(터미널의 D1 시간대)를 고려할 것입니다. 이러한 기간 동안 포지션을 유지하는 시간은 며칠에서 몇 달까지 다양할 수 있습니다. 따라서 목표는 수백, 심지어 수천 개의 포인트를 취하는 것이며 보수적인 정지 손실은 수백 포인트의 거리에 있습니다.
일반적으로 아래에 설명된 모든 것은 모든 시간대에 사용할 수 있습니다. 그러나 차트 기간이 작을수록 거래에 미치는 영향이 클수록 뉴스, 주요 참가자의 시장 투기 및 기타 요인으로 인해 시장 변동성에 영향을 미치는 노이즈 요소가 있음을 명심하십시오.
추세가 길수록 추세가 바뀔 가능성이 적다는 점을 고려하면 추세와 거래할 때 손실보다 수익을 낼 가능성이 더 큽니다. 이제 가격 차트에서 추세를 감지하는 방법을 이해해야 합니다. 이번 글에서는 이런 부분을 다뤄보겠습니다.
2. 추세를 감지하는 방법
다음은 몇 가지 알려진 추세 감지 방법입니다.
- 이동 평균으로
- 지그재그의 피크로
- ADX 표시에 의해
- NRTR로
- Heiken Ashi 캔들스틱의 색상별로
우리는 이러한 모든 방법, 장점 및 단점을 지속적으로 고려할 것입니다. 그런 다음 우리는 내역의 같은 기간에 이것들을 비교해보겠습니다.
2.1. 이동 평균을 사용한 추세 감지
아마도 추세와 방향을 감지하는 가장 쉬운 방법은 이동 평균을 사용하는 것입니다. 기술적 분석의 첫 번째 도구 중 하나인 이동 평균은 여전히 다양한 변형에서 사용되며 대부분의 지표의 기초입니다. 트레이더는 하나의 이동 평균과 "팬"이라고 하는 전체 세트를 모두 사용합니다.
하나의 이동 평균에 대한 간단한 규칙을 공식화해 보겠습니다.- 주어진 시간 프레임에서 바의 종가가 이동 평균보다 높은 경우 추세가 상승합니다.
- 주어진 시간 프레임에서 바의 종가가 이동 평균보다 낮으면 추세가 하락합니다.
이 경우 가격이 이동 평균(소위 "바운스") 근처에서 위아래로 변동할 때 "거짓" 추세 변화의 수를 줄이기 위해 바의 종가를 사용합니다.
이 방법의 실례를 들어봅시다.
그림 2. 이동 평균을 사용하여 추세 식별
여기에서는 EURUSD D1 차트와 종가(차트의 빨간색 선)를 기반으로 하는 기간이 200인 단순 이동 평균을 사용합니다. 그림 하단에서 특별히 개발된 추세 표시기인 MATrendDetector를 볼 수 있습니다. 추세 방향은 0축을 기준으로 한 표시기 히스토그램의 포지션로 표시됩니다. +1은 상승 추세에 해당합니다. -1은 하락 추세입니다. 또한 이 글에서 사용된 이 지표와 기타 지표에 대해 논의할 것입니다.
바가 이동 평균 위/아래로 닫히면 가격이 종종 반대 방향으로 변하는 것을 볼 수 있습니다. 즉. 이 방법은 많은 잘못된 신호를 제공합니다. 그렇기 때문에 expert 및 지표에서의 사용은 추세의 매우 "대략적" 필터로 매우 제한적이게 됩니다.
2.2. 3개의 이동 평균을 사용한 추세 감지
이동 평균을 사용하여 추세 감지 품질을 개선하려면 어떻게 해야 합니까? 예를 들어, 기간이 다른 두 개 이상의 이동 평균을 사용할 수 있습니다. 그러면 기간이 다른 이동 평균의 수(둘 이상)에 대한 추세 감지 규칙은 다음과 같습니다.
- 주어진 기간에 모든 이동 평균이 바의 클로저에 올바른 상승 순서로 표시되면 추세는 상승합니다.
- 주어진 기간에 모든 이동 평균이 바의 클로저에 올바른 하락 순서로 표시되는 경우 추세는 하락합니다.
여기에서는 다음 용어를 사용합니다.
- 올바른 상승 순서 - 모든 이동 평균은 기간이 더 높은 다른 모든 이동 평균보다 더 높아야 합니다.
- 정확한 하락 순서 - 모든 이동 평균은 기간이 더 높은 다른 모든 이동 평균보다 낮아야야 합니다.
이러한 "정확한 평균 순서"는 시각적 유사성 때문에 평균 팬의 위/아래 오프닝이라고도 합니다.
이 방법의 실례를 들어봅시다.
그림 3. 여러 이동 평균을 사용한 추세 감지
여기에서는 EURUSD D1 차트와 기간이 200(굵은 빨간색 선), 50(중간 두께의 노란색 선)이 있는 단순 이동 평균을 사용합니다. 21(가는 보라색 선), 종가를 기반으로 합니다.
그림 하단에서 특별히 개발된 추세 표시기인 FanTrendDetector를 볼 수 있습니다. 추세 방향은 0축을 기준으로 한 표시기 히스토그램의 포지션로 표시됩니다. +1은 상승 추세에 해당합니다. -1은 하락 추세입니다. 히스토그램 값이 0이면 추세를 감지할 수 없음을 의미합니다. 비교를 위한 MATrendDetector 표시기도 있습니다.
추세 변화에 대한 오경보의 수가 감소한 것이 분명합니다. 그러나 추세 감지 지연이 증가했습니다. 모든 이동 평균이 "올바른" 순서로 정렬될 때까지 시간이 걸릴 수 있습니다. 더 나은 것과 그렇지 않은 것은 이러한 방법을 사용하는 거래 시스템에 따라 다릅니다.
이 경우 평균의 기간 값은 어떻게 해서든 선택되지 않지만 trader와 글 작성자가 가장 널리 사용합니다. 평균 세트와 그 수를 선택하여 특정 통화 쌍에 대한 이 추세 감지 방법의 특성을 개선할 수 있습니다.
2.3. 지그재그 표시기의 최대값과 최소값을 사용한 추세 감지
이제 기술 분석 고전의 관점에서 추세 감지에 접근해 보겠습니다. 즉, Charles Dow의 다음 규칙을 사용합니다.
- 가격 차트의 모든 다음 로컬 최대값이 이전 로컬 최대값보다 높고 가격 차트의 각 후속 로컬 최소값도 이전 로컬 최소값보다 높은 경우 추세 상승.
- 가격 차트의 각 후속 로컬 최소값이 이전 로컬 최소값보다 낮고 가격 차트의 각 후속 로컬 최대값도 이전 로컬 최대값보다 낮은 경우 추세 하락.
지그재그 표시기의 상단에서 로컬 최대값/최소값을 찾습니다.
이 방법의 실례를 들어봅시다.
그림 4. 지그재그 표시기를 사용한 추세 감지
여기서 EURUSD D1 차트와 다음 매개변수와 지그재그를 사용합니다. ExtDepth = 5, ExtDeviation = 5, ExtBackstep = 3.
그림 하단에서 특별히 개발된 추세 표시기인 ZigZagTrendDetector를 볼 수 있습니다.
이 추세 감지 방법의 주요 단점은 실시간으로 극값이 이미 형성되었는지 여부를 이해하는 것이 불가능합니다. 내역에서 극단은 매우 잘 볼 수 있으며 그들이 형성된 곳을 이해할 수 있습니다. 그러나 실시간으로 가격이 변할 때 형성된 극값이 갑자기 사라지거나 다시 나타날 수 있습니다. 이를 보려면 모든 expert의 시각적 테스트 모드에서 플로팅되는 지그재그 선을 보십시오.
이 결점은 이 방법을 거래에서 실용적으로 사용할 가치가 없게 만듭니다. 그러나 패턴을 찾고 다양한 거래 시스템의 품질을 평가하기 위해 과거 데이터의 기술적 분석에 매우 유용합니다.
2.4. ADX 표시기를 사용한 추세 감지
다음 고려 방법은 ADX 표시기(평균 방향 이동 지수)를 사용한 추세 감지입니다. 이 지표는 추세 방향을 감지할 뿐만 아니라 그 강도를 평가하는 데에도 사용됩니다. 이것은 ADX 표시기의 매우 유용한 기능입니다. 추세의 강도는 주요 ADX 선에 의해 결정됩니다. 값이 20보다 크면(일반적으로 허용되는 수준이지만 현재로서는 최고가 아닐 수도 있음) 추세가 충분히 강한 것입니다.
추세의 방향은 서로에 대한 +DI 및 -DI 선에 의해 결정됩니다. 이 지표는 지수 평균을 사용하여 세 라인 모두의 평활화를 사용하므로 추세 변화에 대한 응답이 지연됩니다.
추세 감지 규칙을 공식화해 보겠습니다.
- +DI선이 -DI선보다 높으면 추세가 상승됩니다.
- +DI선이 -DI선보다 낮으면 추세가 하락합니다.
이 경우 ADX 추세선은 추세를 감지하는 데 사용되지 않습니다. 이 표시기의 잘못된 신호 수를 줄이는 것이 필요합니다. 추세가 약하면(ADX가 20 미만) 강해질 때까지 기다렸다가 추세와 함께 거래를 시작하는 것이 가장 좋습니다.
이 방법의 실례를 들어봅시다.
그림 5. ADX 지표를 사용하여 추세 식별
여기에서는 EURUSD D1 차트와 다음 매개변수와 함께 ADX 표시기를 사용합니다. PeriodADX = 21(굵은 파란색 선 - ADX 추세 강도 값, 가는 녹색 선 - 값 + DI, 가는 빨간색 선 - -DI 값).
그림 하단에서 특별히 개발된 추세 표시기인 ADXTrendDetector를 볼 수 있습니다. 비교를 위해 ADXTrendDetector 표시기의 위쪽 차트(진홍색)에서는 추세 강도 필터가 비활성화되었으며(ADXTrendLevel = 0) 아래쪽 차트(파란색)에서는 - 활성화되었습니다(ADXTrendLevel = 20).
추세 강도 필터를 켰을 때 추세 방향을 감지하는 데 있어 소위 "바운스" 부분이 누락되었습니다. 실제 작업에서 이 필터를 사용하는 것이 바람직합니다. 시장의 현재 상황(평면/범위/추세)과 통화 쌍 움직임의 특성에 따라 외부 매개변수를 능숙하게 선택하여 지표 품질을 더욱 향상시킬 수 있습니다.
일반적으로 이 지표는 입력 필터로 추세 추적 거래 시스템을 구축할 수 있는 좋은 기회를 제공합니다.
2.5. NRTR 표시기를 사용한 추세 감지
추세를 감지하는 다음 방법 - NRTR(Nick Rypock Trailing Reverse) 표시기를 사용합니다. 이 지표는 항상 도달한 가격 극단에서 일정한 거리에 있습니다. 즉, 상승 추세에서는 더 낮은 가격이, 하락 추세에서는 더 높은 가격이 적용됩니다. 이 지표의 주요 아이디어 - 주요 추세에 대한 작은 수정 움직임은 무시되어야하며 특정 수준을 초과하는 주요 추세에 대한 움직임은 추세 방향 변경에 대한 신호입니다.
이 진술에서 추세 방향 감지 규칙이 나옵니다.
- 추세 상승 - 표시선이 바 클로징의 상승 추세에 해당하는 경우.
- 추세 하락 - 표시선이 바 클로징의 하락 추세에 해당하는 경우.
잘못된 추세 반전이 가격 변동에 미치는 영향을 줄이기 위해 종가를 사용하여 NRTR 라인 포지션을 확인합니다.
이 방법의 실례를 들어봅시다.
그림 6. NRTR 지표를 사용하여 추세 식별
이 큰 파란색 점은 상승 추세에 해당하고 큰 빨간색 점은 하락 추세에 해당합니다. 차트 하단에 아래에 설명된 추세 표시기 NRTRTrendDetector가 표시되었습니다.
2.6. Heiken Ashi 캔들스틱 3개를 사용한 추세 감지
추세를 감지하는 또 다른 인기 있는 방법은 Heiken Ashi 캔들스틱을 사용하는 것입니다. Heiken Ashi 차트는 수정된 일본 캔들 차트입니다. 그들의 값은 이전 캔들과 부분적으로 평균됩니다.
이 방법의 실례를 들어봅시다.
그림 7. Heiken Ashi 캔들스틱의 색상으로 추세 감지
보시다시피, 이 방법은 측면 복도에서 가격이 변동할 때 "거짓" 신호에서 자유롭지 않습니다. 그러나 더 나쁜 것은 이 표시기가 마지막 바뿐만 아니라 끝에서 두 번째 바도 다시 그릴 수 있다는 것입니다. 즉. 우리가 입력한 신호는 다음 바에서 반전될 수 있습니다. 이는 캔들스틱의 색상을 결정할 때 두 개의 바를 분석하기 때문이므로 다른 지원 신호와 함께 이 방법을 사용하는 것이 좋습니다.
3. 추세 지표
이제 추세 지표를 생성해 보겠습니다.
3.1. 이동 평균을 기반으로 한 추세 표시기
가장 쉬운 지표는 이동 평균을 기반으로 추세를 결정하는 가장 쉬운 방법입니다. 어떤 부분으로 구성되어 있는지 생각해 봅시다. 표시기의 전체 소스 코드는 글에 첨부된 MATrendDetector.MQ5 파일에 있습니다.
지표 프로그램의 시작 부분에는 다양한 이동 평균을 계산하기 위해 라이브러리를 연결하는 라인이 있습니다. 이 라이브러리는 클라이언트 터미널과 함께 제공되며 설치 후 즉시 사용할 수 있습니다. 다음은 이 라인입니다.
#include <MovingAverages.mqh>
여기에서 간단한 이동 평균을 계산하는 하나의 함수를 사용할 것입니다.
double SimpleMA(const int position, const int period, const double &price[])
여기에서 입력 매개변수를 정의합니다.
- position - 계산을 시작하는 price[] 배열의 초기 인덱스.
- 기간 - 이동 평균의 기간으로 0보다 커야 합니다.
- price[] - 차트에 표시기를 배치하는 동안 지정된 가격 범위를 포함하는 배열입니다. 기본적으로 종가[] 바 종가가 사용됩니다.
이 함수는 계산된 이동 평균 값을 반환합니다.
텍스트의 다음 부분에는 화면에 표시기를 표시하기 위한 초기 설정이 포함되어 있습니다.
//--------------------------------------------------------------------- #property indicator_separate_window //--------------------------------------------------------------------- #property indicator_applied_price PRICE_CLOSE #property indicator_minimum -1.4 #property indicator_maximum +1.4 //--------------------------------------------------------------------- #property indicator_buffers 1 #property indicator_plots 1 //--------------------------------------------------------------------- #property indicator_type1 DRAW_HISTOGRAM #property indicator_color1 Black #property indicator_width1 2 //---------------------------------------------------------------------
다음 매개변수가 설정됩니다.
- #property indicator_separate_window는 MetaTrader 5 터미널에 별도의 창에 표시 차트를 표시하도록 지시합니다.
- #property indicator_applied_price PRICE_CLOSE - 기본적으로 사용되는 가격 유형입니다.
- #property indicator_minimum -1.4 - 표시기 창에 표시되는 세로 축의 최소값입니다.
- #property indicator_maximum +1.4 - 표시기 창에 표시되는 수직 축의 최대값입니다.
마지막 두 매개변수를 사용하면 표시기 차트를 표시하기 위해 고정 척도를 설정할 수 있습니다. 이것은 지표의 최소값과 최대값(-1에서 +1까지 포함)을 알고 있기 때문에 가능합니다. 이것은 창의 창 테두리와 표시기 제목이 겹치지 않고 차트가 보기 좋게 보이기 위해 수행됩니다.
- #property indicator_buffers 1 - 표시기 계산을 위한 버퍼 수. 우리는 하나의 버퍼만 사용합니다.
- #property indicator_plots 1 - 지표의 그래픽 시리즈 수. 화면에는 하나의 차트만 표시됩니다.
- #property indicator_type1 DRAW_HISTOGRAM - 표시기 차트를 히스토그램으로 표시합니다.
- #property indicator_color1 검정 - 지표 차트의 기본 색상입니다.
- #property indicator_width1 2 - 지표 차트의 선 너비, 이 경우 히스토그램 열의 너비입니다.
다음은 지표의 외부 매개변수를 입력하는 부분으로, 지표를 차트에 배치하는 동안 및 나중에 작동할 때 변경할 수 있습니다.
input int MAPeriod = 200;
이동 평균 기간의 값이라는 하나의 매개 변수만 있습니다.
표시기의 다음 필수 부분 - 표시기가 차트에서 작동할 때 발생하는 다양한 이벤트를 처리하는 기능입니다.
첫 번째는 초기화 함수인 OnInit()입니다. 표시기를 로드한 직후에 호출됩니다. 지표에서 다음과 같이 보입니다.
void OnInit() { SetIndexBuffer( 0, TrendBuffer, INDICATOR_DATA ); PlotIndexSetInteger( 0, PLOT_DRAW_BEGIN, MAPeriod ); }
SetIndexBuffer() 함수는 표시기 버퍼 중 하나와 함께 추세 TrendBuffer[] 값을 저장할 이전에 선언된 배열을 바인딩합니다. 지표 버퍼는 하나만 있고 인덱스는 0입니다.
PlotIndexSetInteger() 함수는 지시창에 표시하지 않고 초기 바의 수를 설정합니다.
수학적으로 불가능하기 때문에 기간보다 작은 바의 수에 대한 단순 이동 평균을 계산하기 때문에 이동 평균 기간과 동일한 바의 수를 지정해 보겠습니다.
다음은 표시기를 다시 계산해야 할 필요성에 대한 이벤트를 처리하는 함수입니다. OnCalculate():
int OnCalculate(const int _rates_total, const int _prev_calculated, const int _begin, const double& _price[ ] ) { int start, i; // If number of bars on the screen is less than averaging period, calculations can't be made: if( _rates_total < MAPeriod ) { return( 0 ); } // Determine the initial bar for indicator buffer calculation: if( _prev_calculated == 0 ) { start = MAPeriod; } else { start = _prev_calculated - 1; } // Loop of calculating the indicator buffer values: for( i = start; i < _rates_total; i++ ) { TrendBuffer[ i ] = TrendDetector( i, _price ); } return( _rates_total ); }
이 함수는 지표 초기화 후 처음으로 호출되며 가격 데이터가 변경될 때마다 호출됩니다. 예를 들어, 지표가 계산되는 기호에 새 눈금이 표시될 때입니다. 자세히 살펴보겠습니다.
먼저 차트에 충분한 바의 수가 있는지 확인합니다. 이동 평균 기간보다 작으면 계산할 것이 없고 이 함수는 반환 연산자로 종결됩니다. 바의 수가 계산에 충분하면 지표를 계산할 초기 바를 결정합니다. 이것은 모든 가격 틱에서 모든 지표 값을 다시 계산하지 않기 위해 수행됩니다.
여기서는 터미널에서 제공하는 메커니즘을 사용합니다. 핸들러 함수를 호출할 때마다 _prev_calculated 함수 인수의 값을 확인합니다. 이는 OnCalculate() 함수의 이전 호출에서 처리된 바의 수입니다. 0이면 표시기의 모든 값을 다시 계산합니다. 그렇지 않으면 인덱스가 _prev_calculated - 1인 마지막 바만 다시 계산합니다.
표시기 버퍼 값을 계산하는 루프는 for 연산자에 의해 수행됩니다. 그의 본문에서는 재계산된 표시기 버퍼의 각 값에 대해 추세 감지 기능을 TrendDetector라고 부릅니다. 따라서 이 함수만 재정의하면 추세 방향을 계산하기 위해 다른 알고리즘을 구현할 수 있습니다. 이 경우 나머지 표시기 부분은 실제로 변경되지 않은 상태로 유지됩니다(외부 매개변수가 변경될 수 있음).
이제 추세 감지 자체의 기능인 TrendDetector를 살펴보겠습니다.
int TrendDetector(int _shift, const double& _price[]) { double current_ma; int trend_direction = 0; current_ma = SimpleMA(_shift, MAPeriod, _price); if(_price[_shift] > current_ma) { trend_direction = 1; } else if(_price[_shift] < current_ma) { trend_direction = -1; } return(trend_direction); }
이 기능은 다음 작업을 수행합니다.
- _shift 인수로 설정한 바에서 시작하여 단순 이동 평균을 계산합니다. 라이브러리 함수 SimpleMA를 사용합니다.
- 이 바의 가격 값을 이동 평균 값과 비교합니다.
- 가격 값이 이동 평균 값보다 크면 1을 반환하고, 그렇지 않으면 가격 값이 이동 평균 값보다 작으면 -1을 반환하고, 그렇지 않으면 0을 반환합니다.
함수가 0을 반환하면 추세를 감지할 수 없음을 의미합니다.
지표 작업의 결과는 그림 2 및 그림 3에서 볼 수 있습니다.
3.2. 이동 평균의 "팬"을 기반으로 하는 추세 표시기
이제 이 지표를 기반으로 이동 평균의 "팬"을 사용하여 추세를 감지하는 좀 더 복잡한 지표를 만드는 방법을 살펴보겠습니다.
표시기의 전체 소스 코드는 글에 첨부된 FanTrendDetector.MQ5 파일에 있습니다.
이 지표와 이전 지표의 차이점은 다음과 같습니다.
- 3개의 이동 평균 기간은 외부 매개변수에서 설정됩니다.
input int MA1Period = 200; // period value of senior moving average input int MA2Period = 50; // period value of medium moving average input int MA3Period = 21; // period value of junior moving average
- 또 다른 TrendDetector 기능:
int TrendDetector(int _shift, const double& _price[]) { double current_ma1, current_ma2, current_ma3; int trend_direction = 0; current_ma1 = SimpleMA(_shift, MA1Period, _price); current_ma2 = SimpleMA(_shift, MA2Period, _price); current_ma3 = SimpleMA(_shift, MA3Period, _price); if(current_ma3 > current_ma2 && current_ma2 > current_ma1) { trend_direction = 1; } else if(current_ma3 < current_ma2 && current_ma2 < current_ma1) { trend_direction = -1; } return(trend_direction); }
이 함수는 if...else 연산자와 그 순서를 사용하여 이동 평균을 서로 비교하여 이동 평균이 올바른 순서로 있는지 확인합니다. 평균이 오름차순으로 정렬되면 1 - 상승세를 반환합니다. 평균이 내림차순으로 정렬되면 -1 - 하락세를 반환합니다. if 블록에서 확인된 두 조건이 모두 거짓이면 0을 반환합니다(추세를 감지할 수 없음). 함수에는 두 개의 입력 인수가 있습니다. 분석된 바의 버퍼 이동과 가격 계열이 있는 버퍼 자체입니다.
나머지 표시 부분은 이전과 동일합니다.
3.3. 지그재그 인디케이터 기반 추세 인디케이터
이제 지그재그의 골절을 사용하여 극한값을 결정하고 Charles Dow에 따라 추세 방향을 감지하는 지표를 살펴보겠습니다. 표시기의 전체 소스 코드는 글에 첨부된 ZigZagTrendDetector.MQ5 파일에 있습니다.
외부 변수에는 외부 표시기 ZigZag의 매개변수 값이 할당됩니다.
//--------------------------------------------------------------------- // External parameters: //--------------------------------------------------------------------- input int ExtDepth = 5; input int ExtDeviation = 5; input int ExtBackstep = 3; //---------------------------------------------------------------------
이 표시기의 중요한 차이점은 표시기 버퍼의 수입니다. 여기에서는 디스플레이 버퍼 외에 두 개의 계산 버퍼를 더 사용합니다. 따라서 표시기 코드에서 적절한 설정을 변경했습니다.
#property indicator_buffers 3
두 개의 추가 버퍼를 추가합니다. 외부 표시기 지그재그에서 얻은 극값을 저장합니다.
double ZigZagHighs[]; // zigzag's upper turnarounds double ZigZagLows[]; // zigzag's lower turnarounds
또한 표시기 초기화 이벤트 핸들러를 변경해야 합니다. 다음 두 개의 추가 버퍼를 계산 버퍼로 설정합니다.
// Buffers to store zigzag's turnarounds SetIndexBuffer(1, ZigZagHighs, INDICATOR_CALCULATIONS); SetIndexBuffer(2, ZigZagLows, INDICATOR_CALCULATIONS);
OnCalculate 함수의 계산 코드에서도 지그재그 균열을 버퍼에 읽어들이도록 해야 합니다. 이것은 다음과 같이 수행됩니다.
// Copy upper and lower zigzag's turnarounds to buffers: CopyBuffer(indicator_handle, 1, 0, _rates_total - _prev_calculated, ZigZagHighs); CopyBuffer(indicator_handle, 2, 0, _rates_total - _prev_calculated, ZigZagLows); // Loop of calculating the indicator buffer values: for(i = start; i < _rates_total; i++) { TrendBuffer[i] = TrendDetector(i); }
TrendDetector 기능은 다음과 같습니다.
//--------------------------------------------------------------------- // Determine the current trend direction: //--------------------------------------------------------------------- // Returns: // -1 - Down trend // +1 - Up trend // 0 - trend is not defined //--------------------------------------------------------------------- double ZigZagExtHigh[2]; double ZigZagExtLow[2]; //--------------------------------------------------------------------- int TrendDetector(int _shift) { int trend_direction = 0; // Find last four zigzag's turnarounds: int ext_high_count = 0; int ext_low_count = 0; for(int i = _shift; i >= 0; i--) { if(ZigZagHighs[i] > 0.1) { if(ext_high_count < 2) { ZigZagExtHigh[ext_high_count] = ZigZagHighs[i]; ext_high_count++; } } else if(ZigZagLows[i] > 0.1) { if(ext_low_count < 2) { ZigZagExtLow[ext_low_count] = ZigZagLows[i]; ext_low_count++; } } // If two pairs of extrema are found, break the loop: if(ext_low_count == 2 && ext_high_count == 2) { break; } } // If required number of extrema is not found, the trend can't be determined: if(ext_low_count != 2 || ext_high_count != 2) { return(trend_direction); } // Check Dow's condition fulfillment: if(ZigZagExtHigh[0] > ZigZagExtHigh[1] && ZigZagExtLow[0] > ZigZagExtLow[1]) { trend_direction = 1; } else if(ZigZagExtHigh[0] < ZigZagExtHigh[1] && ZigZagExtLow[0] < ZigZagExtLow[1]) { trend_direction = -1; } return(trend_direction); }
여기서 우리는 마지막 4개의 지그재그 극값을 검색합니다. 검색은 내역를 거슬러 올라갑니다. 이것이 for 루프의 인덱스가 각 검색 반복에서 0으로 감소하는 이유입니다. 극값이 발견되면 Dow에 따라 추세 정의의 일관성을 위해 서로 비교됩니다. 상승 추세와 하락 추세의 두 가지 가능한 극한 포지션이 있습니다. 이러한 변형은 if...else 연산자에 의해 확인됩니다.
3.4. 경향 지표 기반 ADX 지표
ADX 표시기를 사용하는 ADXTrendDetector 추세 표시기를 고려하십시오. 표시기의 전체 소스 코드는 글에 첨부된 ADXTrendDetector.MQ5 파일에 있습니다. 외부 매개변수에는 외부 표시기 ADX 값이 할당됩니다.
//--------------------------------------------------------------------- // External parameters //--------------------------------------------------------------------- input int PeriodADX = 14; input int ADXTrendLevel = 20;
TrendDetector 기능은 다음과 같습니다.
//--------------------------------------------------------------------- // Determine the current trend direction: //--------------------------------------------------------------------- // Returns: // -1 - Down trend // +1 - Up trend // 0 - trend is not defined //--------------------------------------------------------------------- int TrendDetector(int _shift) { int trend_direction = 0; double ADXBuffer[ 1 ]; double PlusDIBuffer[ 1 ]; double MinusDIBuffer[ 1 ]; // Copy ADX indicator values to buffers: CopyBuffer(indicator_handle, 0, _shift, 1, ADXBuffer); CopyBuffer(indicator_handle, 1, _shift, 1, PlusDIBuffer); CopyBuffer(indicator_handle, 2, _shift, 1, MinusDIBuffer); // If ADX value is considered (trend strength): if(ADXTrendLevel > 0) { if(ADXBuffer[0] < ADXTrendLevel) { return(trend_direction); } } // Check +DI and -DI positions relative to each other: if(PlusDIBuffer[0] > MinusDIBuffer[0]) { trend_direction = 1; } else if(PlusDIBuffer[0] < MinusDIBuffer[0]) { trend_direction = -1; } return( trend_direction ); }
CopyBuffer()를 사용하여 _shift 인수에 의해 주어진 바의 수에 대해 외부 표시기 ADX에서 표시기 버퍼의 필요한 값을 가져옵니다. 다음으로, 서로에 대한 +DI 및 -DI 라인의 포지션을 분석합니다. 필요한 경우 추세 강도를 고려합니다. 정의된 것보다 작으면 추세가 감지되지 않습니다.
3.5. 추세 지표 기반 NTRT 지표
NRTR을 기반으로 하는 NRTRTrendDetector 추세 표시기의 구조는 이전 구조와 유사합니다. 표시기의 전체 소스 코드는 글에 첨부된 NRTRTrendDetector.MQ5 파일에 있습니다.
첫 번째 차이점 - 외부 매개변수 블록:
//--------------------------------------------------------------------- // External parameters: //--------------------------------------------------------------------- input int ATRPeriod = 40; // ATR period, in bars input double Koeff = 2.0; // Coefficient of ATR value change //---------------------------------------------------------------------
두 번째 차이점 - 추세 방향을 감지하는 TrendDetector 기능:
//--------------------------------------------------------------------- // Determine the current trend direction: //--------------------------------------------------------------------- // Returns: // -1 - Down trend // +1 - Up trend // 0 - trend is not defined //--------------------------------------------------------------------- int TrendDetector(int _shift) { int trend_direction = 0; double Support[1]; double Resistance[1]; // Copy NRTR indicator values to buffers:: CopyBuffer(indicator_handle, 0, _shift, 1, Support); CopyBuffer(indicator_handle, 1, _shift, 1, Resistance); // Check values of indicator lines: if(Support[0] > 0.0 && Resistance[0] == 0.0) { trend_direction = 1; } else if(Resistance[0] > 0.0 && Support[0] == 0.0) { trend_direction = -1; } return( trend_direction ); }
여기에서 인덱스가 0과 1인 외부 표시기 NRTR의 두 버퍼에서 값을 읽습니다. Support 버퍼의 값은 상승 추세가 있을 때 0과 다르며, Resistance 버퍼의 값은 하락 추세가 있을 때 0과 다릅니다.
3.6. Heiken Ashi 캔들스틱을 기반으로 한 추세 표시기
이제 Heiken Ashi 캔들스틱을 사용하는 추세 표시기를 살펴보겠습니다.
이 경우 외부 표시기를 호출하지 않고 자체적으로 캔들을 계산합니다. 이렇게 하면 표시기 성능이 향상되고 더 중요한 작업을 위한 CPU 여유 공간이 생깁니다. 표시기의 전체 소스 코드는 글에 첨부된 HeikenAshiTrendDetector.MQ5 파일에 있습니다.
Heiken Ashi 표시기는 외부 매개변수 설정을 가정하지 않으므로 입력 연산자로 블록을 제거할 수 있습니다. 표시기 재계산 이벤트 처리기에서 주요 변경 사항이 우리를 기다리고 있습니다. 여기에서는 현재 차트의 모든 가격 배열에 대한 액세스를 제공하는 핸들러의 대체 변형을 사용할 것입니다.
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& TickVolume[], const long& Volume[], const int& Spread[]) { int start, i; double open, close, ha_open, ha_close; // Determine the initial bar for indicator buffer calculation: if(_prev_calculated == 0) { open = Open[0]; close = Close[0]; start = 1; } else { start = _prev_calculated - 1; } // Loop of calculating the indicator buffer values: for(i = start; i < _rates_total; i++) { // Heiken Ashi candlestick open price: ha_open = (open + close) / 2.0; // Heiken Ashi candlestick close price: ha_close = (Open[i] + High[i] + Low[i] + Close[i]) / 4.0; TrendBuffer[i] = TrendDetector(ha_open, ha_close); open = ha_open; close = ha_close; } return(_rates_total); }
Heiken Ashi 캔들의 색상을 결정할 때 시가와 종가의 두 가지 가격만 있으면 됩니다. 그런 다음 이 가격만 계산하면 됩니다.
TrendDetector 함수 호출을 통해 추세 방향을 감지한 후 Heiken Ashi 캔들스틱의 현재 가격 값을 중간 변수 open과 close에 저장합니다. TrendDetector 기능은 매우 간단해 보입니다. OnCalculate에 삽입할 수 있지만 알고리즘 추가 개발 및 복잡성의 경우 더 큰 다양성을 위해 이 기능을 그대로 둡니다. 다음은 이 기능입니다.
int TrendDetector(double _open, double _close) { int trend_direction = 0; if(_close > _open) // if candlestick is growing, then it is the up trend { trend_direction = 1; } else if(_close < _open) // if candlestick is falling, then it is the down trend { trend_direction = -1; } return(trend_direction); }
함수 인수는 Heiken Ashi 캔들스틱에 대한 두 가지 가격(시가 및 종가)이며, 이에 따라 방향이 결정됩니다.
4. Expert에서 Trend Detection Indicator 사용 예
다양한 지표를 사용하는 Expert Advisor를 만들어 보겠습니다. 다양한 경향 감지 방법을 사용하는 expert의 결과를 비교하는 것은 흥미로울 것입니다. 먼저 기본 매개변수로 결과를 확인한 다음 조정하여 최상의 매개변수를 찾습니다.
이 경우 Expert Advisors를 만드는 목적은 추세 감지 방법을 정확도와 속도별로 비교하는 것입니다. 따라서 모든 Expert Advisors를 만드는 일반적인 원칙을 공식화해 보겠습니다.
- 추세가 하향에서 상향으로 또는 미정에서 상향으로 변경되면 구매 포지션이 열립니다.
- 추세가 상승에서 하락으로 또는 정의되지 않은 상태에서 하락으로 바뀔 때 매도 포지션이 열립니다.
- 추세가 반대 방향으로 또는 정의되지 않은 방향으로 바뀌면 포지션이 닫힙니다.
- 새 바가 열릴 때(해당 신호가 있을 때) Expert Advisor는 포지션을 열거나 닫아야 합니다.
우리가 만든 모든 추세 표시기는 추세 방향에 대한 필수 데이터를 저장하는 색인이 0인 표시기 버퍼를 포함합니다. 포지션을 오픈/클로즈하기 위한 신호를 얻기 위해 Expert Advisors에서 이를 사용할 것입니다.
거래 기능이 필요하기 때문에 MetaTrader 5와 함께 설치된 해당 라이브러리를 포함했습니다. 이 라이브러리에는 CTrade 클래스와 포지션 및 주문 작업을 위한 여러 방법이 포함되어 있습니다. 이것은 거래 기능에 대한 일상적인 작업을 단순화합니다. 라이브러리는 다음 줄에 포함되어 있습니다.
#include <Trade\Trade.mqh>
그 중에서 포지션 오픈과 클로징의 두 가지 방법을 사용할 것입니다. 첫 번째 방법을 사용하면 주어진 방향과 볼륨의 포지션을 열 수 있습니다.
PositionOpen(const string symbol, ENUM_ORDER_TYPE order_type, double volume, double price, double sl, double tp, const string comment )
입력 인수는 다음과 같습니다.
- 기호 - 거래를 위한 상품의 이름(예: "EURUSD").
- order_type - 포지션 개시 방향, 숏 또는 롱.
- 볼륨 - 랏에서 열린 포지션의 볼륨입니다(예: 0.10).
- 가격 - 시작 가격.
- sl - 손절매 가격.
- tp - 이익실현 가격.
- 설명 - 거래 터미널에 포지션이 표시될 때 표시되는 설명입니다.
두 번째 방법을 사용하면 포지션을 청산할 수 있습니다.
PositionClose( const string symbol, ulong deviation )
입력 인수는 다음과 같습니다.
- 기호 - 거래를 위한 상품의 이름(예: "EURUSD").
- 편차 - 포지션을 마감할 때 현재 가격(포인트)에서 허용되는 최대 편차.
MATrendDetector 표시기를 사용하는 Expert Advisor의 구조를 자세히 살펴보겠습니다. Expert Advisor의 전체 소스 코드는 글에 첨부된 MATrendExpert.MQ5 파일에 있습니다. expert의 첫 번째 주요 블록은 외부 매개변수를 설정하는 블록입니다.
input double Lots = 0.1; input int MAPeriod = 200;
Expert Advisor의 랏 (Lots) 매개변수 - 포지션이 열릴 때 사용되는 랏의 크기입니다. 다양한 추세 감지 방법의 비교 결과를 얻기 위해 자금 관리 없이 영구적인 랏을 사용합니다. 다른 모든 외부 매개변수는 위에서 설명한 추세 표시기에 사용됩니다. 목록과 목적은 해당 지표와 정확히 동일합니다.
Expert Advisor의 두 번째 중요한 블록 - Expert Advisor 초기화의 이벤트 핸들러.
//--------------------------------------------------------------------- // Initialization event handler: //--------------------------------------------------------------------- int OnInit() { // Create external indicator handle for future reference to it: ResetLastError(); indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Examples\\MATrendDetector", MAPeriod); // If initialization was unsuccessful, return nonzero code: if(indicator_handle == INVALID_HANDLE) { Print("MATrendDetector initialization error, Code = ", GetLastError()); return(-1); } return(0); }
여기에서 추세 표시기를 참조하기 위해 핸들을 생성하고 생성이 성공하면 0 코드를 반환합니다. 표시기 핸들 생성에 실패한 경우(예: 표시기가 EX5 형식으로 컴파일되지 않은 경우) 이에 대한 메시지를 프린트하고 0이 아닌 코드를 반환합니다. 이 경우 Expert Advisor는 추가 작업을 중지하고 저널에 해당 메시지와 함께 터미널에서 언로드됩니다.
Expert Advisor의 다음 블록 - Expert Advisor 초기화 해제의 이벤트 핸들러.
//--------------------------------------------------------------------- // Indicator deinitialization event handler: //--------------------------------------------------------------------- void OnDeinit(const int _reason) { // Delete indicator handle: if(indicator_handle != INVALID_HANDLE) { IndicatorRelease(indicator_handle); } }
여기서 표시기 핸들이 삭제되고 할당된 메모리가 해제됩니다.
Expert Advisor를 초기화 해제하기 위해 다른 작업을 수행할 필요가 없습니다.
다음은 현재 기호로 새 피크에 대한 이벤트 핸들러인 Expert Advisor의 메인 블록으로 이동합니다.
//--------------------------------------------------------------------- // Handler of event about new tick by the current symbol: //--------------------------------------------------------------------- int current_signal = 0; int prev_signal = 0; bool is_first_signal = true; //--------------------------------------------------------------------- void OnTick() { // Wait for beginning of a new bar: if(CheckNewBar() != 1) { return; } // Get signal to open/close position: current_signal = GetSignal(); if(is_first_signal == true) { prev_signal = current_signal; is_first_signal = false; } // Select position by current symbol: if(PositionSelect(Symbol()) == true) { // Check if we need to close a reverse position: if(CheckPositionClose(current_signal) == 1) { return; } } // Check if there is the BUY signal: if(CheckBuySignal(current_signal, prev_signal) == 1) { CTrade trade; trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK ), 0, 0); } // Check if there is the SELL signal: if(CheckSellSignal(current_signal, prev_signal) == 1) { CTrade trade; trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID ), 0, 0); } // Save current signal: prev_signal = current_signal; }
Expert Advisor에서 사용하는 보조 기능을 살펴봅시다.
우선, Expert Advisor는 차트에서 또 다른 새로운 바를 열려면 신호를 확인해야 합니다. 이를 위해 CheckNewBar 기능이 사용됩니다.
//--------------------------------------------------------------------- // Returns flag of a new bar: //--------------------------------------------------------------------- // - if it returns 1, there is a new bar //--------------------------------------------------------------------- int CheckNewBar() { MqlRates current_rates[1]; ResetLastError(); if(CopyRates(Symbol(), Period(), 0, 1, current_rates)!= 1) { Print("CopyRates copy error, Code = ", GetLastError()); return(0); } if(current_rates[0].tick_volume>1) { return(0); } return(1); }
새 바의 존재는 틱 볼륨 값에 의해 결정됩니다. 새 바를 열 때 볼륨은 처음에 0과 같습니다(따옴표가 없었기 때문에). 새로운 틱이 나오면 크기는 1이 됩니다.
이 함수에서 하나의 요소로 구성된 MqlRates 구조의 current_rates[] 배열을 만들고 여기에 현재 가격 및 볼륨 정보를 복사한 틱 볼륨 값을 확인합니다.
현재 기호에 의한 새 틱에 대한 이벤트 핸들러에서 이 함수를 다음과 같이 사용합니다.
// Wait for beginning of a new bar: if(CheckNewBar()!= 1) { return; }
따라서 새 바가 열리고 현재 추세 방향에 대한 신호를 얻을 수 있습니다. 이것은 다음과 같이 수행됩니다.
// Get signal to open/close position: current_signal = GetSignal(); if(is_first_signal == true) { prev_signal = current_signal; is_first_signal = false; }
추세의 변화를 추적해야 하므로 이전 바의 추세 값을 기억해야 합니다. 위의 코드에서 이를 위해 prev_signal 변수를 사용합니다. 또한 플래그를 사용하여 이것이 첫 번째 신호임을 알려야 합니다(아직 이전 신호가 없음). 이것은 is_first_signal 변수입니다. 플래그 값이 true이면 prev_signal 변수를 초기 값으로 초기화합니다.
여기에서 지표에서 얻은 현재 추세 방향을 반환하는 GetSignal 함수를 사용합니다. 다음과 같습니다.
//--------------------------------------------------------------------- // Get signal to open/close position: //--------------------------------------------------------------------- int GetSignal() { double trend_direction[1]; // Get signal from trend indicator: ResetLastError(); if(CopyBuffer(indicator_handle, 0, 0, 1, trend_direction) != 1) { Print("CopyBuffer copy error, Code = ", GetLastError()); return(0); } return((int)trend_direction[0]); }
추세 표시기의 데이터는 제로 버퍼에서 하나의 요소로 구성된 배열 trend_direction으로 복사됩니다. 그리고 배열 요소의 값은 함수에서 반환됩니다. 또한 double 유형은 컴파일러 경고를 피하기 위해 int 유형으로 캐스트됩니다.
새로운 포지션을 열기 전에 먼저 오픈한 반대 포지션을 청산할 필요가 있는지 확인해야 합니다. 같은 방향으로 이미 열린 포지션이 있는지도 확인해야 합니다. 이 모든 작업은 다음 코드로 수행됩니다.
// Select position by current symbol: if(PositionSelect(Symbol()) == true) { // Check if we need to close a reverse position: if(CheckPositionClose(current_signal) == 1) { return; } }
포지션에 액세스하려면 먼저 포지션을 선택해야 합니다. 이는 현재 기호에 대한 PositionSelect() 함수를 사용하여 수행됩니다. 함수가 true를 반환하면 포지션이 존재하고 성공적으로 선택되었으므로 조작할 수 있습니다.
반대 포지션을 닫으려면 CheckPositionClose 함수가 사용됩니다.
//--------------------------------------------------------------------- // Check if we need to close position: //--------------------------------------------------------------------- // Returns: // 0 - no open position // 1 - position already opened in signal's direction //--------------------------------------------------------------------- int CheckPositionClose(int _signal) { long position_type = PositionGetInteger(POSITION_TYPE); if(_signal == 1) { // If there is the BUY position already opened, then return: if(position_type == (long)POSITION_TYPE_BUY) { return(1); } } if(_signal==-1) { // If there is the SELL position already opened, then return: if( position_type == ( long )POSITION_TYPE_SELL ) { return(1); } } // Close position: CTrade trade; trade.PositionClose(Symbol(), 10); return(0); }
먼저 포지션이 추세 방향으로 열려 있는지 확인하십시오. 그렇다면 함수는 1을 반환하고 현재 포지션는 닫히지 않습니다. 반대 추세 방향으로 포지션이 열려 있으면 반드시 닫아야 합니다. 이것은 위에서 설명한 PositionClose 메소드에 의해 수행됩니다. 열린 포지션이 더 이상 없으므로 0을 반환합니다.
기존 포지션에 대해 필요한 모든 확인 및 조치가 완료되면 새로운 신호가 있는지 확인해야 합니다. 이것은 다음 코드에 의해 수행됩니다.
// Check if there is the BUY signal: if(CheckBuySignal(current_signal, prev_signal)==1) { CTrade trade; trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK), 0, 0); }
매수 신호가 있는 경우 현재 가격 SYMBOL_ASK에 따라 주어진 거래량으로 롱 포지션을 엽니다. 모든 포지션이 반대 신호에 의해 닫히기 때문에 이익실현 및 손절매는 사용되지 않습니다. Expert Advisor는 "항상 시장에" 있습니다.
실제 거래에서는 DC 서버와의 연결 끊김 및 기타 불가항력 조건과 같은 예기치 않은 상황의 경우 보수적인 손절매를 사용하는 것이 좋습니다.
매도 신호의 경우 모든 것이 유사합니다.
// Check if there is the SELL signal: if(CheckSellSignal(current_signal, prev_signal) == 1) { CTrade trade; trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID), 0, 0); }
유일한 차이점은 판매 가격(SYMBOL_BID)입니다.
신호의 존재 여부는 CheckBuySignal 기능(구매)과 CheckSellSignal 기능(판매)에 의해 확인됩니다. 이러한 기능은 매우 간단하고 명확합니다.
//--------------------------------------------------------------------- // Check if signal has changed to BUY: //--------------------------------------------------------------------- // Returns: // 0 - no signal // 1 - there is the BUY signal //--------------------------------------------------------------------- int CheckBuySignal(int _curr_signal, int _prev_signal) { // Check if signal has changed to BUY: if((_curr_signal==1 && _prev_signal==0) || (_curr_signal==1 && _prev_signal==-1)) { return(1); } return(0); } //--------------------------------------------------------------------- // Check if there is the SELL signal: //--------------------------------------------------------------------- // Returns: // 0 - no signal // 1 - there is the SELL signal //--------------------------------------------------------------------- int CheckSellSignal(int _curr_signal, int _prev_signal) { // Check if signal has changed to SELL: if((_curr_signal==-1 && _prev_signal==0) || (_curr_signal==-1 && _prev_signal==1)) { return(1); } return(0); }
여기서 우리는 추세가 반대 방향으로 변경되었는지 또는 추세 방향이 나타났는지 확인합니다. 이러한 조건 중 하나라도 충족되면 함수는 신호 존재를 반환합니다.
일반적으로 Expert Advisor의 이러한 체계는 보다 복잡한 알고리즘에 맞게 쉽게 업그레이드 및 확장할 수 있는 매우 보편적인 구조를 제공합니다.
다른 Expert Advisor는 완전히 동일하게 구축됩니다. 외부 매개변수 블록에만 중요한 차이점이 있습니다. 사용된 추세 표시기와 일치해야 하며 표시기 핸들을 생성할 때 인수로 전달되어야 합니다.
이력 데이터에 대한 첫 번째 Expert Advisor의 결과를 살펴보겠습니다. 우리는 일일 바에서 01.04.2004에서 06.08.2010 사이의 EURUSD 기록을 사용할 것입니다. 기본 매개변수를 사용하여 Strategy Tester에서 Expert Advisor를 실행한 후 다음 결과를 얻습니다.
그림 8. MATrendDetector 표시기를 사용한 Expert Advisor의 테스트 결과
전략 테스터 보고서 | ||||||||||||
MetaQuotes-데모(빌드 302) | ||||||||||||
설정 | ||||||||||||
expert: | MATrendExpert | |||||||||||
상징: | EURUSD | |||||||||||
기간: | 매일 (2004.04.01 - 2010.08.06) | |||||||||||
입력: | 랏=0.100000 | |||||||||||
MA기간=200 | ||||||||||||
브로커: | MetaQuotes Software Corp. | |||||||||||
통화: | USD | |||||||||||
초기 입금: | 10 000.00 | |||||||||||
결과 | ||||||||||||
바: | 1649 | 눈금: | 8462551 | |||||||||
총 순이익: | 3 624.59 | 총 이익: | 7 029.16 | 총 손실: | -3 404.57 | |||||||
이익 계수: | 2.06 | 예상 수익: | 92.94 | |||||||||
회복 계수: | 1.21 | 샤프 비율: | 0.14 | |||||||||
잔액 감소: | ||||||||||||
잔액 축소 절대: | 2 822.83 | 잔액 감소 최대: | 2 822.83 (28.23%) | 잔액 하락 상대: | 28.23% (2 822.83) | |||||||
자기자본 축소: | ||||||||||||
자기자본절감 절대: | 2 903.68 | 자기자본 손실 최대치: | 2 989.93 (29.64%) | 자기자본 축소 상대: | 29.64% (2 989.93) | |||||||
총 거래: | 39 | 공매도(원 %): | 20 (20.00%) | 장기 거래(원 %): | 19 (15.79%) | |||||||
총 거래: | 78 | 이익 거래(총 %): | 7 (17.95%) | 손실 거래(전체의 %): | 32 (82.05%) | |||||||
최대 이익 거래: | 3 184.14 | 최대 손실 거래(전체 대비 %): | -226.65 | |||||||||
평균 이익 거래: | 1 004.17 | 평균 손실 거래(전체 대비 %): | -106.39 | |||||||||
최대 연속 우승($): | 4 (5 892.18) | 최대 연속 손실($): | 27 (-2 822.83) | |||||||||
최대 연속 이익(개수): | 5 892.18 (4) | 최대 연속 손실(횟수): | -2 822.83 (27) | |||||||||
평균 연속 승리: | 2 | 평균 연속 손실: | 8 | |||||||||
일반적으로 테스트 시작부터 2004년 9월 22일까지의 섹션을 제외하고는 좋아 보입니다. 앞으로 이 섹션이 반복되지 않을 것이라는 보장은 없습니다. 이 기간의 차트를 보면 제한된 범위에서 횡방향 움직임이 우세한 것을 알 수 있습니다. 이러한 상황에서 우리의 단순 추세 expert는 그렇게 좋지 않았습니다. 다음은 거래가 이루어진 이 기간의 사진입니다.
그림 9. 측면 움직임이 있는 섹션
또한 차트에 SMA200 이동 평균이 있습니다.
이제 동일한 간격과 기본 매개변수를 사용하여 여러 이동 평균이 있는 표시기를 사용하여 더 "고급" Expert Advisor를 표시하는 항목을 살펴보겠습니다.
그림 10. FanTrendDetector 표시기를 사용한 Expert Advisor의 테스트 결과
전략 테스터 보고서 | ||||||||||||
MetaQuotes-데모(빌드 302) | ||||||||||||
설정 | ||||||||||||
expert: | FanTrendExpert | |||||||||||
상징: | EURUSD | |||||||||||
기간: | 매일 (2004.04.01 - 2010.08.06) | |||||||||||
입력: | 랏=0.100000 | |||||||||||
MA1Period=200 | ||||||||||||
MA2Period=50 | ||||||||||||
MA3Period=21 | ||||||||||||
브로커: | MetaQuotes Software Corp. | |||||||||||
통화: | USD | |||||||||||
초기 입금: | 10 000.00 | |||||||||||
결과 | ||||||||||||
바: | 1649 | 눈금: | 8462551 | |||||||||
총 순이익: | 2 839.63 | 총 이익: | 5 242.93 | 총 손실: | -2 403.30 | |||||||
이익 계수: | 2.18 | 예상 수익: | 149.45 | |||||||||
회복 계수: | 1.06 | 샤프 비율: | 0.32 | |||||||||
잔액 감소: | ||||||||||||
잔액 축소 절대: | 105.20 | 잔액 감소 최대: | 1 473.65 (11.73%) | 잔액 하락 상대: | 11.73% (1 473.65) | |||||||
자기자본 축소: | ||||||||||||
자기자본절감 절대: | 207.05 | 자기자본 손실 최대치: | 2 671.98 (19.78%) | 자기자본 축소 상대: | 19.78% (2 671.98) | |||||||
총 거래: | 19 | 공매도(원 %): | 8 (50.00%) | 장기 거래(원 %): | 11 (63.64%) | |||||||
총 거래: | 38 | 이익 거래(총 %): | 11 (57.89%) | 손실 거래(전체의 %): | 8 (42.11%) | |||||||
최대 이익 거래: | 1 128.30 | 최대 손실 거래(전체 대비 %): | -830.20 | |||||||||
평균 이익 거래: | 476.63 | 평균 손실 거래(전체 대비 %): | -300.41 | |||||||||
최대 연속 우승($): | 2 (1 747.78) | 최대 연속 손실($): | 2 (-105.20) | |||||||||
최대 연속 이익(개수): | 1 747.78 (2) | 최대 연속 손실(횟수): | -830.20 (1) | |||||||||
평균 연속 승리: | 2 | 평균 연속 손실: | 1 | |||||||||
훨씬 낫습니다! 이전 expert가 이전에 포기한 "문제"섹션을 보면 그림이 다음과 같습니다.
그림 11. 측면 움직임이 있는 단면에 대한 FanTrendExpert 결과
그림 9 와 비교해 보십시오. 추세 변화에 대한 오경보의 수가 감소했음을 알 수 있습니다. 그러나 거래 수는 절반으로 줄어들었으며 이는 매우 논리적입니다. 두 Expert Advisors의 균형/자본 곡선을 분석할 때 많은 거래가 최대 이익을 얻는 측면에서 최적보다 덜 마감되었음을 알 수 있습니다. 따라서 Expert Advisor의 다음 업그레이드는 거래 마감 알고리즘의 개선입니다. 그러나 이것은 이 글의 범위를 벗어납니다. 독자 스스로 할 수 있습니다.
5. Expert Advisor의 테스트 결과
모든 expert를 테스트해 보겠습니다. EURUSD 쌍과 기간 D1에 대한 1993년부터 2010년까지 사용 가능한 모든 기록 범위에 대한 결과가 아래에 나와 있습니다.
그림 12. MATrendExpert 테스트
그림 13. FanTrendExpert 테스트
그림 14. ADXTrendExpert 테스트(ADXTrendLevel = 0)
그림 15. ADXTrendExpert 테스트(ADXTrendLevel = 20)
그림 16. NTRTrendExpert 테스트
그림 17. Heiken Ashi 테스트
테스트 결과를 생각해 봅시다.
리더로서 가장 일반적인 Expert Advisor 둘이 있습니다. 하나는 이동 평균에서 하나는 이동 평균의 "팬"에서 말이죠. 실제로, 이 expert들은 단순히 마지막 기간 동안 평활화된 일련의 가격을 사용함으로써 추세(따라서 가격)를 따르는 규칙에 가장 가깝습니다. 기간이 200인 다소 "무거운" 이동 평균을 사용하기 때문에 시장 변동성의 영향이 줄어들 것으로 보입니다.
이러한 Expert Advisor의 낮은 거래 수는 단점이 아닙니다. 포지션 유지 기간이 200일 추세에 따라 최대 몇 개월까지 지속될 수 있기 때문입니다. 흥미롭게도 MATrendExpert는 균형이 증가하는 추세 영역을 대체하고, (expert의 맥락에서) 비용이 손실되는 평탄한 영역을 나타냅니다.
ADX 표시기의 추세 감지 방식도 좋은 결과를 보였습니다. 거기에서 PeriodADX는 17의 값으로 약간 변경되어 기록 전체에 걸쳐 더 균일한 결과를 제공합니다. 추세 강도에 따른 필터 효과는 유의하지 않습니다. ADXTrendLevel 매개변수를 조정하거나 현재 시장 변동성에 따라 동적으로 설정해야 할 수도 있습니다. 몇 번의 하락 기간이 있으므로 균형 곡선을 균등화하기 위한 추가 조치가 필요합니다.
NRTR 표시기는 전체 테스트 범위와 무작위로 선택한 긴 간격 모두에서 기본 설정을 사용하여 실질적으로 0의 수익성을 보여주었습니다. 어느 정도 이것은 이 추세 감지 방법의 안정성의 표시입니다. 아마도 매개변수를 조정하면 이 Expert Advisor를 수익성 있게 만들 수 있습니다. 즉, 최적화가 필요합니다.
Heiken Ashi에 기반을 둔 Expert Advisor는 분명히 수익성이 없었습니다. 기록상으로는 괜찮아 보이지만 실시간으로 다시 그리기 때문에 테스트 결과가 이상적이지 않습니다. 다시 그리기가 쉽지 않은 Smoothed Heiken Ashi 표시기의 부드러운 버전을 사용하면 더 나은 결과를 얻을 수 있습니다.
확실히, 모든 Expert Advisor는 동적 스톱 수준 당기기와 목표 수준 생성으로 열린 포지션을 수행하는 시스템의 이점을 누릴 것입니다. 또한 자본 관리 시스템을 갖추면 손실을 최소화하고 장기적으로 이익을 늘릴 수 있는 것이 좋습니다.
결론
따라서 추세를 감지하는 코드를 작성하는 것은 그리 어렵지 않습니다. 여기서 가장 중요한 것은 시장의 일부 법칙을 활용하는 일과 합리적인 아이디어입니다. 그리고 이러한 법률이 더 근본적일수록 이러한 법률을 기반으로 하는 거래 시스템에 대해 더 확신하게 될 것입니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/136
![거래 시스템의 평가 - 일반적 진입, 퇴출 및 거래의 효율성](https://c.mql5.com/2/0/Measure_Trade_Efficiency_MQL5.png)
![MQL5에서 다중 색상 표시기 만들기](https://c.mql5.com/2/0/paint_indicator_MQL5__1.png)
![Bill Williams의 "New Trading Dimensions"에 기반한 Expert Advisor](https://c.mql5.com/2/0/MQL5_alligator__1.png)
![자신만의 추적 손절매 만드는 법](https://c.mql5.com/2/0/Trailing_Stop_MQL5.png)