English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 소개: 간단한 전문가 자문 및 사용자 지정 지표 작성 방법

MQL5 소개: 간단한 전문가 자문 및 사용자 지정 지표 작성 방법

MetaTrader 5 | 1 7월 2021, 10:32
180 0
Denis Zyatkevich
Denis Zyatkevich

소개

MetaTrader 5 클라이언트 터미널에 포함된 MQL5(MetaQuotes Programming Language 5)는 MQL4에 비해 많은 새로운 가능성과 더 높은 성능을 가지고 있다. 이 글은 당신이 이 새로운 프로그래밍 언어에 익숙해지도록 도와줄 것이다. 이 문서에는 전문가 자문 및 사용자 지정 지표 작성 방법의 간단한 예가 나와 있습니다. 저희는 또한 이러한 예를 이해하는 데 필요한 MQL5 언어의 몇 가지 세부 사항을 고려할 것이다.

그 글 내용, MQL5의 상세한 설명에서 찾을 수 있다.MQL5 Reference, MetaTrader 5에 포함되어. MQL5 기본 제공 도움말에 포함된 정보는 언어를 연구하기에 충분하다. 이 글은 MQL4에 익숙한 사람들, 그리고 이제 막 거래 시스템과 지표를 프로그래밍하기 시작한 신참들에게 유용할 수 있다.


MQL5 시작하기

메타트래더 5 거래 플랫폼을 사용하면 금융상품에 대한 기술적 분석을 수행하고 수동 및 자동 모드에서 거래할 수 있습니다. MetaTrader 5는 전작인 MetaTrader 4와 다르다. 특히 거래, 위치, 질서의 개념이 다듬어졌다.

  • 포지션 - 시장약정, 매입 또는 매도된 금융상품의 계약수.
  • 주문 - 특정 조건 하에서 어느 정도의 금융상품의 매입 또는 매도 명령이다.
  • 거래 - 브로커에 의한 일부 주문의 실행 사실이며, 이는 열림, 수정 또는 닫힘 위치로 이어집니다.

클라이언트 터미널에는 다양한 용도의 프로그램을 작성할 수 있는 MQL5 프로그래밍 언어가 내장되어 있습니다.

  • Expert Advisor - 특정 알고리즘에 따라 거래되는 프로그램입니다. Expert Advisor를 사용하면 자동 거래를 위한 거래 시스템을 구현할 수 있습니다(트레이더 없이 거래 작업을 수행할 수 있음). Professional Advisor는 거래 업무를 수행하고, 직책을 열고 마감하며, 보류 중인 주문을 관리할 수 있습니다.
  • 인디케이터 - 분석하기에 편리한 그래픽 형태로 데이터를 제공할 수 있는 프로그램입니다.
  • 스크립트 - 한 번에 일련의 작업을 수행할 수 있는 프로그램입니다.

Expert Advisors, 인디케이터 및 스크립트는 MQL5 표준 라이브러리의 함수 및 운영 체제 라이브러리를 포함한 DLL 함수를 호출할 수 있습니다. 코드의 조각들은 다른 파일에 위치한 프로그램의 텍스트, MQL5로 쓰여진에 포함될 수 있다.

프로그램 (Expert Advisor, 인디케이터 또는 스크립트)을 작성하려면 MetaTrader 5 클라이언트 터미널을 시작하고 MetaQuotes Language Editor/b0도구메뉴에서나 단순히 F4 키를 누르기만 하면 됩니다.

그림 1. Launching MetaEditor.

MetaEditor 5에서 윈도우는 새로운 걸 파일 메뉴에서 선택하거나 Ctrl+N을 누릅니다.

그림 2. 새로운 프로그램 만들기.

MQL5 마법사에서 윈도우는 당신이 생성하고 싶어하는 프로그램 유형을 고릅니다: 

그림 3. MQL5 마법사.

다음 단계에서 프로그램을 시작한 후 사용자에게 요청할 프로그램 이름, 작성자 정보 및 매개 변수를 지정할 수 있습니다.

그림 4. Expert Advisor의 일반 속성.

그런 다음 프로그램 템플릿(Expert Advisor, 인디케이터 또는 스크립트)이 생성됩니다. 이 템플릿은 코드를 편집하고 채울 수 있습니다.

그림 5. 새로운 프로그램의 템플릿.

프로그램이 준비되면 컴파일해야 합니다. 컴파일을 위해 프로그램은 컴파일파일 메뉴에서 선택하거나 F7 키를 누릅니다:

그림 6. 프로그램 컴필레이션.

프로그램 코드에 오류가 없으면 확장자 .ex5가 있는 파일이 생성됩니다. 그런 다음 실행을 위해 MetaTrader 5 클라이언트 터미널 차트에 이 새 Expert Advisor, 인디케이터 또는 스크립트를 첨부할 수 있습니다.

MQL5 프로그램은 연산자의 순서입니다. 각 연산자는 세미콜론 기호로 끝납니다 ";". 편의를 위해 당신은 코멘트를 본인의 코드에 더하고 싶을지 모릅니다. 이는 "/*" 및 "*/"의 심볼 안에 혹은 "//"의 심볼 다음에 라인의 끝에 위치하게 됩니다. MQL5는 "이벤트 중심" 프로그래밍 언어입니다. 즉, 특정 이벤트 (프로그램 시작 또는 종료, 새로운 견적 도착 등)가 발생하면 클라이언트 터미널은 지정된 작업을 수행하는 사용자가 작성한 해당 함수 (하위 프로그램)를 시작합니다. 클라이언트 터미널에는 다음과 같은 사전 정의된 이벤트가 있습니다.

  • 시작 이벤트는 스크립트가 실행 중일 때 발생합니다 (스크립트에서만 사용됨). 이는 OnStart 기능을 실행하게 됩니다. MQL4 등가 - 스크립트에서의 시작기능.
  • Init 이벤트는 Professional Advisor 인디케이터가 시작될 때 발생합니다.. 이는 OnInit 기능을 실행하게 됩니다. MQL4 등가 - init 기능.
  • Deinit 이벤트는 Expert Advisor 또는 Indicator가 종료될 때 발생합니다 (예: 차트에서 분리, 클라이언트 터미널 닫기 등). 이는 OnDeinit 기능을 실행하게 됩니다. MQL4 등가 - deinit 기능.
  • NewTick 이벤트는 현재 금융상품에 대한 새로운 견적이 도래할 때 발생합니다 (Expert Advisor에서만 사용됨). 이는 OnTick 기능을 실행하게 됩니다. MQL4 등가 - Expert Advisors에서의 start 기능.
  • Calculate 이벤트는 인디케이터가 시작될 때와 (OnInit 기능이 실행된 후에) 현재 금융상품에 대한 새로운 견적이 도래할 때 (지표에서만 사용) 발생합니다. 이는 OnCalculate 기능을 실행하게 됩니다. MQL4 등가 - 인디케이터에서의 start 기능.
  • Trade이벤트는 주문이 실행, 수정 또는 삭제될 때, 위치가 열림, 수정 또는 닫힐 때 발생합니다 (Expert Advisor에서만 사용됨).. 이는 OnTrade 기능을 실행하게 됩니다. MQL4에서 는 해당 이벤트와 기능의 등가가 없습니다.
  • BookEvent 이벤트는 시장의 깊이가 변경될 때 발생합니다 (Expert Advisors에서만 사용됨).. 이는 OnBookEvent 기능을 실행하게 됩니다. MQL4에서는 해당 이벤트와 기능의 등가 가 없으며, 시장의 깊이 또한 없습니다.
  • ChartEvent 이벤트는 사용자가 차트를 작업할 때 발생합니다. 차트 창이 포커스에 있을 때 마우스를 클릭하고 키를 누릅니다. 또한 그래픽 개체를 생성, 이동 또는 삭제하는 동안 발생합니다 (Expert Advisor 및 인디케이터에서 사용). 이는 OnChartEvent 기능을 실행하게 됩니다. MQL4에서의 이벤트 및 기능등가가 없습니다.
  • Timer 이벤트는 주기적으로 발생하는데 이는 timer가 트리거 될 때이며, 이는 EventSetTimer 기능을 이용하여 활성화 됐을 때입니다. 이는 OnTimer 기능을 실행하게 됩니다. MQL4에서이 이벤트와 기능의 등가가 있습니다. 타이머 (timer) 외에도요.

