
MetaTrader의 MetaTrader: 하나의 차트에서 여러 로봇 실행하기
콘텐츠
소개
금융시장의 세계에서 자동매매 시스템은 의사결정 과정에서 필수적인 요소가 되었습니다. 이러한 시스템은 미리 정의된 규칙과 알고리즘을 사용하여 시장을 분석하고 진입 및 청산 결정을 내리고 거래를 체결하도록 구성할 수 있습니다. 하지만 여러 차트에서 로봇을 설정하고 실행하는 것은 시간이 많이 걸리는 작업일 수 있습니다. 각 로봇이 각 차트에 대해 개별적으로 구성되어야 하므로 이는 우리에게 추가적인 노력을 요구하게 됩니다.
이 글에서는 MetaTrader 4와 5에서 범용 로봇을 만들 수 있는 간단한 템플릿을 구현하는 방법을 보여드리겠습니다. 템플릿을 사용하면 여러분은 로봇을 하나의 차트에 첨부하고 나머지 차트는 EA 내에서 처리할 수 있습니다. 따라서 이 템플릿은 여러분이 여러 개의 차트에서 로봇을 설정하고 실행하는 프로세스를 크게 간소화하여 여러분의 시간과 노력을 절약하도록 해 줄입니다 이 기사에서는 이와 같은 아이디어에서 테스트에 이르기까지 MQL5에서 이러한 로봇을 만드는 과정을 자세히 살펴보겠습니다.
문제 설명 및 적용 가능성 제한
이 아이디어는 얼마 전에 떠올랐지만 저는 오랫동안 전문적인 판매자의 비슷한 결정을 봐 왔습니다. 즉 제가 이 분야에서 아이디어를 낸 최초도 아니고 마지막도 아니지만 항상 그렇듯이 프로그래머가 그러한 결정을 내리기 위해서는 몇 가지 조건이 충족되어야 합니다. MQL5 스토어에서 이러한 Expert Advisors를 개발하는 주된 이유는 사용자 편의성에 대한 열망 때문입니다. 하지만 저의 경우에는 조금 다른 동기가 있었습니다. 저는 여러 상품에 대해 여러 전략을 동시에 테스트하거나 동일한 전략이지만 여러 통화 특성을 확인하기 위해 먼저 여러 전략을 테스트해야 했습니다.
또한 테스터에서 전략을 테스트할 때 특히 다중 통화 모드에서 매우 중요한 요소는 수익성 의 곡선으로 이는 과거 데이터를 백 테스트 할 때 자동 거래 시스템 평가의 기초가 됩니다. 하나의 상품에서 트레이딩 시스템을 개별적으로 테스트할 때 나중에 이러한 보고서를 결합하는 것은 매우 어렵습니다. 적어도 MetaTrader 5에서는 그런 도구가 있는지 모르겠습니다. 네 번째 버전의 터미널에는 이러한 조작을 위한 비공식 도구가 하나 있습니다. 저는 적어도 하나의 기사에서 이 방법을 사용했지만 물론 이러한 접근 방식은 바람직하지 않습니다.
테스트 과정 외에도 자동매매 자체와 각 차트에서 독립적으로 작동하는 유사한 EA의 동기화 과정도 마찬가지로 중요합니다. 이러한 차트가 너무 많으면 컴퓨터 리소스가 추가로 필요해 거래 성능이 느려지게 되고 예기치 않은 오류 및 기타 불쾌한 사고가 발생하여 최종 거래 결과에 악영향을 미칠 수 있습니다. 이러한 각 EA에 대해 우리는 고유한 주문 ID, 빈번한 서버 요청에 대한 보호 및 언뜻 보기에는 명확하지 않은 기타 여러 가지 사항을 준비해야 합니다.
EA의 그래픽 부분을 처리하는 것은 별도의 매우 민감한 문제입니다. 이제 어느 정도 숙련된 모든 EA 제작자는 EA가 첨부된 차트에 최소한의 표시를 합니다. 이렇게 하면 EA가 더 진지해 보이고 자신감 있어 보이며 항상 사용자에게 차트에 일부 정보를 표시하게 하면 사용자는 EA 거래 프로세스를 더 효과적으로 제어할 수 있습니다. 또한 필요한 경우 수동으로 제어를 하기 위한 요소를 추가할 수도 있습니다. 이 모든 것을 사용자 인터페이스라고 합니다. 이러한 EA를 각각의 차트에 적용하고 인터페이스에서 그래픽, 텍스트 및 숫자 정보를 모두 업데이트하는 일은 기하급수적으로 증가하게 됩니다. 그러나 만약 우리가 멀티 템플릿을 사용하게 되면 터미널에서 최소한의 리소스를 필요로 하는 하나의 인터페이스만 있으면 됩니다.
물론 이러한 템플릿이 모든 문제를 해결하지는 못하지만 그럼에도 불구하고 제 프로젝트에서는 많은 도움이 됩니다. 저는 다양한 로봇을 사용하며 일반적으로 모든 접근 방식은 그마다의 특성이 있지만 저의 방법은 많은 초보 프로그래머가 쉽게 사용할 수 있다고 생각합니다. 저의 방법을 완전히 복사할 필요는 없지만 원하는 경우 필요에 맞게 쉽게 조정할 수 있습니다. 제 목표는 특별한 것을 알려드리는 것이 아니라 그러한 문제를 해결할 수 있는 옵션 중 하나를 보여드리고 설명하는 것입니다.
멀티봇 사용 측면에서 MetaTrader 4와 MetaTrader 5 터미널의 차이점
최신 MetaTrader 5의 가장 마음에 드는 점은 테스터의 강력한 기능입니다. 위에서 설명한 EA 개발 방식을 사용한다면 여러분은 여러 상품에서 동시에 테스트하는 데 필요한 모든 기능을 누릴 수 있습니다. 테스터는 시간별로 시세를 자동 동기화하여 시간 척도에 따라 명확하게 동기화된 수익성 곡선을 제공합니다. MetaTrader 4에는 이러한 기능이 없습니다. 이것이 가장 큰 단점이라고 생각합니다. 그럼에도 불구하고 MetaQuotes는 네 번째 터미널을 지원하기 위해 최선을 다하고 있으며 그 인기가 여전히 높다는 점은 주목할 가치가 있습니다. 저는 MetaTrader 4의 적극적인 사용자로서 이러한 단점은 생각만큼 크지 않다고 말할 수 있습니다.
MQL4 언어가 최근 MQL5로 업데이트되었습니다. 즉 우리와 비슷한 템플릿을 작성할 때 코드의 차이는 그리 크지 않습니다. 두터미널 모두에 대해 구현을 하는 것이 제가 일하는 방식입니다. 그러므로 여러분은 두 터미널 모두에 대한 템플릿을 받게 될 것입니다. 이러한 이전 터미널의 개선을 통해 우리는 정말 필요한 다음과 같은 기능을 사용할 수 있게 되었습니다:
- CopyClose - 바 종가 요청
- CopyOpen - 바 오픈 가격 요청
- CopyHigh - 바 피크 요청
- CopyLow - 바 저가 요청
- CopyTime - 바 오픈 시간 요청
- SymbolInfoTick - 요청된 심볼에 대한 마지막 수신 틱 요청
- SymbolInfoInteger - 정수와 번호가 매겨진 목록으로 설명할 수 있는 심볼 데이터를 요청합니다.
- SymbolInfo******* - 필요한 기타 함수
이러한 기능은 MQL4와 MQL5모두에 있습니다. 이러한 함수를 사용하면 모든 심볼과 기간에 대한 바 데이터를 가져올 수 있습니다. 따라서 네 번째 버전과 다섯 번째 버전의 테스터 간의 유일한 차이점은 네 번째 터미널의 이러한 기능이 테스트가 수행되는 현재 차트에서만 작동하고 나머지 요청은 MetaTrader 4 테스터의 특성으로 인해 데이터가 없음이라고 단순히 표시한다는 사실입니다. 따라서 템플릿을 테스트할 때는 선택한 심볼과 단일 로봇의 수익 곡선 중 하나에 대해서만 거래가 이루어집니다.
다섯 번째 터미널에서는 여러분은 이미 요청된 모든 심볼과 공통 수익성 라인에 대한 거래를 받게 됩니다. 거래에 적용하는 것과 관련하여 두 터미널에서 이러한 로봇을 사용하면 이러한 템플릿의 전체 성능을 사용하게 됩니다. 즉 차이점은 테스터에만 있습니다. 그러나 이러한 경우에도 EA를 만들 때 MetaTrader 5 버전으로 시작하는 것이 더 낫다는 사실에서 얽매일 필요는 없습니다. 필요한 모든 테스트가 끝나면 MetaTrader 4용 버전을 빠르게 만들 수 있습니다.
물론 제가 다루지 않은 여러 가지 차이점이 있습니다. 이러한 템플릿을 위한 정교한 구조를 만들 때는 이러한 뉘앙스를 알아야 합니다. 저는 그 중 일부의 중요성을 강조하고 싶습니다. MetaTrader 5는 이전 버전보다 확실히 낫지만 그럼에도 불구하고 네번째 터미널은 많은 상황에서 계산 리소스에 대한 요구가 다섯 번째 터미널에 비해 그리 크지 않기 때문에 저는 네 번째 터미널을 버리고 싶지 않습니다. 두 도구 모두 여전히 훌륭합니다.
범용 템플릿 구축의 뉘앙스
이러한 템플릿을 만들려면 터미널이 어떻게 작동하는지 Expert Advisor가 무엇인지, MetaTrader 차트가 무엇인지 이해해야 합니다. 또한 각차트는 별도의 객체라는 점을 이해해야 합니다. 이러한 각 차트는 여러 지표와 하나의 EA에만 연결될 수 있습니다. 동일한 차트가 여러 개 있을 수도 있습니다. 일반적으로 하나의 심볼 기간에 여러 개의 다른 EA를 실행하거나 서로 다른 설정으로 하나의 EA를 실행하기 위해서는 여러 개의 차트가 만들어집니다. 이러한 미묘한 차이를 이해하면 우리는 다음과 같은 결론을 얻게 됩니다. 멀티 차트를 사용하지 않으려면 템플릿 내에서 이 모든 것을 구현해야 한다는 것입니다. 이는 다이어그램의 형태로 표현될 수 있습니다:
이와는 별도로 tick에 대해서도 언급해야 합니다. 이 접근 방식의 단점은 각각의 차트에 대해 새로운 틱이 나타나도록 핸들러를 구독할 수 없다는 사실로 로봇이 작업 중인 차트에서 틱을 적용하거나 타이머를 사용해야 합니다. 궁극적으로 이것은 틱 로봇에게 다음과 같은 불쾌한 순간을 의미하게 될 것입니다:
- 우리는 사용자 정의 OnTick 핸들러를 작성해야 합니다.
- 이러한 핸들러는 OnTimer의 파생으로 구현해야 합니다.
- OnTimer는 지연과 함께 작동하기 때문에 틱은 완벽하지 않습니다(지연 값은 중요하지 않지만 그 존재가 중요합니다).
- 틱을 얻으려면 SymbolInfoTick 함수가 필요합니다.
밀리초 단위로 시간을 계산하는 분들, 특히 차익거래를 좋아하는 분들에게는 거부할 수 없는 것이 될 수 있다고 생각합니다. 하지만 저는 제 템플릿에서는 이 점을 강조하지 않습니다. 수년간 다양한 시스템을 구축하면서 저는 바 트레이딩 패러다임에 도달했습니다. 즉, 거래 및 기타 계산은 대부분 새로운 바가 나타날 때 발생합니다. 이 접근 방식에는 여러 가지 분명한 장점이 있습니다:
- 새로운 바의 시작을 결정하는 데 부정확하더라도 거래에 큰 영향을 미치지 않습니다.
- 바의 기간이 길수록 이러한 영향은 줄어듭니다.
- 바 형태의 이산화를 통해 테스트 속도가 크게 향상됩니다.
- 실제 틱 테스트와 인적인 틱 테스트 모두에서 동일한 테스트 품질을 제공합니다.
이 접근 방식은 EA 구축에서 특정한 패러다임을 알려줍니다. 이 패러다임은 틱 EA와 관련된 많은 문제를 해결하고 테스트 프로세스의 속도를 높이며 주요 장애물인 수익에 대한 높은 수학적 기대치를 제공하며 많은 시간과 컴퓨팅 파워를 절약할 수 있도록 해줍니다. 더 많은 장점을 찾을 수 있다고 생각하지만 이 글의 맥락에서는 이것으로 충분하다고 생각합니다.
템플릿을 구현하기 위해 템플릿 내부에 거래 터미널의 작업 공간 전체 구조를 구현할 필요는 없으며 각 로봇에 대해 별도의 차트를 구현하는 것만으로도 충분합니다. 이것이 가장 최적의 구조는 아니지만 우리가 각각의 개별 상품이 상품 목록에 한 번만 표시된다는 사실을 받아들인다면 이 최적화는 필요하지 않습니다. 이는 다음과 같이 표시됩니다:
차트를 구현하기 위해 가장 간단한 구조를 구현했습니다. 이제 이러한 템플릿의 입력, 그리고 더 중요한 것은 MQL5 언어의 허용 가능성 내에서 각각의 상황에 대한 동적 차트의 수 및 EA의 수를 고려하는 방법입니다. 이 문제를 해결할 수 있는 유일한 방법은 문자열 입력 변수를 사용하는 것입니다. 문자열을 사용하면 매우 많은 양의 데이터를 저장할 수 있습니다. 실제로 이러한 템플릿에 필요한 모든 매개변수를 설명하기 위해서는 입력 데이터에 동적 배열이 필요합니다. 물론 그런 기회를 이용하는 사람이 거의 없기 때문에 아무도 그런 일을 실행하지 않을 것입니다. 문자열은 우리가 원하는 모든 것을 넣을 수 있는 동적 배열입니다. 이제 사용해 보겠습니다. 가장 간단한 템플릿의 경우 다음과 같이 세 가지 변수를 도입하기로 했습니다:
- Charts - 우리의 차트(목록)
- Chart Lots - 거래용 랏(목록)
- Chart Timeframes - 차트 기간(목록)
일반적으로 이 모든 데이터를 하나의 문자열로 결합할 수 있지만 그러면 그 구조가 복잡해지고 잠재적인 사용자가 데이터를 올바르게 설명하는 방법을 파악하기 어렵습니다. 또한 이 데이터를 문자열에서 가져오는 변환 기능의 엄청난 복잡성은 말할 것도 없고 작성 과정에서 실수를 저지르기 쉬우며 사용할 때 매우 불쾌한 일이 많이 발생할 수 있습니다. 판매자들 사이에서 비슷한 솔루션을 보았는데 대체적으로는 모든 것이 제대로 작동했습니다. 모든 데이터는 쉼표로 구분하여 간단히 나열됩니다. EA가 시작될 때 특수 함수를 사용하여 문자열에서 데이터를 가져와 해당 동적 배열을 채운 다음 코드에서 사용합니다. 우리도 이 방식을 따라갈 것입니다. 동일한 열거 규칙을 사용하여 유사한 문자열을 더 추가할 수 있습니다. 저는 ":" 를 구분 기호로 사용하기로 했습니다. 쉼표를 사용하면 차트 랏과 같은 더블 배열을 처리하는 방법이 명확하지 않습니다. 이러한 문자열 변수를 더 추가할 수 있으며 일반적으로 훨씬 더 완벽하고 다양한 템플릿을 구성할 수 있지만 여기서는 이를 구현하는 방법을 보여드리고 빠르고 쉽게 수정할 수 있는 템플릿의 첫 번째 버전을 제공하도록 하겠습니다.
이러한 배열을 구현하는 것만으로는 충분하지 않으며 예를 들어 공통 변수를 구현하는 것도 필요합니다:
- 서명되지 않은 작업 기간 - 지정되지 않은 차트 기간
- 미서명 랏 수정 - 지정되지 않은 랏
차트 목록이 채워져야 합니다. 차트 랏 및 차트 차트주기에 대해서도 동일한 작업을 선택할 수 있습니다. 예를 들어 모든 차트에 대해 한개의 랏 사용하고 모든 차트에 대해 동일한 기간을 사용할 수 있습니다. 템플릿에도 비슷한 기능이 구현될 예정입니다. 이러한 템플릿을 기반으로 구축된 EA의 입력 매개변수를 설정할 때 간결성과 명확성을 보장하기 위해 가능한 한 이러한 구현 규칙을 적용하는 것이 바람직합니다.
이제 이러한 패턴을 최소한으로 구현하기 위해 몇 가지 중요한 변수를 더 정의해 보겠습니다:
- 마지막 바의 수 - 각 차트에 대해 우리가 저장하는 차트의 마지막 바의 수입니다.
- 랏 보증금 - 지정된 랏을 사용하기 위한 보증금
- 첫번째 매직 - 별도의 EA 거래를 위한 고유 ID
첫 번째 변수는 꽤 명확하다고 생각합니다. 두 번째 변수는 파악하기 훨씬 더 어렵습니다. 이것이 제가 EA에서 자동 랏 통제하는 방식입니다. 만약 제가 "0"으로 설정하면 알고리즘에게 해당 문자열 또는 위에서 고려한 공유 변수에 지정된 고정 랏만 거래하면 된다는 것을 알려주는 것입니다. 그렇지 않으면 저는 설정에 지정된 랏이 적용될 수 있도록 필요한 보증금을 설정합니다. 증거금이 적거나 많으면 이 랏의 가치가 방정식에 따라 변경된다는 것을 쉽게 알 수 있습니다:
- Lot = Input Lot * ( Current Deposit / Deposit For Lot )
이제 모든 것이 명확해졌을 것 같습니다. 고정 랏을 원하면 0으로 설정하고 그 외의 경우에는 입력 설정에서 위험도에 따라 증거금을 조정합니다. 저렴하고 좋을 것 같네요. 필요한 경우 여러분은 자동 랏에 대한 위험을 평가하는 방식을 변경할 수 있지만 개인적으로 이 옵션이 마음에 듭니다.
동기화 특히 Expert Magic Number 설정과 같은 문제에 대해 알아볼 가치가 있습니다. EA로 거래하거나 혼합된 형태로 거래할 때 자존심이 강한 모든 프로그래머는 이 특정 변수에 특별한 주의를 기울입니다. 문제는 여러 EA를 사용할 때는 각각의 EA에 고유한 ID가 있는지 확인하는 것이 매우 중요하다는 것입니다. 그렇지 않으면 주문, 거래 또는 포지션으로 작업할 때 완전히 엉망이 되고 전략이 제대로 작동하지 않으며 대부분의 경우 완전히 작동을 멈추게 됩니다. 제가 이유를 설명할 필요가 없기를 바랍니다. 우리는 EA가 차트에 배치될 때마다 이러한 ID를 구성하고 반복되지 않도록 해야 합니다. 단 한 번의 실수라도 비참한 결과를 초래할 수 있습니다. 또한 실수로 EA와 함께 차트를 닫은 경우 차트를 새로 다시 구성해야 합니다. 결과적으로 실수할 확률이 크게 높아집니다. 또한 다른 많은 측면에서도 매우 불쾌합니다. 예를 들어 차트를 닫고 어떤 ID가 사용되었는지 잊어버리는 경우가 있습니다. 이 경우 거래 내역을 파헤쳐서 찾아야 합니다. ID가 없으면 새로 다시 시작한 EA가 제대로 작동하지 않을 수 있으며 더 많은 불쾌한 일이 발생할 수 있습니다.
저와 같은 템플릿을 사용하면 EA 설정에서 시작 ID만 설정하면 됩니다. 이후 나머지 ID는 증분을 사용하여 자동으로 생성되어 해당 EA 사본에 할당되므로 이러한 제어에서 자유롭고 오류 가능성을 최소화할 수 있습니다. 이 프로세스는 다시 시작할 때마다 자동으로 수행됩니다. 어쨌든 시작 아이디 하나만 기억하는 것이 중간에 임의의 아이디를 기억하는 것보다 훨씬 쉽습니다.
범용 템플릿 작성
이제 템플릿을 구현할 차례입니다. 불필요한 요소는 생략하려고 노력할 것이므로 이 템플릿이 필요한 사람은 누구나 소스 코드에서 나머지를 다운로드하여 확인할 수 있습니다. 여기서는 아이디어와 직접적으로 관련된 내용만 보여드리겠습니다. 스탑 레벨 및 기타 매개변수는 사용자가 정의합니다. 제가 구현한 내용은 소스 코드에서 확인할 수 있습니다. 먼저 반드시 필요한 입력 변수를 정의해 보겠습니다:
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes input int LastBars=10;//Last Bars Count input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned input double RepurchaseLotE=0.01;//Fix Lot For Unsigned input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix) input int MagicE=156;//First Magic
여기에서 공유 변수의 예와 마찬가지로 동적 배열을 반영하는 문자열 변수를 채우는 예제를 볼 수 있습니다. 참고로 이 코드는 MQL4와 MQL5에서 모두 동일하게 보일 것입니다. 저는 가능한 한 모든 것을 비슷하게 만들려고 노력했습니다.
이제 문자열 데이터를 가져올 방법을 결정해 보겠습니다. 이 작업은 해당 함수에 의해 수행되지만 우리는 먼저 문자열에서 얻은 데이터를 추가할 배열을 만들 것입니다:
//+------------------------------------------------------------------+ //|Arrays | //+------------------------------------------------------------------+ string S[];// Symbols array double L[];//Lots array ENUM_TIMEFRAMES T[];//Timeframes array
다음 함수는 이러한 배열을 채웁니다:
//+------------------------------------------------------------------+ //| Fill arrays | //+------------------------------------------------------------------+ void ConstructArrays() { int SCount=1; for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools { if (SymbolsE[i] == ':') { SCount++; } } ArrayResize(S,SCount);//set the size of the character array ArrayResize(CN,SCount);//set the size of the array to use bars for each character int Hc=0;//found instrument index for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools { if (i == 0)//if we just started { int LastIndex=-1; for (int j = i; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i,LastIndex); Hc++; } else { S[Hc]=SymbolsE; Hc++; } } if (SymbolsE[i] == ':') { int LastIndex=-1; for (int j = i+1; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1)); Hc++; } else { S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1)); Hc++; } } } for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars { CN[i]=LastBars; } ConstructLots(); ConstructTimeframe(); }
간단히 말해 여기서 문자열의 데이터 양은 구분 기호 덕분에 계산됩니다. 첫 번째 배열을 기준으로 다른 모든 배열의 크기는 심볼이 있는 배열과 유사하게 설정되며 그 후 심볼이 먼저 채워진 다음Construct Lots 및 ConstructTimeframe과 같은 함수를 통해 채워집니다. 구현 방식은 이 함수의 구현 방식과 유사하지만 몇 가지 차이점이 있습니다. 여러분은 소스 코드에서 구현을 확인할 수 있습니다. 중복 코드를 표시하지 않기 위해 글에 추가하지 않았습니다.
이제 가상 차트에 적합한 클래스와 그에 따라 연결된 가상 로봇을 만들어야 합니다. 가상 차트와 EA가 배열에 저장되도록 정의하는 것부터 시작하겠습니다:
//+------------------------------------------------------------------+
//| Charts & experts pointers |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];
chart class부터 시작하겠습니다:
//+------------------------------------------------------------------+ //| Chart class | //+------------------------------------------------------------------+ class Chart { public: datetime TimeI[]; double CloseI[]; double OpenI[]; double HighI[]; double LowI[]; string BasicSymbol;//the base instrument that was extracted from the substring double ChartPoint;//point size of the current chart double ChartAsk;//Ask double ChartBid;//Bid datetime tTimeI[];//auxiliary array to control the appearance of a new bar static int TCN;//tcn string CurrentSymbol;//symbol ENUM_TIMEFRAMES Timeframe;//timeframe int copied;//how much data is copied int lastcopied;//last amount of data copied datetime LastCloseTime;//last bar time MqlTick LastTick;//last tick fos this instrument Chart() { ArrayResize(tTimeI,2); } void ChartTick()//this chart tick { SymbolInfoTick(CurrentSymbol,LastTick); ArraySetAsSeries(tTimeI,false); copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI); ArraySetAsSeries(tTimeI,true); if ( copied == 2 && tTimeI[1] > LastCloseTime ) { ArraySetAsSeries(CloseI,false); ArraySetAsSeries(OpenI,false); ArraySetAsSeries(HighI,false); ArraySetAsSeries(LowI,false); ArraySetAsSeries(TimeI,false); lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI); lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI); lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI); lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI); lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI); ArraySetAsSeries(CloseI,true); ArraySetAsSeries(OpenI,true); ArraySetAsSeries(HighI,true); ArraySetAsSeries(LowI,true); ArraySetAsSeries(TimeI,true); LastCloseTime=tTimeI[1]; } ChartBid=LastTick.bid; ChartAsk=LastTick.ask; ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT); } }; int Chart::TCN = 0;
이 클래스에는 틱과 바의 업데이트를 제어하는 함수가 하나만 있으며 특정 차트에 필요한 매개변수를 식별하는 데 필요한 필드도 있습니다. 일부 매개변수가 누락되었습니다. 원하는 경우 예를 들어 ChartPoint 업데이트와 같이 누락된 업데이트를 추가하여 추가할 수 있습니다. 바 배열은 MQL4스타일로 만들어집니다. 저는 여전히 MQL4에서 미리 정해진 배열로 작업하는 것이 매우 편리합니다. 제로 바가 현재 바라는 것이 편리합니다. 어쨌든 이것은 저의 비전일 뿐입니다. 여러분은 원하는 대로 자유롭게 따라할 수 있습니다.
이제 우리는 별도의 가상 EA의 클래스를 나타내야 합니다:
//+------------------------------------------------------------------+ //| Bot instance class | //+------------------------------------------------------------------+ class BotInstance//expert advisor object { public: CPositionInfo m_position;// trade position object CTrade m_trade;// trading object ///-------------------this robot settings---------------------- int MagicF;//Magic string CurrentSymbol;//Symbol double CurrentLot;//Start Lot int chartindex;//Chart Index ///------------------------------------------------------------ ///constructor BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index { chartindex=chartindex0; MagicF=MagicE+index; CurrentSymbol=Charts[chartindex].CurrentSymbol; CurrentLot=L[index]; m_trade.SetExpertMagicNumber(MagicF); } /// void InstanceTick()//bot tick { if ( bNewBar() ) Trade(); } private: datetime Time0; bool bNewBar()//new bar { if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 ) { if (Time0 != 0) { Time0=Charts[chartindex].TimeI[1]; return true; } else { Time0=Charts[chartindex].TimeI[1]; return false; } } else return false; } //////************************************Main Logic******************************************************************** void Trade()//main trade function { //Close[0] --> Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart //Open[0] --> Charts[chartindex].OpenI[0] ----------------------------------------------------------------------- //High[0] --> Charts[chartindex].HighI[0] ----------------------------------------------------------------------- //Low[0] --> Charts[chartindex].LowI[0] ------------------------------------------------------------------------- //Time[0] --> Charts[chartindex].TimeI[0] ----------------------------------------------------------------------- if ( true ) { CloseBuyF(); //CloseSellF(); } if ( true ) { BuyF(); //SellF(); } } double OptimalLot()//optimal lot calculation { if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE); else return CurrentLot; } //here you can add functionality or variables if the trading function turns out to be too complicated //////******************************************************************************************************************* ///trade functions int OrdersG()//the number of open positions / orders of this virtual robot { ulong ticket; bool ord; int OrdersG=0; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol ) { OrdersG++; } } return OrdersG; } /////////********/////////********//////////***********/////////trade function code block void BuyF()//buy market { double DtA; double CorrectedLot; DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime if ( (DtA > 0 || DtA < 0) ) { CorrectedLot=OptimalLot(Charts[chartindex]); if ( CorrectedLot > 0.0 ) { //try buy logic } } } void SellF()//sell market { //Same logic } void CloseSellF()//close sell position { ulong ticket; bool ord; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol ) { //Close Sell logic } } } void CloseBuyF()//close buy position { //same logic } bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot { int MagicT[]; ArrayResize(MagicT,magiccount); for ( int i=0; i<magiccount; i++ ) { MagicT[i]=MagicE+i; } for ( int i=0; i<ArraySize(MagicT); i++ ) { if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true; } return false; } /////////********/////////********//////////***********/////////end trade function code block };
코드의 양을 줄이기 위해 반복적인 로직을 일부 제거했습니다. EA의 전체 알고리즘이 구현되는 클래스입니다. 이 클래스의 주요 기능입니다:
- Trade() - 해당 차트의 바 핸들러에서 호출되는 기본 거래 함수입니다.
- BuyF() - 시장 함수로 구매
- SellF() - 시장 함수를 통한 판매
- CloseBuyF() - 시장별 매수 포지션 청산 함수
- CloseSellF() - 시장별 매도 포지션 청산 함수
이것은 각각 바의 거래를 시연하기 위한 최소한의 함수의 집합입니다. 이 데모에서는 아무 포지션이나 열고 다음 바에서 청산하면 됩니다. 이 글의 틀 안에서 이 정도면 충분합니다. 이 클래스에는 이해를 보완할 수 있는 몇 가지 추가 기능들이 있습니다:
- OrdersG() - 차트에 연결된 특정 심볼에 개설된 포지션 계산
- OptimalLot() - 거래 함수에 보내기 전에 랏을 준비합니다(고정 랏 선택 또는 자동 랏 계산).
- bOurMagic() - 기록에서 허용된 목록에 맞는지 트랜잭션을 확인합니다(사용자 지정 기록을 정렬하는 경우에만 해당).
이러한 함수는 트레이딩 로직을 구현하는 데 필요할 수 있습니다. 새로운 바 핸들러를 사용하는 것도 합리적일 것입니다:
- InstanceTick() - 별도의 EA 인스턴스에 대한 틱 시뮬레이션
- bNewBar() - 새로운 바의 모양을 확인하기 위한 조건자InstanceTick 내부에서 사용)
조건자에 새로운 바가 표시되면 Trade 함수가 트리거 됩니다. 주요 트레이딩 로직을 설정하는 기능입니다. 해당 차트와의 연결은 인스턴스가 생성될 때 할당된 chartindex 변수를 사용하여 수행됩니다. 따라서 각각의 EA 인스턴스는 쿼트를 가져와야 하는 차트를 알고 있습니다.
이제 가상 차트와 EA를 직접 만드는 과정을 살펴보겠습니다. 가상 차트가 먼저 생성됩니다:
//+------------------------------------------------------------------+ //| Creation of graph objects | //+------------------------------------------------------------------+ void CreateCharts() { bool bAlready; int num=0; string TempSymbols[]; string Symbols[]; ConstructArrays();//array preparation int tempcnum=CN[0]; Chart::TCN=tempcnum;//required number of stored bars for all instruments for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph { Charts[j] = new Chart(); Charts[j].lastcopied=0; ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays ArrayResize(Charts[j].OpenI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].HighI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].LowI,tempcnum+2);//----------------------------------- ArrayResize(Charts[j].TimeI,tempcnum+2);//---------------------------------- Charts[j].CurrentSymbol = S[j];//symbol Charts[j].Timeframe = T[j];//timeframe } ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots }
차트를 만들고 가상 EA로 배열의 크기를 설정한 후에는 EA 자체의 인스턴스를 생성하고 가상 EA와 차트의 연결을 구현해야 합니다:
//+------------------------------------------------------------------+ //| create and hang all virtual robots on charts | //+------------------------------------------------------------------+ void CreateInstances() { for (int i = 0; i < ArraySize(S); i++) { for (int j = 0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); break; } } } }
연결은 가상 EA를 생성할 때 각 인스턴스에 설정된 "j" 인덱스를 사용하여 수행됩니다. 위에 표시된 해당 변수가 강조 표시되어 있습니다. 물론이 모든 것은 여러 가지 방법으로 훨씬 더 우아하게 수행 될 수 있지만 가장 중요한 것은 일반적인 아이디어가 명확하다는 것입니다.
이제 남은 것은 각 차트와 EA에서 어떻게 틱이 시뮬레이션되는지 를 보여주는 것뿐입니다:
//+------------------------------------------------------------------+ //| All bcharts & all bots tick imitation | //+------------------------------------------------------------------+ void AllChartsTick() { for (int i = 0; i < ArraySize(Charts); i++) { Charts[i].ChartTick(); } } void AllBotsTick() { for (int i = 0; i < ArraySize(S); i++) { if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick(); } }
이 템플릿은 제가 훨씬 더 어려운 작업을 위해 만든 복잡한 템플릿을 다시 작업 하여 얻은 것이므로 여기저기에 과도한 작업물이 있을 수 있다는 점에 유의하세요. 여러분 스스로 원한다면 여기서 제거할 것들을 제거하고 코드를 더 깔끔하게 만들 수 있을 것입니다.
템플릿 외에도 간단한 인터페이스가 있어 프리랜스 서비스나 다른 목적으로 주문을 할 때 유용하게 사용할 수 있습니다:
이 인터페이스에 여유 공간을 남겨두었으므로 공간이 충분하지 않은 경우 세 개의 항목으로 충분할 것입니다. 필요한 경우 구조를 쉽게 확장하거나 완전히 변경할 수 있습니다. 이 예제에서 누락된 세 개의 필드를 추가하려면 코드에서 다음 위치를 찾아야 합니다:
//+------------------------------------------------------------------+ //| Reserved elements | //+------------------------------------------------------------------+ "template-UNSIGNED1",//UNSIGNED1 "template-UNSIGNED2",//UNSIGNED2 "template-UNSIGNED3",//UNSIGNED3 //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1 //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2 //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3 //////////////////////////// //TempText="UNSIGNED1 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText); //TempText="UNSIGNED2 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText); //TempText="UNSIGNED3 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText); ///////////////////////////
처음 세 항목 은 인터페이스에 새로운 요소의 이름을 지정하고 두 번째 세 항목 은 EA를 시작할 때 인터페이스를 만들 때 사용되며 마지막 세번째 항목은 인터페이스의 정보를 업데이트하는 함수에 사용됩니다. 이제 두 템플릿의 성능을 테스트할 차례입니다. 테스터 시각화 도구는 시각적인 데모를 하는 데에 충분합니다. MetaTrader 5의 비주얼라이저가 훨씬 낫기 때문에 저는 이 옵션만 보여드리겠습니다. 또한 작업 결과는 효율성을 확인하는 데 필요한 모든 것을 명확하게 보여줄 것입니다:
보시다시피 주요 외환 쌍에 대한 7가지 차트를 모두 업로드했습니다. 시각화 로그는 나열된 모든 심볼에 대해 거래가 진행 중임을 보여줍니다. 거래는 필요에 따라 독립적으로 수행됩니다. 즉 EA는 각각 자체의 차트에서 거래하며 상호 작용을 전혀 하지 않습니다.
결론
이 기사에서는 MetaTrader 4 및 MetaTrader 5 터미널용 범용 템플릿 구축의 주요 뉘앙스를 검토하고 간단하지만 작동하는 템플릿을 만들고 작업의 가장 중요한 점을 분석하고 MetaTrader 5 테스터 비주얼라이저를 사용하여 그 실행 가능성을 확인했습니다. 이제 여러분은 이와 같은 템플릿이 그렇게 복잡하지 않다는 것을 알 수 있을 것입니다. 일반적으로 이러한 템플릿을 다양하게 구현할 수 있지만 적용 가능한 템플릿은 완전히 다를 수 있다는 것은 분명합니다. 가장 중요한 것은 이러한 구조를 구축하는 기본적인 뉘앙스를 이해하는 것입니다. 필요한 경우 여러분이 개인용으로 템플릿을 다시 바꾸어 작업할 수 있습니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/12434



