
MQL5를 사용하여 트렌드 및 차트 패턴을 감지하는 방법
소개
트레이더로서 우리 모두는 차트를 다룹니다. 그리고 올바른 결정을 내리기 위해 가격의 움직임에서 발생할 수 있는 다양한 시나리오를 이해하는 데 정확성을 높이도록 차트를 읽으려고 노력합니다. 그 이유는 차트에는 잠재적인 가격 움직임을 예측하는 데 도움이 되는 패턴이 많이 나타나기 때문입니다. 따라서 이를 쉽고 정확하게 수행하는 데 도움이 되는 유용한 도구가 있다면 우리에게 좋은 일이 될 것이라고 생각합니다. 이 글에서는 우리는 차트에 나타나는 가격 패턴을 감지할 수 있는지, 이러한 패턴은 가격의 움직임에 의해 형성될 수 있는 추세 또는 차트 패턴과 같다는 사실과 이러한 맥락에서 몇 가지 유용한 도구를 알아볼 것입니다.
우리는 다음 주제에서 이에 대해 다룰 것입니다.
이 글을 읽고 나면 고점과 저점을 감지하고 그에 따라 추세 유형, 더블 탑과 바닥을 식별할 수 있습니다. 여러분은 여기의 코드를 직접 작성해보고 실계좌에서 사용하기 전에 더 나은 인사이트와 결과를 얻기 위해 필요한 사항을 테스트하고 개발해야 합니다. 이 글의 주요 목적은 고점과 저점 및 차트의 패턴을 감지하는 주요 개념을 이해하여 알려진 또는 알려지지 않은 중요한 패턴 중 필요한 것을 감지하기 위해 코드를 점점 더 발전시키는 것입니다. 차트에서 볼 수 있는 의미 있는 패턴이 많이 있습니다. 그러므로 여러분이 이를 활용하는 방법을 이해하게 되면 거래의 판도를 바꿀 수 있을 것입니다.
이 글에서 우리는 MetaTrader 5 트레이딩 터미널에 내장된 MQL5(메타쿼츠 언어) IDE를 사용할 것입니다. MQL5 사용법을 모르지만 MetaTrader 5를 다운로드하고 MQL5를 사용하여 코드를 작성하는 방법을 배우고 싶다면 이전 글에서 MetaEditor에서 MQL5 코드 작성하기라는 항목을 읽어 보시기 바랍니다.
고점 및 저점 감지
이 부분에서는 먼저 MQL5로 차트에서 고점과 저점을 감지한 다음 이를 기반으로 각 차트 패턴에 따라 조건을 선언하겠습니다. 먼저, 고점과 저점의 정의는 다음과 같습니다:
고점:
높음은 매수세의 강세로 인해 특정 수준까지 가격이 상승한 후 매도자가 나타나 이 높은 고점에서 가격을 낮췄다는 의미입니다. 다음 그림은 그 예입니다.
저점:
저점은 매도세의 강세로 인해 특정 수준까지 하락했다가 매수세가 나타나 이 저점 수준에서 가격을 끌어올렸다는 의미입니다. 다음 그림은 그 예입니다.
이 두 가지 중요한 가격 수준에 대한 이해를 하고 우리는 이러한 유형의 움직임을 감지할 수 있는 MQL5 프로그램 또는 Expert Advisor를 만들어야 합니다. 이를 위해 사용할 수 있는 방법은 여러 가지가 있습니다. 그 중 한 가지 방법을 알려드리겠습니다.
특정 가격 수준(고가, 저가)을 결정한 다음 다른 특정 가격 수준(고가, 저가)으로 이동하여 고가와 고가, 저가 대 저가를 비교하여 또 다른 고가 또는 저가가 있는지를 확인합니다. 이를 위해서는 다음과 같은 구체적인 단계를 거쳐야 합니다.
OnTick() 밖에서 함수를 만들어 최고 또는 최저를 반환하도록 합니다. 이 함수의 이름을 정수 변수(getNextMove)로 지정합니다. 이 함수에 설정해야 하는 매개 변수는 다음과 같습니다:
- Int move: 이동이 높거나 낮은지 여부를 결정합니다.
- int count: startPos 변수와 관련된 개수를 결정합니다.
- int startPos: 시작해야 할 시작 위치를 결정합니다.
int getNextMove(int move, int count, int startPos)
우리는 이 함수 내에서 if 문을 사용하여 함수 매개 변수의 값을 식별하고 (startPos)가 0보다 작은 경우 startPos 값을 카운트 값에 더하고 0 값으로 startPos를 업데이트하여 현재 바에서 시작하도록 합니다.
if(startPos<0) { count +=startPos; startPos =0; }
이제 우리는 함수에서 (count) 및 (startPos) 변수를 식별했습니다. 함수 실행을 종료하고 이동 값을 반환하는 반환 연산자와 세 개의 표현식으로 구성된 삼항 연산자(?:)를 사용하여 첫 번째 표현식은 부울 타입의 데이터를 반환하고 이후 참이면 두 번째 표현식이 실행되고 거짓이면 세 번째 표현식이 실행됩니다. 이렇게 반환된 값에서 (move) 변수를 식별합니다.
따라서 첫 번째 연산자에서 가격 움직임 변수가 high와 같은지 여부를 지정하고, 참이면 가장 높은 값인 (High)를 반환하고 거짓이면 가장 낮은 값인 (Low)를 반환합니다.
가격 움직임이 높은지 확인하기 위해 iHighest() 및 iLowest() 함수에서 사용되는 시계열 식별자 중 하나인 MODE_HIGH 함수를 사용하여 고가를 반환합니다. 가장 높은 값과 가장 낮은 값의 인덱스를 반환하는 iHighest 및 iLowest 함수의 매개변수는 다음과 같습니다:
- symbol: Symbol()을 사용하여 현재 심볼 이름을 상수 문자열로 반환합니다.
- timeframe: Period()를 사용하여 현재 시간 프레임을 ENUM_TIMEFRAMES로 반환합니다.
- type: 이동 유형을 시계열 식별자로 반환하기 위해 (ENUM_SERIESMODE) move를 사용합니다. 이 유형은 iHighest의 경우 최고값, iLowest의 경우 최저값이 됩니다.
- count: 정수(count) 변수를 사용하여 요소의 수를 반환합니다.
- start: 정수(startPos) 변수를 사용하여 인덱스를 반환합니다.
return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
이후의 움직임을 반환하는 이 함수를 만든 후에는 현재 움직임의 높거나 낮은 값을 구하는 주요 함수가 될 또 다른 정수 함수를 만듭니다. 이 함수의 이름은 세 개의 정수 변수(move, count, startPos)를 매개변수로 사용하는 (getmove)가 됩니다.
int getmove(int move, int count, int startPos)
이 함수 내부에서 우리는 움직임이 MODE_HIGH 또는 MODE_LOW와 같지 않으면 반환되는 값이 (-1)인지 확인해야 합니다.
if(move!=MODE_HIGH && move!=MODE_LOW) return (-1);
새 정수(currentBar) 변수를 생성하고 (startPos)를 할당합니다.
int currentBar=startPos;
새로운 정수(moveReturned) 변수를 생성하고 다음 매개변수(move, (count*2+1), currentBar-count)를 사용하여 생성된 getNextMove 함수를 할당합니다.)
int moveReturned=getNextMove(move,count*2+1,currentBar-count);
표현식을 확인해야 하므로 While을 사용하여 루프를 만들고 참일 경우 연산자가 실행될 것입니다. 여기서 확인해야 할 표현식은 moveReturned가 currentBar와 같지 않은지 그리고 참이면 실행해야 하는 연산자가 무엇인지입니다:
- (move, count,currentBar+1)의 매개변수를 사용하여 getNextMove로 (currentBar) 변수를 업데이트합니다.
- (move,count*2+1,currentBar-count) 매개변수를 사용하여 getNextMove로 (moveReturned) 변수를 업데이트합니다.
while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+1); moveReturned=getNextMove(move,count*2+1,currentBar-count); }
그런 다음 반환 함수를 사용하여 currentBar 값을 반환하여 함수를 종료합니다.
return(currentBar);
그런 다음 OnTick() 내부로 이동하여 고점과 저점을 감지하는 데 도움이 되는 함수를 호출합니다. 먼저 우리는 세 개의 정수 변수를 생성할 것입니다.
int checkBars= 5; int move1; int move2;
move1의 경우 (MODE_HIGH,checkBars,0), move2의 경우 (MODE_HIGH,checkBars,move1+1) 매개변수를 사용하여 미리 생성한 getmove 함수로 move1과 move2를 업데이트하여 두 개의 고점을 감지합니다.
move1=getmove(MODE_HIGH,checkBars,0); move2=getmove(MODE_HIGH,checkBars,move1+1);
다음 단계에 따라 이 두 고점 위에 선 객체를 만듭니다:
이름이 있는 객체를 제거하는 (ObjectDelete)를 사용하여 기존 줄을 삭제합니다. 이 함수에는 매개 변수가 있는데 첫 번째 매개 변수는 차트 식별자를 결정하는 chart_id이며 현재 차트에는 0을 사용합니다. 두 번째 매개변수는 문자열로 사용할 객체 이름을 결정하기 위한 이름입니다.
ObjectDelete(0,"topLine");
새로운 객체를 생성하는 ObjectCreate 함수를 사용하여 새로운 topLine 객체를 생성합니다. 매개변수는 다음과 같습니다:
- chart_id: 차트 식별자로 긴 유형을 반환하려면 (0)을 사용합니다.
- name: "topLine"을 사용하여 객체의 이름으로 문자열 유형을 반환합니다.
- type: OBJ_TREND를 사용하여 ENUM_OBJECT 유형 또는 객체 유형을 반환합니다.
- nwin: 현재 차트의 창 인덱스로 (0)을 사용합니다.
- time1: move2 앵커의 시간을 결정하고 날짜/시간 유형을 반환합니다. iTime(Symbol(),Period(),move2)을 사용합니다.
- price1: move2 앵커의 가격을 결정하고 더블 유형을 반환합니다. iHigh(Symbol(),Period(),move2)를 사용합니다.
- timeN=0: move1 앵커의 시간을 결정하고 날짜/시간 유형을 반환합니다. iTime(Symbol(),Period(),move1)을 사용합니다.
- priceN=0: 이동1 앵커의 가격을 결정하고 더블 유형을 반환합니다. iHigh(Symbol(),Period(),move1)를 사용합니다.
보시다시피 iHigh 함수는 바의 고가를 반환하며 매개 변수는 심볼, 시간대 및 시프트입니다. iTime 함수는 바의 개장 시간을 반환하며 매개 변수는 iHigh 함수와 동일합니다.
ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1));
이 생성된 객체의 색상, 특정 너비 및 선 유형을 ObjectSetInteger 함수를 사용하여 설정합니다. 매개변수는 다음과 같습니다:
- chart_id: 차트 식별자를 결정합니다. (0)이 될 것입니다.
- name: 객체 이름입니다. 고가의 경우 "TopLine"이 됩니다.
- prop_id: 객체 속성을 결정합니다. 색상은 OBJPROP_COLOR, 너비는 OBJPROP_WIDTH, 선의 유형은 OBJPROP_RAY_RIGHT가 됩니다.
- prop_value: 원하는 값을 결정합니다. 색상은 clrRed, 너비는 3, 선 유형은 true가 됩니다.
ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
move1과 move2 변수를 고점과 동일하게 업데이트하여 두 개의 저점을 얻지만 모드는 시계열 식별자로 MODE_LOW가 됩니다.
move1=getmove(MODE_LOW,checkBars,0); move2=getmove(MODE_LOW,checkBars,move1+1);
이 두 저점 아래에 선 객체를 삭제하고 생성하는 것은 고점에 대해 수행한 작업과 동일하지만 객체 이름이 "bottomLine"이 되고 녹색이 된다는 점에서 약간의 차이가 있습니다.
ObjectDelete(0,"bottomLine"); ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1)); ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
다음은 하나의 코드 블록에 포함된 전체 코드입니다:
//+------------------------------------------------------------------+ //| moveFinder.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ void OnTick() { int checkBars= 5; int move1; int move2; move1=getmove(MODE_HIGH,checkBars,0); move2=getmove(MODE_HIGH,checkBars,move1+1); ObjectDelete(0,"topLine"); ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1)); ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true); move1=getmove(MODE_LOW,checkBars,0); move2=getmove(MODE_LOW,checkBars,move1+1); ObjectDelete(0,"bottomLine"); ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1)); ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true); } int getmove(int move, int count, int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) return (-1); int currentBar=startPos; int moveReturned=getNextMove(move,count*2+1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+1); moveReturned=getNextMove(move,count*2+1,currentBar-count); } return(currentBar); } int getNextMove(int move, int count, int startPos) { if(startPos<0) { count +=startPos; startPos =0; } return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); }
이 코드를 오류 없이 컴파일하고 실행하면 차트에서 두 개의 고점을 감지하는 빨간선과 그 아래에 두 개의 저점을 감지하는 녹색의 두 줄을 얻을 수 있습니다. 다음은 테스트의 예입니다:
이 두 선은 차트에서 가격의 움직임을 나타내는 패턴을 나타낼 수 있습니다. 앞의 예에서 보았듯이 두 개의 상승 선이 있고 위쪽 선의 각도가 아래쪽 선보다 넓기 때문에 급격한 상승 움직임이 있음을 알 수 있습니다. 따라서 이 선들은 가격 움직임을 해석하는 데 매우 유용한 도구가 될 수 있습니다.
다음은 다른 가격 움직임에 따라 다른 패턴을 가진 또 다른 예시입니다:
이전 차트에서 볼 수 있듯이 두 선이 평행하게 움직이긴 하지만 아래쪽 선은 위로, 위쪽 선은 아래로 움직이고 있다는 것은 매수세가 가격을 올리고 동시에 매도세가 가격을 낮추면서 매수세와 매도세 사이에 균형이 잡혀 있음을 나타내는 다른 가격 움직임을 나타냅니다.
다음은 또 다른 가격 행동 패턴의 예시입니다:
이전 차트에서 볼 수 있듯이 가격을 낮출 수 있는 매도세의 강도를 나타낼 수 있는 두 개의 평행한 하향선이 있기 때문에 여기서는 차트 패턴이 다릅니다.
트렌드 감지
이전 파트에서 우리는 차트에서 고점과 저점을 감지하는 방법을 배웠습니다. 이제 이 코드를 발전시켜 차트에서 추세를 감지하는 데 필요한 고점과 저점 두 개를 감지할 수 있습니다. 다음 부분에서는 이전 코드와는 약간의 차이가 있기는 하나 가능한 한 세 가지 유형의 차트에서 추세를 감지하는 코드를 개발하는 방법에 대해 설명합니다.
간단히 말해서 추세는 가격의 움직임이며 이 움직임은 상승, 하락 또는 상승도 하락도 아닌 명확한 방향이 없을 수 있습니다. 곧 다음과 같은 세 가지 유형의 추세가 있습니다:
상승 추세:
이러한 유형의 가격 변동은 시장에서 이 유형에 따라 매수세가 강세이기 때문에 가격이 계속 상승하여 더 높은 가격을 달성하게 됩니다. 따라서 차트에서 가격이 더 높은 저점과 더 높은 고점을 명확하게 형성하는 것을 확인할 수 있습니다. 다음 그림은 이러한 유형을 나타내는 그래프입니다:
하락 추세:
이 유형의 추세는 상승 추세의 유형과 반대되는 시나리오로 하락 추세 유형에서는 매도세가 매수세보다 강해 가격을 하락 시켜 가격을 낮추는 것입니다. 따라서 차트에서 가격은 더 낮은 고점과 더 낮은 저점을 형성하는 것을 볼 수 있습니다.
다음은 시각적 관점에서 이를 설명하는 그래프입니다:
횡보:
이 유형에서는 상승 추세 또는 하락 추세로 설명할 수 있는 가격 움직임을 찾을 수 없습니다. 따라서 이 유형은 상승 추세 또는 하락 추세를 제외한 모든 형태에 해당되며 다양한 형태가 있습니다. 다음 그림은 이러한 형태 중 일부입니다:
이제 우리는 추세가 있는지(상승 또는 하락) 또는 추세가 없는지(횡보)를 감지할 수 있는 MQL5 EA를 만들어야 합니다. 다음 코드는 이러한 유형의 EA를 생성하는 코드입니다:
//+------------------------------------------------------------------+ //| trendFinder.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ void OnTick() { int checkBars= 5; int high1, high2, low1, low2; double highVal1, highVal2, lowVal1, lowVal2; high1=getmove(MODE_HIGH,checkBars,0); high2=getmove(MODE_HIGH,checkBars,high1+1); highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5); ObjectDelete(0,"topLine"); ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1)); ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true); low1=getmove(MODE_LOW,checkBars,0); low2=getmove(MODE_LOW,checkBars,low1+1); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5); ObjectDelete(0,"bottomLine"); ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1)); ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true); if(lowVal1>lowVal2&&highVal1>highVal2) { Comment("Uptrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<highVal2&&lowVal1<lowVal2) { Comment("Downtrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else { Comment("Sideways", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } } int getmove(int move, int count, int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) return (-1); int currentBar=startPos; int moveReturned=getNextMove(move,count*2+1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+1); moveReturned=getNextMove(move,count*2+1,currentBar-count); } return(currentBar); } int getNextMove(int move, int count, int startPos) { if(startPos<0) { count +=startPos; startPos =0; } return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); }
다음은 추세를 감지하는 이 코드의 차이점입니다.
두 개의 고점과 두 개의 저점에 대해 4개의 정수 변수를 만들고 OnTick 함수 범위 내에서 두 개의 고점 값과 두 개의 저점 값에 대해 또 다른 4개의 더블 변수를 만듭니다:
int high1, high2, low1, low2; double highVal1, highVal2, lowVal1, lowVal2;
고점과 그 매개변수의 값의 결과를 반올림하는 NormalizeDouble 함수를 사용하여 두 개의 고점(highVal1, highVal2)을 업데이트하는 것은 다음과 같습니다:
- value: 정규화해야 하는 숫자로, iHigh 함수를 사용하여 고가를 반환하며 매개 변수는 현재 심볼은 (_Symbol), 현재 기간은 (_Period), shift는 high1과 high2 입니다.
- digits: 소수점 뒤의 자릿수는 5가 됩니다.
highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
앞서 언급한 것과 동일한 매개 변수를 사용하여 다음과 같은 차이가 존재하며 NormalizeDouble 함수를 사용하여 두 개의 저점(lowVal1, lowVal2)을 업데이트합니다:
- value: 저점을 반환하는 iLow 함수를 사용하며 매개변수는 저점1과 저점 지수에 대한 shift를 제외하고는 동일합니다.
lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
추세를 식별하기 위해 설정해야 하는 조건은 if 문을 사용하여 EA가 고점과 저점의 네 가지 값을 연속적으로 확인한 다음 서로 관련된 위치를 결정하고 추세가 있는지(상승 또는 하락) 추세가 없는지(횡보)를 결정해야 합니다.
상승 추세의 상태:
lowVal1이 lowVal2 보다 크고 동시에 highVal1이 highVal2보다 크면 상승 추세이므로 EA는 차트에 다음과 같은 코멘트를 반환해야 합니다:
- Uptrend
- Current High
- Previous High
- Current Low
- Previous Low
if(lowVal1>lowVal2&&highVal1>highVal2) { Comment("Uptrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
하락 추세의 상태:
highVal1이 highVal2보다 낮고 동시에 lowVal1이 lowVal2보다 낮으면 하락 추세이며 EA는 차트에 다음과 같은 코멘트를 반환해야 합니다:
- Downtrend
- Current High
- Previous High
- Current Low
- Previous Low
else if(highVal1<highVal2&&lowVal1<lowVal2) { Comment("Downtrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
횡보 상태:
네 가지 값의 포지션이 상승 추세와 하락 추세 조건을 제외한 다른 조건일 경우 횡보이며 EA는 차트에 다음과 같은 코멘트를 반환해야 합니다:
else { Comment("Sideways", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
이 코드를 오류와 함께 컴파일하고 EA를 실행하면 트렌드 신호를 받을 수 있습니다. 다음은 트렌드 유형 및 조건에 따른 테스트의 예시입니다.
상승 추세:
이전 그림에서 이 차트의 가격 움직임에서 더 높은 저점과 더 높은 고점이 나타납니다. 이는 상승 추세의 예라는 것을 알 수 있습니다. 그래서 차트 왼쪽 상단에 코멘트로 상승 추세 신호를 받았습니다.
하락 추세:
이전 차트에서 명확하게 볼 수 있듯이 가격 움직임에 따라 더 낮은 고점과 더 낮은 저점이 나타나며 하락 추세를 보이고 있습니다. 그래서 우리는 차트에 코멘트로 하락 추세 신호를 받았습니다.
횡보:
이전 예에서 볼 수 있듯이 상승 추세와 하락 추세와는 다른 형태이며 더 낮은 고점과 더 높은 저점이 나타났습니다. 이는 횡보입니다. 그래서 차트에 코멘트로 횡보 신호를 받았습니다.
차트 더블 탑 감지
우리는 고점과 저점을 감지하는 방법을 배운 후 고점과 저점을 감지하는 기본 코드를 일부 개발하여 이를 기반으로 추세를 감지하는 방법을 배웠습니다. 따라서 잠재적인 가격의 움직임을 나타낼 수 있는 특정 차트 또는 가격 행동 패턴을 감지하기 위해 코드를 더 개발할 수 있을 것입니다. 우리는 이를 시도해 볼 수 있습니다.
이 부분에서는 저는 코드 개발이 거의 없는 이러한 차트 패턴의 예를 제공할 것입니다. 이를 통해 주요 아이디어를 이해하고 특히 코드에 유용한 기술 도구를 병합하는 경우 더 많은 작업을 해서 더 중요한 패턴을 감지할 수 있도록 합니다. 이 글의 이 부분에서는 차트에서 볼 수 있는 인기 차트 패턴 중 하나인 더블 탑을 살펴보겠습니다.
더블 탑은 차트에서 볼 수 있는 차트 패턴으로 고점이 거의 동일하게 형성되어 있어 매수세가 약해지고 가격이 하락할 가능성이 있음을 나타내는 패턴입니다. 자세한 내용은 여러 가지가 있지만 형태만 언급하면 앞서 언급한 것과 동일하다고 할 수 있습니다. 다음 그래프는 잠재적인 더블 탑 패턴의 시각적 예시입니다:
이전 예시에서 이것이 잠재적 패턴이며 다음 그래프와 같이 두 고점 사이의 저점 아래에서 가격이 돌파하고 종가가 형성되면 정합 패턴이 될 것이라고 설명한 것을 기억하실 것입니다.
이제 MetaTrader 5에서 이 둘을 감지하는 데 사용할 수 있는 MQL5 EA를 만들어야 합니다. EA는 두 개의 고점과 두 개의 저점을 지속적으로 확인하고 서로 관련된 위치를 확인한 다음 더블 탑 패턴의 조건인 특정 조건에 따라 특정 결과를 반환해야 합니다. 여기서 우리는 동일한 고점 뿐만 아니라 약간 낮거나 높은 고점이 올 수 있을 것입니다. 현재 고점이 이전 고점보다 낮거나 같고 동시에 현재 저점이 이전 저점보다 크면 잠재적 인 더블 탑의 신호가 될 수 있으므로 우리는 실제 패턴을 간단하게 나타내려고 노력할 것입니다. 현재 고점이 이전 고점보다 낮거나 같고 동시에 현재 저점이 이전 저점보다 낮으면 더블 탑 신호가 됩니다.
다음은 이를 수행하는 전체 코드입니다:
//+------------------------------------------------------------------+ //| DT patternFinder.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ void OnTick() { int checkBars= 5; int high1, high2, low1, low2; double highVal1, highVal2, lowVal1, lowVal2; high1=getmove(MODE_HIGH,checkBars,0); high2=getmove(MODE_HIGH,checkBars,high1+1); highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5); ObjectDelete(0,"topLine"); ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1)); ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true); low1=getmove(MODE_LOW,checkBars,0); low2=getmove(MODE_LOW,checkBars,low1+1); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5); ObjectDelete(0,"bottomLine"); ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1)); ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true); if(highVal1<=highVal2&&lowVal1>lowVal2) { Comment("Potential Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<=highVal2&&lowVal1<lowVal2) { Comment("Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else Comment(" "); } int getmove(int move, int count, int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) return (-1); int currentBar=startPos; int moveReturned=getNextMove(move,count*2+1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+1); moveReturned=getNextMove(move,count*2+1,currentBar-count); } return(currentBar); } int getNextMove(int move, int count, int startPos) { if(startPos<0) { count +=startPos; startPos =0; } return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); }
이 코드의 차이점은 패턴의 조건입니다.
잠재적인 더블 탑의 경우 highVal1이 highVal2 보다 낮거나 같고 lowVal1이 lowVal2보다 큰 경우 다음 값이 차트에 코멘트로 나타나야 합니다:
- 잠재적 더블 탑
- Current High
- Previous High
- Current Low
- Previous Low
if(highVal1<=highVal2&&lowVal1>lowVal2) { Comment("Potential Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
더블 탑의 경우, highVal1이 highVal2보다 낮거나 같고 lowVal1이 lowVal2 보다 낮으면 차트에 다음과 같은 값이 코멘트로 나타나야 합니다.
- 더블 탑
- Current High
- Previous High
- Current Low
- Previous Low
else if(highVal1<=highVal2&&lowVal1<lowVal2) { Comment("Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
더블 탑 패턴이 없는 경우 코멘트로 아무것도 반환하지 않습니다.
else Comment(" ");
이 코드를 오류 없이 컴파일하고 EA를 실행한 후 우리는 테스트에서 다음과 같은 예시를 신호로 확인할 수 있습니다.
잠재적 더블 탑의: 경우
이전 차트에서 볼 수 있듯이 사전 설정 조건인 더 높은 저점과 등가 고점이 일치하기 때문에 잠재적인 이중 고점 신호가 있습니다.
더블 탑의 경우:
이전 차트에서 볼 수 있듯이 사전 설정된 조건과 일치하는 고점과 저점, 즉 더 낮거나 동등한 고점과 더 낮은 저점이 있기 때문에 더블 탑 신호가 있습니다.
차트 더블 바텀 감지
이 파트에서는 더블 탑의 반대 패턴인 더블 바텀 패턴을 감지하는 방법을 배웁니다. 더블 바텀은 차트에서 볼 수 있는 차트 패턴으로 저점과 저점이 거의 같은 패턴으로 구성되어 있어 매도세가 약해지고 가격이 상승할 가능성이 있다는 것을 나타내지만 그 형태만을 보면 앞서 살펴본 것과 동일하다는 것을 알 수 있습니다. 다음 그래프는 잠재적인 더블 바텀 패턴의 시각적 예시입니다:
MetaTrader 5에서 앞의 두 값을 감지하는 데 사용할 수 있는 또 다른 MQL5 EA를 만들어야 합니다. EA는 두 개의 저점과 두 개의 고점을 지속적으로 확인하고 서로 관련된 위치를 결정한 다음 더블 바텀 패턴의 조건에 따라 특정 결과를 반환해야 합니다. 코드에서는 동일한 개발을 반대의 경우로 적용하여 동일한 고점 뿐만 아니라 약간 더 높거나 낮은 저점을 가진 실제 패턴에 접근하므로 현재 저점이 이전 저점보다 높거나 같고 동시에 현재 고점이 이전 고점보다 낮으면 잠재적 인 더블 바텀의 신호가 될 것입니다. 현재 저점이 이전 저점보다 크거나 같고 동시에 현재 고점이 이전 고점보다 높으면 더블 바텀 신호가 됩니다.
다음은 이를 수행하는 전체 코드입니다:
//+------------------------------------------------------------------+ //| DB patternFinder.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ void OnTick() { int checkBars= 5; int high1, high2, low1, low2; double highVal1, highVal2, lowVal1, lowVal2; high1=getmove(MODE_HIGH,checkBars,0); high2=getmove(MODE_HIGH,checkBars,high1+1); highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5); ObjectDelete(0,"topLine"); ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1)); ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true); low1=getmove(MODE_LOW,checkBars,0); low2=getmove(MODE_LOW,checkBars,low1+1); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5); ObjectDelete(0,"bottomLine"); ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1)); ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3); ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true); if(lowVal1>=lowVal2&&highVal1<highVal2) { Comment("Potential Double Bottom", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(lowVal1>=lowVal2&&highVal1>highVal2) { Comment("Double Bottom", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else Comment(" "); } int getmove(int move, int count, int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) return (-1); int currentBar=startPos; int moveReturned=getNextMove(move,count*2+1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+1); moveReturned=getNextMove(move,count*2+1,currentBar-count); } return(currentBar); } int getNextMove(int move, int count, int startPos) { if(startPos<0) { count +=startPos; startPos =0; } return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); }
이 코드의 차이점은 패턴의 조건입니다.
잠재적 더블 바텀의 경우 lowVal1이 lowVal2보다 크거나 같고 highVal1이 highVal2보다 낮으면 차트에 다음과 같은 값의 코멘트로 신호를 나타내야 합니다.
- 잠재적 더블 바텀
- Current High
- Previous High
- Current Low
- Previous Low
if(lowVal1>=lowVal2&&highVal1<highVal2) { Comment("Potential Double Bottom", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
더블 탑의 경우, lowVal1이 lowVal2보다 크거나 같고 highVal1이 highVal2보다 큰 경우 다음 값으로 차트에 코멘트로 신호를 나타내야 합니다:
- 더블 바텀
- Current High
- Previous High
- Current Low
- Previous Low
else if(lowVal1>=lowVal2&&highVal1>highVal2) { Comment("Double Bottom", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
이 코드를 오류 없이 컴파일하고 EA를 실행하면 테스트 결과 다음과 같은 신호를 얻을 수 있습니다.
잠재적 더블 바텀의 경우:
이전 차트에서 볼 수 있듯이 다 낮은 고점과 동등하거나 더 높은 저점이라는 사전 설정 조건과 일치하므로 잠재적인 더블 바텀 신호가 있습니다.
더블 바텀의 경우:
이전 차트에서 볼 수 있듯이 더 높은 고점과 동등하거나 더 높은 저점이라는 사전 설정 조건과 일치하므로 더블 바텀 신호가 있습니다.
결론
트레이더에게 가장 중요한 것은 가격의 움직임입니다. 이 가격의 움직임을 잘 이해하면 더 나은 투자 또는 거래 결정을 내릴 수 있기 때문입니다. 가격 움직임은 우리가 읽고 이해해야 하는 많은 패턴을 형성합니다. 이 기사에서는 MetaTrader 5 트레이딩 터미널에서 사용할 수 있도록 MQL5로 시스템을 생성하여 이 작업을 더 쉽게 할 수 있는 방법에 대해 알아보았습니다.
우리는 고점과 저점을 감지하는 방법을 배운 후 추세(상승 추세, 하락 추세, 횡보)와 인기 차트 패턴 중 하나인 더블 탑과 그 반대인 더블 바텀을 감지하는 방법을 배웠습니다. 또한 트렌드에 대한 모든 개념과 이 차트 유형에 대한 좋은 기반을 알아보고 앞서 언급한 프로그램이나 시스템을 적합한 조건에 따라 개발할 수 있도록 했습니다. 고점과 저점을 감지할 수 있는 시스템을 만드는 주요 개념을 학습한 후에는 이 시스템을 점점 더 발전시켜 헤드 앤 숄더, 삼각형, 직사각형 등과 같은 더 많은 차트 패턴을 감지할 수 있도록 할 수 있습니다. 이 글이 트레이딩 비즈니스에서 더 나은 결과를 얻기 위해 트레이딩 및 트레이딩 시스템을 개발하는 데 도움이 되길 바랍니다.
MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/12479