변수들을 사용하기 전, 각 데이터 유형을 지정해야 합니다. MQL5는 MQL4보다 더 많은 데이터 유형을 지원합니다.

  • bool은 논리 값 (참 또는 거짓)을 저장하기 위한 것입니다. 메모리는 1바이트입니다.
  • char는 -128 ~ 127의 정수 값을 저장합니다.. 메모리는 1바이트입니다.
  • uchar 0부터 255까지의 부호 없는 정수 값을 저장하기 위한 것입니다. 메모리는 1바이트입니다.
  • short은 -32 768 ~ 32 767의 정수 값을 저장합니다.. 메모리는 2바이트입니다.
  • ushort는 0부터 65 535까지의 부호 없는 정수 값을 저장하기 위한 것입니다. 메모리는 2바이트입니다.
  • int는 는 -2 147 483 648 ~ 2 147 483 647의 정수 값을 저장하기 위한 것입니다. 메모리는 4바이트입니다.
  • uint는 0에서 4 294 967 295의 부호 없는 정수 값을 저장하기 위한 것입니다. 메모리는 4바이트입니다.
  • long은 -9 223 372 036 854 775 808에서 9 223 372 036 854 775 807의 정수 값을 저장합니다. 메모리는 8바이트입니다.
  • ulong은 0에서 18 446 744 073 709 551 615의 부호 없는 정수 값을 저장합니다. 메모리는 8바이트입니다.
  • float는 부동 소수점 값을 저장하기 위한 것입니다. 메모리는 4바이트입니다.
  • double은 부동 소수점 값을 저장하기 위한 것입니다. 일반적으로 가격 데이터를 저장하는 데 사용됩니다. 메모리는 8바이트입니다.
  • datetime은 날짜 및 시간 값을 저장하기 위한 것으로, 01.01.1970 00:00:00부터 경과된 시간 (초)입니다. 메모리는 8바이트입니다.
  • color 색에 대한 정보를 저장하기 위한 것으로, 빨간색, 녹색 및 파란색 세 가지 색 구성 요소의 특성을 포함합니다. 메모리는 4바이트입니다.
  • enum 열거를 나타냅니다. 제한된 특정 데이터 집합의 유형을 지정할 수 있습니다. 메모리는 4바이트입니다.
  • string은 텍스트 문자열을 저장하기 위한 것입니다. 내부 표현은 8바이트 구조로, 문자열이 있는 버퍼 크기와 해당 버퍼에 대한 포인터를 포함합니다.

최적의 성능과 합리적인 메모리 사용에 적합한 데이터 유형을 선택해야 합니다. MQL5에는 구조라는 새로운 개념이 있습니다. 구조는 논리적으로 관련된 데이터를 결합합니다.

거래 시스템

이 글에서 예로 든 거래 시스템은 유럽 금융 기관들이 아침에 문을 열고, 나중에 미국에서 경제 이벤트가 발행되어 EURUSD의 트렌드로 이어진다는 가정에 기초하고 있습니다. 차트 기간은 중요하지 않지만, 전체 요일 (또는 해당 부분)이 한 번에 표시되므로 관찰에 매우 편리하기 때문에 미닛 바를 사용하는 것이 좋습니다.

그림 7. 거래 시스템.

오전 7시 (서버 시간)입니다. 구매 중지 및 판매 중지 보류 중인 주문은 당일의 가격 범위를 벗어나는 1포인트 거리에 배치됩니다. 구매 중지 보류 중인 주문의 경우 스프레드가 고려됩니다. 손절 (Stop Loss) 레벨은 범위의 반대쪽에 배치됩니다. 실행 후 손실 중지 주문은 단순 이동 평균으로 이동하지만 수익성이 높은 경우에만 이동됩니다.

이러한 유형의 후행 중지는 기존의 후행 중지와 비교했을 때 다음과 같은 이점이 있습니다. 즉, 가격 급상승이 수정될 경우 위치가 조기에 닫히는 것을 방지할 수 있습니다. 반면, 추세가 종료되고 평탄한 이동이 시작되면 포지션 클로져로 이어집니다. 단순 이동 평균은 분 차트 데이터를 사용하여 계산되며 평균 주기는 240입니다.

이익 수준은 현재 시장의 변동성에 따라 달라집니다. 시장 변동성을 파악하기 위해 평균 참 범위 (ATR) 지표 (일별 차트에 기간이 5와 같음)를 사용합니다. 그래서, 이것은 지난 주의 하루 평균 범위를 보여줍니다. 긴 포지션에 대한 이익 실현 (Take Profit) 레벨 값을 결정하기 위해 ATR 지표의 가치를 당일의 최소 가격에 추가할 예정입니다. 짧은 위치도 마찬가지입니다. ATR 인디케이터의 값을 당일의 최대 가격에서 뺍니다. 주문 가격 값이 손절 및 이익 실현 수준을 벗어나는 경우 주문이 배치되지 않습니다. 오후 7시 (서버 시간) 이후에는 보류 중인 주문이 모두 삭제되고 오늘 배치되지 않습니다 (오픈 포지션은 닫을 때까지 계속 추적됩니다).


인디케이터 쓰기

위에서 설명한 거래 시스템의 수익 수준을 보여주는 지표를 작성하겠습니다.

줄의 첫 번째 기호가 "#"이면 이 문자열이 전처리기 지시어임을 의미합니다. 지시어는 헤더 파일 및 가져온 기능포함하여 추가 프로그램 속성을 지정하고 상수를 선언하는 데 사용됩니다. 전처리기 지시문 뒤에는 세미콜론 (;) 기호가 없습니다.

#property copyright   "2010, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property description "This indicator calculates TakeProfit levels"
#property description "using the average market volatility. It uses the values"
#property description "of Average True Range (ATR) indicator, calculated"
#property description "on daily price data. Indicator values are calculated"
#property description "using maximal and minimal price values per day."
#property version     "1.00"

작성자와 작성자의 웹 페이지에 대한 정보는 저작권링크 속성에서 지정할 수 있으며, 설명 속성을 통해 간단한 설명을 추가할 수 있으며, 버전 속성을 통해 프로그램 버전을 지정할 수 있습니다. 인디케이터가 실행 중일 때 이 정보는 다음과 같습니다.

그림 8. 인디케이터 정보.

차트 또는 별도의 창에서 지표 위치를 지정해야 합니다. 속성 중 하나를 지정하면 다음과 같은 작업을 수행할 수 있습니다: indicator_chart_window 또는 indicator_separate_window:

#property indicator_chart_window

또한 사용할 인디케이터의 버퍼 수와 그래픽 시리즈 수를 지정해야 합니다.. 저희의 경우, 두 개의 선이 있으며, 각 선은 고유한 버퍼를 가지고 있습니다. 즉, 데이터가 플로팅 될 배열입니다.

#property indicator_buffers 2
#property indicator_plots   2

각 지시선에 대해 다음 속성을 지정하십시오. 유형(indicator_type 속성), 색상(indicator_color 속성), 도면 스타일(indicator_style 속성) 및 텍스트 레이블(속성):

#property indicator_type1   DRAW_LINE
#property indicator_color1  C'127,191,127'
#property indicator_style1  STYLE_SOLID
#property indicator_label1  "Buy TP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  C'191,127,127'
#property indicator_style2  STYLE_SOLID
#property indicator_label2  "Sell TP"

기본 선 유형은 DREW_LINE - 선의 경우, DREW_Section - 섹션의 경우, 히스토그램의 경우 DREW_HISTORAM입니다. 다른 그림 스타일도 많이 있습니다. 세 가지 RGB 구성 요소의 밝기를 지정하거나 빨간색, 녹색, 파란색, 흰색 등과 같이 미리 정의된 색상을 사용하여 색상을 정의할 수 있습니다. The line styles are: STYLE_SOLID - solid line, STYLE_DASH - dashed line, STYLE_DOT - dotted line, STYLE_DASHDOT - dash-dot line, STYLE_DASHDOTDOT - dash-two points.

그림 9. 인디케이터 라인에 대한 설명.

입력 수식어를 사용하여 외부 변수 (인디케이터를 실행한 후 값을 지정할 수 있음), 유형 및 기본값을 지정합니다.

input int             ATRper       = 5;         //ATR Period
input ENUM_TIMEFRAMES ATRtimeframe = PERIOD_D1; //Indicator timeframe

매개 변수 이름은 주석으로 지정할 수 있습니다. 변수 이름 대신 표시됩니다.

그림 10. 인디케이터의 입력 매개 변수.

글로벌 수준 (모든 기능에 표시)에서는 인디케이터의 여러 기능에 사용되는 변수 (및 변수 유형)를 지정합니다.

double bu[],bd[];
int hATR;

bu[]bd[] 배열은 지시자의 위쪽 및 아래쪽 선에 사용됩니다. 동적 배열 (예: 지정된 수의 요소가 없는 배열)을 사용할 것입니다. 사용할 요소 (그 크기는 자동으로 할당됨)의 수를 정확히 알 수 없기 때문입니다. 내장된 기술 인디케이터의 핸들은 HATR 변수에 저장됩니다. 인디케이터를 사용하려면 인디케이터 핸들이 필요합니다.

인디케이터가 실행되면 (차트에 부착된 후) 기능 OnInit이 호출됩니다.

void OnInit()
  {
   SetIndexBuffer(0,bu,INDICATOR_DATA);
   SetIndexBuffer(1,bd,INDICATOR_DATA);
   hATR=iATR(NULL,ATRtimeframe,ATRper);
  }

IndexBuffer 기능은 인디케이터 선으로 표시된 인디케이터 값을 저장하는 데 사용할 인디케이터의 버퍼인 bu[]bd[] 배열을 지정해야 합니다. 첫 번째 매개 변수는 지시자의 버퍼 인덱스를 정의하고 순서는 0부터 시작합니다. 두 번째 매개 변수는 인디케이터의 버퍼에 할당된 배열을 지정합니다. 세 번째 매개 변수는 인디케이터의 버퍼에 저장된 데이터 유형(INDICATOR_DATA - 플로팅용 데이터, INDICATOR_COLOR_INDEX - 도면 색상, INDICATOR_CALCOLATIONs - 중간 계산을 위한 보조 버퍼)을 지정합니다.

iATR 기능에 의해 반환되는 인디케이터 핸들은 hATR 변수에 저장됩니다. iATR 함수의 첫 번째 매개 변수는 거래 기호, NULL - 은 현재 차트의 기호입니다. 두 번째 매개 변수는 지시자 계산에 사용되는 차트 기간을 지정합니다. 세 번째 파라미터는 ATR 인디케이터의 평균 시간입니다.

OnCalculate 기능은 OnInit 함수 실행이 종료된 직후와 현재 기호에 대한 새 따옴표가 도착한 직후에 호출됩니다. 이 함수를 호출하는 방법에는 두 가지가 있습니다. 지표에 사용된 그 중 하나는 다음과 같습니다.

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[])

OnCalculate 함수를 호출한 후 클라이언트 터미널은 다음 매개 변수를 전달합니다. rates_total - 현재 차트의 바의 수, prev_calculated - 인디케이터, time[], open[], high[], low[] 에 의해 이미 계산된 바, close[], tick_volume[], volume[], spread[] - 각 바에 대한 시간, 시가, 고가, 저가, 종가, tick_volume, 거래량 및 스프레드 값을 각각 포함하는 배열. 계산 시간을 줄이기 위해 이미 계산되어 변경되지 않은 지표 값을 다시 계산할 필요가 없습니다. OnCalculate 함수를 호출한 후 이미 계산된 바의 수를 반환합니다.

OnCalculate 함수의 코드는 괄호로 묶여 있습니다. 함수에서 사용되는 로컬 변수로 시작합니다 (유형 및 이름).

  {
   int i,day_n,day_t;
   double atr[],h_day,l_day;

i 변수는 주기 카운터로 사용되며 day_nday_t 변수는 일 수를 저장하고 임시로 저장하는 데 사용됩니다. 하루 중 최대 및 최소 가격 값을 계산할 때. atr[] 배열은 ATR 인디케이터 및 h_dayl_day 변수의 값을 저장하는 데 사용됩니다. 하루 동안 최대 및 최소 가격 값을 저장하는 데 사용됩니다.

먼저 CopyBuffer 함수를 사용하여 ATR 인디케이터의 값을 atr[] 배열에 복사해야 합니다. 이 함수의 첫 번째 매개 변수로 ATR 인디케이터의 핸들을 사용합니다. 두 번째 매개 변수는 인디케이터의 버퍼 수 (0부터 번호 지정)이며 ATR 인디케이터에는 하나의 버퍼만 있습니다. 세 번째 매개 변수는 시작할 첫 번째 요소의 수를 지정하고 색인화는 현재에서 과거로 수행되며 0 번째 요소는 현재 (완료되지 않은) 바에 해당합니다. 네 번째 매개 변수는 복사해야 하는 요소의 수를 지정합니다.

마지막 (완료된) 바에 해당하는 두 번째 요소에만 관심이 있으므로 두 요소를 복사해보겠습니다. 마지막 매개 변수는 데이터를 복사할 대상 배열입니다.

   CopyBuffer(hATR,0,0,2,atr);

배열 색인화의 방향은 AS_SERIES 플래그에 따라 다릅니다. 설정된 경우 (즉, true와 같음) 배열은 시계열로 간주되고 요소 인덱싱은 가장 최근 데이터에서 가장 오래된 데이터로 수행됩니다. 설정되지 않은 경우 (즉, false와 같음) 이전 요소의 색인이 더 낮고 최신 요소의 색인이 더 큽니다.

   ArraySetAsSeries(atr,true);

atr[] 배열의 경우 ArraySetAsSeries 함수를 사용하여 AS_SERIES 플래그를 true로 설정합니다 (이 함수의 첫 번째 매개 변수는 배열입니다. 두 번째 매개 변수는 새 플래그 값입니다.) 이제 현재 (완료되지 않은) 바의 인덱스는 0과 같고, 두 번째 (완료된) 바의 인덱스는 1과 같습니다.

for 연산자를 사용하면 루프를 만들 수 있습니다.

   for(i=prev_calculated;i<rates_total;i++)
     {
      day_t=time[i]/PeriodSeconds(ATRtimeframe);
      if(day_n<day_t)
        {
         day_n=day_t;
         h_day=high[i];
         l_day=low[i];
        }
        else
        {
         if(high[i]>h_day) h_day=high[i];
         if(low[i]<l_day) l_day=low[i];
        }
      bu[i]=l_day+atr[1];
      bd[i]=h_day-atr[1];
     }

for 연산자 뒤에 괄호로 묶인 첫 번째 연산자는 i=prev_calculated 문입니다. 다음으로 표현식이 있습니다. 이 경우에는 i<rates_total입니다. 루프 조건입니다 - 루프는 참일 때 실행됩니다. 세 번째는 루프를 실행할 때마다 실행된 명령문입니다. 이 경우에는 i++입니다 (i=i+1와 같고 i 변수를 1 씩 늘리는 것을 의미합니다).

루프에서 i 변수는 prev_calculated 값에서 1 단계의 rates_total-1 과 같은 값까지 다양합니다. 기록 데이터 배열 (time[], high[]low[])은 기본적으로 시계열이 아니며 0 번째 인덱스는 다음에 해당합니다. 내역 상 가장 오래된 바, 마지막 바는 현재 완료되지 않은 바에 해당합니다. 루프에서 계산되지 않은 첫 번째 바 (prev_calculated) 에서 마지막 바 (rates_total-1)까지의 모든 바가 처리됩니다. 이러한 각 바에 대해 지표 값을 계산합니다.

time[] 배열의 시간 값은 01.01.1970 00:00:00부터 경과 한 시간 (초)으로 저장됩니다. 하루 (또는 다른 기간)의 초 수로 나누면 결과의 정수 부분은 01.01.1970 (또는 다른 기간)에서 시작하는 하루의 숫자가됩니다. PeriodSeconds 함수는 매개 변수로 정의 된 기간(초)을 반환합니다. day_t 변수는 i 색인이 있는 바에 해당하는 일 수입니다. day_n 변수는 최고 가격과 최저 가격이 계산되는 날짜입니다.

if 연산자를 고려해 보겠습니다. 이 연산자의 대괄호 표현식이 참이면 if 키워드 다음에 나오는 연산자가 실행됩니다. false일 경우 else 키워드 다음에 오는 연산자가 실행됩니다. 각 연산자는 복합적일 수 있습니다. 즉, 여러 연산자로 구성될 수 있으며, 이 경우 괄호로 묶여 있습니다.

처리된 날짜의 최고 및 최저 가격 값은 각각 h_dayl_day 변수에 저장됩니다. 저희의 경우 다음 조건을 확인합니다. 분석된 바가 새로운 날에 해당하면 최대 및 최소 가격 값을 다시 계산하기 시작하고 그렇지 않으면 계속합니다. 저희는 각 지표 라인에 대한 값을 계산하고 있습니다. 상단 라인의 경우 최소 일일 가격을 사용하고 하단 라인의 경우 가격의 최대값을 사용합니다.

OnCalculate 함수 끝에서 return 연산자는 계산된 바의 수를 반환합니다.

   return(rates_total);
  }

DeInit 함수 (차트에서 인디케이터가 제거되거나 클라이언트 터미널이 닫힐 때 작동 함) 내부에서 ATR 인디케이터에 의해 예약된 메모리, IndicatorRelease 기능을 사용하여 해제됩니다. 이 함수에는 인디케이터의 핸들이라는 하나의 매개 변수만 있습니다.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hATR);
  }

이제 인디케이터가 완성되었습니다. 컴파일하려면 MetaEditor의 파일 메뉴에서 컴파일을 선택하거나 F7 키를 누르십시오. 코드에 오류가 없으면 컴파일이 성공합니다. 편집 결과는 Toolbox 창의 Errors 탭에 인쇄됩니다. 귀하의 경우 컴파일러는 다음 문자열에 대해 "변환 가능한 데이터 손실" 경고를 인쇄 할 수 있습니다.

      day_t=time[i]/PeriodSeconds(ATRtimeframe);

이 줄에서 저희는 의도적으로 소수 부분을 버리고 있으므로 이러한 데이터 손실은 오류가 아닙니다.

지표가 완성되고 컴파일되면 MetaTrader 5 클라이언트 터미널의 차트에 첨부하거나 다른 지표, Expert Adviser 또는 스크립트에서 사용할 수 있습니다. 이 지표의 소스 코드는 이 글에서 첨부 파일로 제공됩니다.


Expert Adviser 작성

이제 위에서 설명한 거래 시스템을 구현하는 Expert Advisor를 작성할 때입니다. 저희는 하나의 금융 상품만 거래할 것이라고 가정합니다. 여러 Expert Advisor가 하나의 상품을 거래하려면 각 상품이 전체 포지션에 기여하는 정도를주의 깊게 분석해야 하며 이는 이 글 의 범위를 벗어납니다.

#property copyright   "2010, MetaQuotes Software Corp."
#property version     "1.00"
#property description "This Expert Advisor places the pending orders during the"
#property description "time from StartHour till EndHour on the price levels, that"
#property description "are 1 point below/lower the current trade range."
#property description "The StopLoss levels are placed at the opposite side"
#property description "of the price range. After order execution, the TakeProfit value"
#property description "is set at the 'indicator_TP' level. The StopLoss level is moved"
#property description "to the SMA values only for the profitable orders."

이러한 전 처리기 지시문의 목적은 인디케이터 작성 섹션에서 이미 고려되었습니다. Expert Adviser과 동일한 방식으로 작동합니다.

입력 매개 변수의 값 (Expert Advisor를 시작한 후 사용자가 정의 할 수 있음), 유형 및 기본값을 지정하겠습니다.

input int    StartHour = 7;
input int    EndHour   = 19;
input int    MAper     = 240;
input double Lots      = 0.1;

StartHourEndHour 매개 변수는 보류중인 주문의 기간 (시작 및 종료 시간)을 정의합니다. MAper 매개 변수는 후행 중 오픈 포지션의 손절 (StopLoss) 수준에 사용되는 단순 이동 평균의 평균 기간을 정의합니다. Lots 매개 변수는 거래에 사용되는 금융 상품의 양을 정의합니다.

다양한 거래 기능에서 사용될 글로벌 변수를 지정해 봅시다 :

int hMA,hCI;

hMA 변수는 MA 인디케이터의 핸들을 저장하는 데 사용되고 hCI 변수는 사용자 지정 인디케이터의 핸들을 저장하는 데 사용됩니다 (위에 작성된 인디케이터).

Expert Advisor가 실행되면 OnInit 기능이 실행됩니다.

void OnInit()
  {
   hMA=iMA(NULL,0,MAper,0,MODE_SMA,PRICE_CLOSE);
   hCI=iCustom(NULL,0,"indicator_TP");
  }

이 함수에서는 MA 인디케이터와 사용자 지정 인디케이터의 핸들을 얻습니다. iMA 함수 및 해당 매개 변수는 위에 설명 된 iATR 함수에서와 동일하게 사용됩니다.

iCustom 함수의 첫 번째 매개 변수는 악기의 기호 이름입니다. NULL은 현재 차트의 악기를 의미합니다. 두 번째 매개 변수는 차트 타임 프레임으로 지표를 계산하는 데 사용되는 데이터이며 0은 현재 차트의 기간을 의미합니다. 세 번째 매개 변수는 인디케이터의 파일 이름입니다 (확장자 없음). 파일 경로는 MQL5\Indicators\ 폴더에 상대적입니다.

각각의 새로운 견적 도착 후에 실행되는 OnTick 함수를 만들어 보겠습니다.

void OnTick()
  {

함수 코드는 괄호로 묶여 있습니다.

Expert Advisor에서 사용할 미리 정의 된 데이터 구조를 지정하겠습니다.

   MqlTradeRequest request;
   MqlTradeResult result;
   MqlDateTime dt;
MqlTradeRequest 사전 정의된 구조에는 거래 작업에서 OrderSend 함수로 전달되는 주문 및 위치 매개 변수가 있습니다. MqlTradeResult 구조의 목적은 OrderSend 함수에 의해 반환된 거래 운영 결과에 대한 정보를 저장하는 것입니다. MqlDateTime 미리 정의된 구조의 목적은 날짜 및 시간 정보를 저장하는 것입니다.

OnTick 함수에서 사용할 로컬 변수 (및 해당 유형)를 지정하겠습니다.

   bool bord=false, sord=false;
   int i;
   ulong ticket;
   datetime t[];
   double h[], l[], ma[], atr_h[], atr_l[],
          lev_h, lev_l, StopLoss,
          StopLevel=_Point*SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL),
          Spread   =NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK) - SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits);
bordsord 불 방식의 변수는 매수 중지(Buy Stop) 및 매도 중지(Sell Stop) 보류 주문의 존재를 보여주는 플래그로 사용됩니다. 존재하는 경우 해당 변수의 값은 true이고 그렇지 않으면 false입니다. i 변수는 루프 연산자의 카운터 및 중간 데이터 저장에 사용됩니다. 대기중인 주문의 티켓은 ticket 변수에 저장됩니다.

내역 데이터에서 t[], h[]l[] 배열은 각 바의 시간, 최대 및 최소 가격 값을 저장하는 데 사용됩니다. ma[] 배열은 MA 인디케이터의 값을 저장하는 데 사용되며 atr_h[]atr_l[] 배열은 저희가 만든 indicator_TP 사용자 지정 인디케이터의 상단 및 하단 라인 값입니다.

lev_hlev_l 변수는 현재 날짜의 최대 및 최소 가격 값과 보류중인 주문의 개시 가격을 저장하는 데 사용됩니다. 손절매 변수는 오픈 포지션의 손절매 가격을 일시적으로 저장하는 데 사용됩니다.

StopLevel 변수는 STOP_LEVEL 값을 저장하는 데 사용됩니다. 현재 가격과 보류중인 주문 가격 (가격 단위) 사이의 최소 거리입니다. 이 값은 포인트로 정의 된 STOP_LEVEL 변수 값에 의한 포인트 가격 (_Point 사전 정의된 변수)의 곱으로 계산됩니다. STOP_LEVEL의 값은 SymbolInfoInterger 함수에 의해 반환됩니다. 이 함수의 첫 번째 매개 변수는 기호 이름이고 두 번째 매개 변수는 요청된 속성의 인디케이터입니다. 기호 (금융 상품의 이름)는 Symbol 함수 (매개 변수 없음)를 사용하여 얻을 수 있습니다.

Spread 값은 스프레드 값 (가격 단위)을 저장하는 데 사용됩니다. 이 값은 NormalizeDouble 함수를 사용하여 정규화 된 현재 Ask 및 Bid 값의 차이로 계산됩니다. 이 함수의 첫 번째 매개 변수는 정규화 할 double 유형의 값이고 두 번째는 _Digits 사전 정의된 변수에서 얻은 포인트 뒤의 자릿수입니다. 현재 Ask 및 Bid 값은 SymbolInfoDouble 함수를 사용하여 얻을 수 있습니다. 이 함수의 첫 번째 매개 변수는 기호 이름이고 두 번째 매개 변수는 속성 인디케이터입니다.

대부분의 OrderSend 함수 호출에 일반적으로 사용되는 값으로 구조 request를 채웁니다.

   request.symbol      =Symbol();
   request.volume      =Lots;
   request.tp          =0;
   request.deviation   =0;
   request.type_filling=ORDER_FILLING_FOK;
request.symbol 요소에는 거래되는 상품의 기호 이름, request.volume 요소-금융 상품의 볼륨 (계약 크기), request.tp - 이익 실현의 숫자 값 (경우에 따라 사용하지 않고 0으로 채움), request.deviation - 거래 운영 실행 중 허용 된 가격 편차, request.type_filling - 주문 유형은 다음 중 하나일 수 있습니다.
  • ORDER_FILLING_FOK - 거래량이 주문에 지정된 것보다 같거나 더 좋은 경우에만 거래를 실행할 수 있습니다. 수량이 부족하면 주문이 진행되지 않습니다.
  • ORDER_FILLING_IOC - 충분한 수량이 없습니다. 주문은 최대 사용 가능한 시장 수량으로 실행됩니다. 
  • ORDER_FILLING_RETURN-ORDER_FILING_IOC와 동일하지만 이 경우 누락된볼륨에 대한 추가 주문이 이루어집니다.

TimeCurrent 함수를 사용하여 현재 서버 시간 (마지막 인용 시간)을 가져옵니다. 이 함수의 유일한 매개 변수는 결과가 있는 구조에 대한 포인터입니다.

   TimeCurrent(dt);

모든 계산을 위해 현재 날짜에 대한 내역 가격 데이터만 필요합니다. 필요한 (그리고 약간의 예약이 있는) 바의 수는 다음 공식을 사용하여 계산할 수 있습니다. i = (dt.hour + 1)*60, 여기서 dt.hour - 현재 시간을 포함하는 구조 요소입니다 시간, 최대 및 최소 가격 값은 각각 CopyTime, CopyHigh, CopyLow 기능을 사용하여 t[], h[]l[] 배열에 복사됩니다.

   i=(dt.hour+1)*60;
   if(CopyTime(Symbol(),0,0,i,t)<i || CopyHigh(Symbol(),0,0,i,h)<i || CopyLow(Symbol(),0,0,i,l)<i)
     {
      Print("Can't copy timeseries!");
      return;
     }

CopyTime, CopyHighCopyLow 함수의 첫 번째 매개 변수는 기호 이름이고 두 번째 매개 변수는 차트 타임 프레임이고, 세 번째는 복사 할 시작 요소이고, 네 번째는 복사할 요소의 수, 다섯 번째는 데이터의 대상 배열입니다. 이러한 각 함수는 복사 된 요소의 수를 반환하거나 오류의 경우 음수 값 -1을 반환합니다.

if 연산자는 세 배열 모두에 대해 복사된 요소의 수를 확인하는 데 사용됩니다. 복사된 요소의 수가 계산에 필요한 것보다 적거나 (배열 중 하나라도) 오류가 발생한 경우 "Can't copy timeseries!" 메시지를 Experts 로그에 기록하고 return 연산자를 사용하여 OnTick 함수 실행을 종료합니다. 메시지는 인쇄 기능으로 인쇄됩니다. 쉼표로 구분된 모든 유형의 데이터를 인쇄할 수 있습니다.

가격 데이터가 t[], h[]l[] 배열에 복사되면 AS_SERIES 플래그를 true로 설정합니다. 위에서 고려한 ArraySetAsSeries 함수를 사용합니다. 배열 인덱싱을 timeseries (현재 가격에서 이전 가격으로)로 설정해야 합니다.

   ArraySetAsSeries(t,true);
   ArraySetAsSeries(h,true);
   ArraySetAsSeries(l,true);

오늘의 최대 및 최소 가격 값은 lev_hlev_l 변수에 배치됩니다.

   lev_h=h[0];
   lev_l=l[0];
   for(i=1;i<ArraySize(t) && MathFloor(t[i]/86400)==MathFloor(t[0]/86400);i++)
     {
      if(h[i]>lev_h) lev_h=h[i];
      if(l[i]<lev_l) lev_l=l[i];
     }

루프는 MathFloor(t[i]/86400) == MathFloor(t[0]/86400) 오늘에 속하는 바로 검색을 제한하려면 조건이 참입니다. 이 표현의 왼쪽에는 현재 바의 숫자가 있고 오른쪽에는 현재 날짜의 숫자 (86400은 하루의 초 수)가 있습니다. MathFloor 함수는 숫자 값을 반올림합니다. 즉, 양수 값에 정수 부분만 사용합니다. 이 함수의 유일한 매개 변수는 반올림 할 expression입니다. MQL5 및 MQL4에서 동등성은 "==" symbols로 정의됩니다 (관계 작동 참조).

주문 가격은 다음과 같이 계산됩니다. 매수 중지 유형의 보류 주문에 대해 1 포인트 (_Point 사전 정의된 변수는 가격 단위의 포인트 크기와 동일) 및 Spreadlev_h 변수 (lev_h+=Spread+_Point 또는 lev_h=lev_h+Spread+_Point)에 추가합니다. For the pending orders of 매도 중지 type we are subtracting one point from the lev_l 변수 값 (lev_l-=_Point or lev_l=lev_l-_Point).

   lev_h+=Spread+_Point;
   lev_l-=_Point;

다음으로 CopyBuffer 함수를 사용하여 인디케이터의 버퍼에서 배열로 값을 복사합니다. MA 값은 ma[] 배열에 복사되고 사용자 지정 인디케이터의 위쪽 줄 값은 atr_h[] 배열에 복사되고 인디케이터의 아래쪽 줄 값은 atr_l[] 배열. CopyBuffer 함수는 인디케이터의 세부 사항을 고려할 때 위에서 설명했습니다.

   if(CopyBuffer(hMA,0,0,2,ma)<2 || CopyBuffer(hCI,0,0,1,atr_h)<1 || CopyBuffer(hCI,1,0,1,atr_l)<1)
     {
      Print("Can't copy indicator buffer!");
      return;
     }

두 번째 (마지막 완료) 바에 해당하는 MA 인디케이터 값과 마지막 바에 해당하는 인디케이터 값이 필요하므로 이 두 요소를 ma[]에 복사합니다. 배열과 한 요소를 atr_h[]atr_l[] 배열에 추가합니다. 복사 중 오류가 발생하거나 복사된 값의 수가 필요한 것보다 적은 경우 (이러한 배열에 대해) 메시지가 Experts 로그에 인쇄되고 OnTick 함수는 return 연산자를 사용하여 종료됩니다

ma[] 배열의 경우 배열의 timeseries 인덱싱을 나타내는 AS_SERIES 플래그를 설정합니다

   ArraySetAsSeries(ma,true);

atr _ []atr_l[] 배열에는 요소가 하나만 있으므로 시계열 색인 생성은 중요하지 않습니다. 이익 실현 수준을 결정하기 위해 atr_l[0] 값이 더 사용될 것이므로 매도 포지션은 Ask 가격으로 마감되지만 Bid 가격이 가격 차트에서 사용되기 때문에 atr_l[0] 값에 스프레드를 추가합니다.

   atr_l[0]+=Spread;

PositionsTotal 함수는 오픈 포지션의 수를 반환합니다 (매개 변수 없음). 위치 색인은 0부터 시작합니다. 열려있는 모든 위치를 검색하는 루프를 만들어 보겠습니다.

// in this loop we're checking all opened positions
   for(i=0;i<PositionsTotal();i++)
     {
      // processing orders with "our" symbols only
      if(Symbol()==PositionGetSymbol(i))
        {
         // we will change the values of StopLoss and TakeProfit
         request.action=TRADE_ACTION_SLTP;
         // long positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
           {
            // let's determine StopLoss
            if(ma[1]>PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]; else StopLoss=lev_l;
            // if StopLoss is not defined or lower than needed            
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(StopLoss-PositionGetDouble(POSITION_SL),_Digits)>0
               // if TakeProfit is not defined or higer than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(PositionGetDouble(POSITION_TP)-atr_h[0],_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLoss-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(atr_h[0]-SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_h[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // short positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            // let's determine the value of StopLoss
            if(ma[1]+Spread<PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]+Spread; else StopLoss=lev_h;
            // if StopLoss is not defined or higher than needed
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(PositionGetDouble(POSITION_SL)-StopLoss,_Digits)>0
               // if TakeProfit is not defined or lower than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(atr_l[0]-PositionGetDouble(POSITION_TP),_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(StopLoss-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK)-atr_l[0]-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_l[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // if there is an opened position, return from here...
         return;
        }
     }
루프 내에서 if 연산자를 사용하여 현재 차트의 심볼에 대해 오픈 포지션을 선택합니다. PositionGetSymbol 함수는 악기의 기호를 반환하며, 작업할 위치를 자동으로 선택합니다. 이 함수에는 오픈 포지션 목록에서 위치 색인이라는 하나의 매개 변수만 있습니다. 오픈 포지션의 손절매 및 이익 실현 값을 변경해야 할 가능성이 있으므로 TRADE_ACTION_SLTP 값을 request.action 요소에 입력해보겠습니다. 다음으로 방향에 따라 위치가 롱 포지션과 숏 포지션으로 나뉩니다.

매수 포지션의 경우 손절매 수준은 다음과 같이 결정됩니다. MA 지표 값이 포지션의 시가보다 높으면 손절매 값은 MA 지표 값과 동일하다고 가정하고, 그렇지 않으면 손절매 값은 lev_l 변수 값. 오픈 포지션에 대한 손절매 현재 값은 PositionGetDouble 함수를 사용하여 결정됩니다. 이 함수에는 위치 속성의 인디케이터인 하나의 매개 변수만 있습니다. 오픈 포지션에 대해 손절매 값이 정의되지 않았거나 (0과 같음)이 값이 예상보다 높으면이 포지션에 대한 손절매 및 이익 실현 값을 수정합니다. 이익 실현 값이 정의되지 않았거나 (0과 같음) 예상보다 높으면 (지표의 상단 선보다 높음)이 포지션에 대한 손절매 및 이익 실현 값을 수정합니다.

손절매 및 이익 실현 값의 변경 가능성을 확인해야 합니다. 손절매의 새 값은 현재 입찰 가격 (적어도 STOP_LEVEL 값)보다 낮아야하며 이익 실현의 새 값은 현재 입찰 가격보다 적어도 STOP_LEVEL 값만큼 커야 합니다. 저희는 비교를 위해 정규화 된 차이를 사용했습니다. 이중 유형의 부동 소수점 이진수를 부동 소수점 십진수로 변환하여 발생하는 부정확성 때문에 비교된 값이 마지막 자릿수에서 다를 수 있기 때문입니다.

오픈 포지션에 대한 손절매 또는 이익 실현 수준을 변경해야 하고 거래 규칙에 따라 새 값이 유효한 경우 손절매 및 이익 실현의 새 값을 구조의 해당 요소에 넣고 트레이드 서버에 데이터를 보내기 위해OrderSend 기능을 호출합니다.

매도 포지션의 손절매 (StopLoss)와 이익 실현 (TakeProfit) 값의 변경은 동일합니다. 매도 포지션과 비교하여 매도 포지션은 매도 가격에 의해 마감되므로 매도 값이 비교에 사용됩니다. 현재 차트에 대해 오픈 포지션이 있는 경우 return 연산자를 사용하여 OnTick 함수 실행을 종료합니다.

OrdersTotal 함수 (매개 변수 없음)는 보류중인 주문 수를 반환합니다. 인덱스는 0부터 시작합니다. 모든 보류중인 주문을 처리하는 데 사용할 루프를 만들어 보겠습니다.

// in this loop we're checking all pending orders
   for(i=0;i<OrdersTotal();i++)
     {
      // choosing each order and getting its ticket
      ticket=OrderGetTicket(i);
      // processing orders with "our" symbols only
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         // processing Buy Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_h<atr_h[0])
              {
               // if the opening price is lower than needed
               if((NormalizeDouble(lev_h-OrderGetDouble(ORDER_PRICE_OPEN),_Digits)>0
                  // if StopLoss is not defined or higher than needed
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(OrderGetDouble(ORDER_SL)-lev_l,_Digits)!=0)
                  // is opening price close to the current price?
                  && NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting the ticket number to the structure
                  request.order=ticket;
                  // putting the new value of opening price to the structure
                  request.price=NormalizeDouble(lev_h,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_l,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passed
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Buy Stop order
            bord=true;
           }
         // processing Sell Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[0])
              {
               // if the opening price is higher than needed
               if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>0
                  // if StopLoss is not defined or lower than need
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>0)
                  // is opening price close to the current price?
                  && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting ticket of modified order to the structure
                  request.order=ticket;
                  // putting new value of the opening price to the structure
                  request.price=NormalizeDouble(lev_l,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_h,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passedе
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Sell Stop order
            sord=true;
           }
        }
     }
OrderGetTicket 함수를 사용하여 추가 작업을 위해 주문을 선택하고 주문 티켓을 ticket 변수에 저장합니다. 이 함수에는 열린 주문 목록의 주문 색인이라는 단 하나의 매개 변수가 있습니다. OrderGetString 함수는 기호 이름을 가져 오는데 사용됩니다. 하나의 매개 변수 (주문 속성 인디케이터) 만 있습니다. 현재 차트의 이름과 심볼 이름을 비교하고 있으며 Expert Advisor가 작업중인 기기별로 주문을 선택할 수 있습니다. 주문 유형은 해당 주문 유형 인디케이터와 함께 OrderGetInteger 함수에 의해 결정됩니다. 매수 중지 및 매도 중지 주문을 별도로 처리합니다.

현재 시간이 StartHour에서 EndHour까지의 범위에 있고 매수 중지 주문의 시가가 지표의 상단 라인을 초과하지 않는 경우 시가 및 손절매 수준 값을 수정합니다 (필요한 경우), 그렇지 않으면 주문을 삭제합니다.

다음으로 보류중인 주문의 시가 또는 손절매 수준을 수정해야 하는지 결정합니다. 매수 중지 주문의 시가가 예상보다 낮거나 손절매가 정의되지 않았거나 더 높으면 TRADE_ACTION_MODIFY 값을 request.action 요소에 넣습니다. - 보류중인 주문 매개 변수를 변경해야 함을 의미합니다. 또한 request.ticket 요소에 주문 티켓을 넣고 OrderSend 함수를 사용하여 거래 서버로 거래 요청을 보냅니다 비교해 보면 스프레드의 가치는 다양할 수 있기 때문에 시가가 예상보다 낮은 경우를 결정하지만, 스프레드가 변경될 때마다 주문을 수정하지 않고 해당하는 최고 수준 (최대 스프레드 값)으로 설정됩니다.

또한 비교에서 저희는 손절매의 가치가 예상보다 높을 때만 결정하고 있습니다. 왜냐하면 가격 범위가 낮 동안 확장 될 수 있고 가격의 새로운 최저치 이후에 손절매 주문의 가치를 낮추어야하기 때문입니다. 거래 서버에 요청을 보낸 후 return 연산자를 사용하여OnTick 함수 실행이 종료됩니다. 매수 중지 주문이 있는 경우 bord 변수 값은 true로 설정됩니다.

판매 중지 주문은 구매 중지 주문과 동일하게 처리됩니다.

이제 주문이 없을 경우 매수 중지 및 매도 중지 대기 주문을하겠습니다. TRADE_ACTION_PENDING 값을 request.action 요소에 넣습니다 (대기중인 주문이 있음을 의미 함).

   request.action=TRADE_ACTION_PENDING;

현재 시간의 값이 주문 시간 내에있는 경우 다음과 같이 주문합니다.

   if(dt.hour>=StartHour && dt.hour<EndHour)
     {
      if(bord==false && lev_h<atr_h[0])
        {
         request.price=NormalizeDouble(lev_h,_Digits);
         request.sl=NormalizeDouble(lev_l,_Digits);
         request.type=ORDER_TYPE_BUY_STOP;
         OrderSend(request,result);
        }
      if(sord==false && lev_l>atr_l[0])
        {
         request.price=NormalizeDouble(lev_l,_Digits);
         request.sl=NormalizeDouble(lev_h,_Digits);
         request.type=ORDER_TYPE_SELL_STOP;
         OrderSend(request,result);
        }
     }
  }
매수 중지 및 매도 중지 주문을하는 동안 bordsord 변수의 값을 분석하여 동일한 주문이 있는지 확인합니다. 또한 다음과 같은 조건을 확인하고 있습니다. 주문 가격이 저희 지표 값 이내여야 합니다. 정규화 된 주문 가격은 request.price 요소에 배치되고, 손절매의 정규화 된 값은 주문 유형 (ORDER_BUY_STOP 또는 ORDER_SELL_STOP) 인 request.sl 변수에 배치됩니다. request.type 변수에 배치됩니다. 그 후 거래 서버에 요청을 보냅니다. OnTick 함수의 코드는 세미콜론으로 끝납니다.

인디케이터에 의해 할당 된 리소스는 위에서 고려한 IndicatorRelease 함수를 사용하여 OnDeinit 함수 내에서 해제됩니다.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

Expert Advisor가 완료되었습니다. 오류가 없으면 컴파일이 성공한 것입니다. 이제 차트에 첨부하여 실행할 수 있습니다. 소스 코드는 이 글 의 첨부 파일로 다운로드 할 수 있습니다.


시작 및 디버깅

Expert Advisor 및 Indicator가 준비되면 기본 제공 MetaEditor 디버거를 사용하여 시작하는 방법과 디버깅 방법을 고려해 보겠습니다.

Expert Advisor를 실행하려면 네비게이터 (Navigator) 창의 Expert Advisors 그룹에서 찾아야 합니다. 그런 다음 컨텍스트 메뉴에서 차트에 첨부를 선택하면 마우스 오른쪽 버튼을 클릭하면 나타납니다.

그림 11. Expert Advisor를 시작합니다.

Expert Advisor의 입력 매개 변수가 있는 창이 나타납니다. 필요한 경우 이러한 매개 변수를 변경할 수 있습니다. 확인을 누르면 차트의 오른쪽 상단에 아이콘 이 표시되며 이 아이콘은 Expert Advisor가 작동 중임을 나타냅니다. 네비게이터 (Navigator) 창을 표시하거나 닫으려면 보기 메뉴에서 네비게이터 (Navigator)를 선택하거나 Ctrl+N을 누릅니다. Expert Advisor를 실행하는 두 번째 방법은 Insert 메뉴의 전문가 (Experts) 하위 메뉴에서 선택하는 것입니다. 

Expert Advisor가 거래를 할 수 있으려면 클라이언트 터미널 옵션에서 오토 트레이딩이 허용되어야 합니다. 도구 메뉴-> 옵션 창-> 전문가 조언 탭-> 오토 트레이딩 허용 옵션이 활성화 되어야 합니다. Expert Advisor가 DLL에서 함수를 호출할 수 있으려면 DLL 가져오기 허용s 옵션도 활성화해야 합니다.

그림 12. 터미널 옵션 - 오토 트레이딩 허용.

또한 해당 옵션을 확인하여 각 Expert Advisor에 대해 개별적으로 외부 DLL 라이브러리의 거래 및 가져오기 권한 또는 금지를 설정할 수 있습니다.

Expert Advisor가 지표를 사용한다는 사실에도 불구하고 지표의 선은 차트에 표시되지 않습니다. 필요한 경우 인디케이터를 수동으로 부착할 수 있습니다.

지표 시작은 Expert Advisors와 동일합니다. 내장 지표를 시작하려면 네비게이터 (Navigator) 창에서 인디케이터 트리를 확장합니다 (사용자 지정 지표의 경우 확장해야 합니다. 사용자 지정 지표 트리) 팝업 메뉴를 표시하려면 마우스 오른쪽 버튼을 클릭 한 다음 상황에 맞는 메뉴에서 차트에 첨부를 선택합니다. 두 번째 방법은 삽입 메뉴에서 인디케이터를 선택하고 그룹을 선택 (또는 사용자 지정 인디케이터의 경우 사용자 지정 선택) 및 인디케이터 자체를 선택하는 것입니다.

스크립트는 Expert Advisors 및 인디케이터와 동일한 방식으로 시작됩니다.

클라이언트 터미널 이벤트 (거래 서버와의 연결/연결 해제, 자동 업데이트, 위치 및 주문 변경, Expert Advisors 및 스크립트 실행, 오류 메시지)에 대한 정보는 도구 상자 (Toolbox) 창의 저널 (Journal) 탭에서 찾을 수 있습니다. Expert Advisors, 인디케이터 및 스크립트가 인쇄한 메시지는 전문가 (Experts) 탭에 있습니다.

MetaEditor에는 내장 디버거가 있습니다. 이를 통해 프로그램을 디버깅 할 수 있습니다 - 전문가 자문, 지표 및 스크립트의 단계별 실행. 디버깅은 프로그램 코드에서 오류를 찾고 Expert Advisor, 인디케이터 또는 스크립트 실행 중에 프로세스를 관찰하는 데 도움이 됩니다. 디버그 모드에서 프로그램을 실행하려면 디버그 메뉴에서 시작을 선택하거나 F5 키를 눌러야 합니다. 프로그램이 컴파일되어 별도의 차트에서 디버그 모드로 실행되며, 기간과 기호는 MetaEditor의 옵션 창의 디버깅 탭에서 지정할 수 있습니다.

그림 13. 편집기 옵션 - 디버깅.

F9 키를 누르거나 줄의 왼쪽을 두 번 클릭하거나 디버그 (Debug) 창에서 Toggle Breakpoint 를 선택하여 중단 점을 설정할 수 있습니다. 디버그 모드에서 프로그램 실행은 중단점이 있는 연산자보다 먼저 중지됩니다. 중지 후 디버그 (Debug) 탭이 Toolbox 창에 나타납니다 (그림 14.). 왼쪽에는 호출 스택 패널이 있습니다. 파일, 함수 및 행 번호가 표시됩니다. 오른쪽에는 감시 패널이 있으며 감시된 변수의 값이 표시됩니다. 감시 목록에 변수를 추가하려면 패널을 마우스 오른쪽 버튼으로 클릭하고 추가를 선택하거나 Insert 키를 누르십시오.

그림 14. 프로그램 디버그.

단계별 프로그램 실행은 F11, F10 또는 Shift+F11 키를 눌러 수행 할 수 있습니다. F11 키를 누르거나 디버그 (Debug) 메뉴에서 Step Into를 선택하면 호출된 모든 함수를 입력하는 프로그램 실행의 한 단계를 통과합니다. F10 키를 누르거나 디버그 (Debug) 메뉴에서 Step Over를 선택하면 호출된 함수를 입력하지 않고 프로그램 실행의 한 단계를 통과합니다. Shift+F11 키를 누르거나 디버그 (Debug) 메뉴에서 Step Out을 선택하면 한 단계 더 높은 프로그램 실행이 실행됩니다. 코드 왼쪽의 녹색 화살표는 실행될 코드 줄을 표시합니다.


결론

이 글에서는 간단한 Expert Advisor 및 인디케이터 작성 예제가 제시되고 MQL5 프로그래밍 언어의 기본 사항이 설명됩니다. 여기에 제공된 거래 시스템은 예시로 선택되었으므로 작성자는 실제 거래에서의 사용에 대해 책임을지지 않습니다.


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

파일 첨부됨 |
indicator_tp.mq5 (2.17 KB)
expert.mq5 (11.3 KB)
MQL5에서 객체 포인터 사용 MQL5에서 객체 포인터 사용
기본적으로 MQL5의 모든 오브젝트는 참조로 전달되지만 오브젝트 포인터를 사용할 가능성이 있습니다. 그러나 객체가 초기화되지 않을 수 있으므로 포인터 검사를 수행해야 합니다. 이 경우 MQL5 프로그램은 심각한 오류로 종료되고 언로드됩니다. 자동으로 생성된 객체는 이러한 오류를 일으키지 않으므로 이 점에서 매우 안전합니다. 이 글에서는 개체 참조와 개체 포인터의 차이점을 이해하고 포인터를 사용하는 보안 코드를 작성하는 방법을 고려합니다.
초보자를 위한 MQL5 : Expert Adviser의 기술 지표 사용 가이드 초보자를 위한 MQL5 : Expert Adviser의 기술 지표 사용 가이드
Expert Advisor에서 내장 또는 사용자 지정 인디케이터의 값을 얻으려면 먼저 해당 기능을 사용하여 핸들을 만들어야 합니다. 이 글에서의 예는 자신의 프로그램을 만드는 동안 이 또는 해당 기술 지표를 사용하는 방법을 보여줍니다. 이 글에서는 MQL5 언어로 빌드된 인디케이터에 대해 설명합니다. 트레이딩 전략 개발에 대한 경험이 많지 않은 사람들을 위해 제공되는 기능 라이브러리를 사용하여 지표로 작업하는 간단하고 명확한 방법을 제공합니다.
MQL5 for Newbies의 맞춤 인디케이터 MQL5 for Newbies의 맞춤 인디케이터
새로운 주제는 초보자에게 복잡하고 배우기 어려운 것 같습니다. 우리가 알고있는 주제는 우리에게 매우 간단하고 명확해 보입니다. 그러나 우리는 모든 사람이 처음부터 무언가를 심지어 우리의 모국어로 공부해야 한다는 걸 기억하지 못하는 것 같습니다. 자신의 거래 전략을 개발할 수있는 광범위한 가능성을 제공하는 MQL5 프로그래밍 언어도 마찬가지입니다. 기본 개념과 가장 간단한 예를 통해 학습을 시작할 수 있습니다. 기술 인디케이터와 MetaTrader 5 클라이언트 터미널의 상호 작용은 간단한 사용자 지정 인디케이터 SMA의 예에 대한 이 글에서 고려됩니다.
MQL5의 객체 생성 및 파괴 순서 MQL5의 객체 생성 및 파괴 순서
사용자 지정 개체, 동적 배열 또는 개체 배열 등 모든 개체는 특정 방식으로 MQL5 프로그램에서 생성 및 삭제됩니다. 종종 일부 개체는 다른 개체의 일부이며 초기화 해제시 개체 삭제 순서가 특히 중요합니다. 이 글에서는 개체 작업 메커니즘을 다루는 몇 가지 예를 제공합니다.