
Expert Advisor 개발 기초부터(31부): 미래를 향해(IV)
소개
"Expert Advisor 개발 기초부터(파트 29)" 글에서 우리는 EA에서 차트 트레이드를 제거한 후 차트 트레이드 패널을 지표로 전환했습니다. 이를 수행하는 방법과 지표를 계속 작동시키는 데 필요한 기능을 조정하고 유지 관리하는 방법은 파트 30에 설명되어 있습니다. 이것은 가능한 접근 방식 중 하나로 실제로는 다른 방법도 있지만 이에 대해서는 다음 기회에 고려할 것입니다.
따라서 우리는 아직 EA에서 제거해야 할 부분이 남아 있습니다. 우리는 그 부분을 지금 삭제할 예정이며 이 글이 이 시리즈의 마지막 글이 될 것입니다. 제거해야 할 것은 사운드 시스템입니다. 이전 글을 읽지 않았다면 혼란스러울 수 있습니다.
전체 프로세스가 어떻게 진행되는지 이해하기 위해(설명이 필요한 부분이 많으므로) 이전 글에 기반한 거의 동일한 모델을 사용하겠습니다. 이렇게 하면 전문 프로그래머가 아닌 분들도 이해하기 쉽게 설명할 수 있습니다. 또한 약간의 재미를 더하기 위해 시스템을 약간 복잡하게 만들 것입니다.
이번에 새로운 부분은 사운드 시스템입니다. 이 부분은 더 이상 EA의 일부가 아닙니다. 하지만 이는 앞으로 많은 이점을 제공할 것입니다. 하지만 여기서 중요한 것은 앞으로 어떤 일이 일어날지 이해하는 것이니 서두르지 마세요. 이 글은 비교적 짧지만 흥미로울 것입니다.
사운드 서비스 도입
이러한 모든 변화는 여러분을 짜증나게 만들 수 있습니다. 하지만 이 아이디어는 여러분을 당황하게 하려는 것이 아니라 작은 변화가 어떻게 큰 변화를 가져오고 MetaTrader 5 플랫폼을 훨씬 더 즐겁게 사용할 수 있는지를 보여주기 위한 것입니다. 동시에 이러한 모든 작업이 어떻게 여러 변화를 가능하게 하는지 확인할 수 있습니다.
이렇게 하면 필요한 것과 필요하지 않은 것을 선택할 수 있습니다. 어떤 기능이 실제로 사용되면 나중에 개선 사항을 추가하여 더욱 유용하고 즐거운 기능으로 만들 수 있습니다. 이렇게 하면 이전에 생성한 내용을 크게 변경하거나 다시 프로그래밍할 필요가 없습니다. 아이디어는 항상 재사용한다는 것입니다.
이러한 기능 중 하나가 바로 사운드 시스템입니다. 이 시스템을 EA 내부에 두는 것이 좋은 생각인 것처럼 보일 수 있습니다. 어떤 의미에서 이 시스템은 EA의 전체 기능을 방해하지는 않습니다. 그러나 EA에서 이를 제거하고 이들 간의 통신을 구현하면 마치 사운드 라이브러리처럼 매우 간단한 방법으로 사운드 알림을 사용할 수 있다는 것을 알 수 있습니다. 솔루션은 매우 유용할 것입니다.
EA 내에만 알림 시스템을 배치하는 것은 의미가 없습니다. 지표나 특정 시간에 실행되는 스크립트에서 사운드 시스템을 사용하는 것이 유용할 수 있습니다. 이는 분석에 매우 도움이 될 것입니다. 따라서 MetaTrader 5 플랫폼은 분석 측면에서 진정한 괴물이 될 수 있으며 포지션 진입 또는 청산 등 매우 구체적인 순간에 시장을 더 잘 분석하기 위한 대량의 계산을 할 수 있습니다. 이 모든 작업이 최소한의 노력으로 수행될 수 있습니다.
이렇게 말할 수도 있습니다: "하지만 저는 모든 사운드를 MQH 파일(헤더 파일)에 추가하고 실행 파일에 임베드하여 필요한 동작을 얻을 수 있습니다." 예, 가능합니다. 하지만 다음 시나리오를 생각해 보세요: 시간이 지남에 따라 이 MQH 파일은 커지고 이에 따라 일부 오래된 프로그램은 이 헤더 파일(MQH)과 호환되지 않게 될 수 있습니다. 이러한 오래된 파일을 다시 컴파일해야 하는 경우 문제가 발생할 수 있습니다. 그러나 만약 프로세스 간 통신 프로토콜이 있는 모듈형 시스템을 만들면 이전 프로그램과의 호환성을 유지하면서 플랫폼의 기능을 확장할 수 있습니다.
이러한 변경의 이유는 가능한 모든 경로를 생성하고 사용할 수 있는 방법을 보여주기 위해서입니다. 저는 가능한 한 원래의 작동을 그대로 유지하면서 EA에서 일부 기능을 제거하여 이를 보여주고 있습니다.
이전 글에서 저는 차트 트레이드를 다시 만들어 EA에 통합했을 때와 동일하게 작동하도록 하는 방법을 설명했습니다. 그러나 EA에서 이를 제거한 후에 동일한 모드에서 계속 작동할 수 있는 방법을 만들어야 했습니다. 제가 보여드린 방법은 여러 가지 방법 중 하나이며 최선은 아니지만 효과가 있습니다. 모든 솔루션에는 솔루션이 어떻게 작동하는지에 대한 적절한 이해가 필요합니다. 때로는 하나의 아이디어에만 국한하는 것이 문제 상황을 해결하는 데 도움이 되지 않는 경우도 있습니다. 많은 사람들이 무언가를 할 수 없다고 생각하거나 시스템이 제한적이라고 말하지만 실제로는 솔루션을 계획하고 구현하는 책임자의 지식이 부족한 것이 한계가 되는 것입니다.
우리는 데이터를 저장하는 구조를 사용하지 않고 주문 시스템을 구현할 때 이러한 문제를 경험했습니다. 많은 사람들이 불가능한 일이라고 그런 일을 할 수 있는 방법이 없다고 생각했습니다. 하지만 저는 그것이 가능하다는 것을 보여주었습니다. 중요한 것은 자신이 무엇을 하고 있는지를 알고 이해하는 것입니다. 첫 번째 단계는 각각의 솔루션의 유형의 한계를 파악하는 것입니다.
따라서 시스템이 더 성장함에 따라 기능을 확장할 것이라는 점을 염두에 두고 사운드 시스템을 가능한 한 모듈식으로 만드는 방법을 알아봅시다.
우선 우리는 기능을 확장해야 하는 경우를 제외하고는 C_Sound 클래스를 건드리지 않을 것입니다. 따라서 이 수업에는 큰 변화가 없을 것입니다. 사실 이 단계에서는 이 클래스는 변경되지 않지만 시스템에 약간의 추가를 해야 합니다. 그 중 첫 번째는 아래와 같은 헤더 파일입니다:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalVariableAlert "Sound Alert" //+------------------------------------------------------------------+
여러분은 EA에서 이 파일을 사용할 것이라고 생각할 지도 모르겠습니다. 그러나 아니요... 적어도 아직은 EA에서 이 파일을 사용하지는 않을 것입니다. 나중에 보게 될 다른 파일을 사용할 것입니다.
그런 다음 우리는 사운드 서비스가 될 파일을 만들 수 있습니다. 아래 코드에 표시되어 있습니다:
#property service #property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Auxiliar\C_Sounds.mqh> #include <NanoEA-SIMD\Interprocess\Sound.mqh> //+------------------------------------------------------------------+ void OnStart() { union u00 { double value; struct s00 { uint i0, i1; }info; }u_local; C_Sounds Sound; while (!IsStopped()) { Sleep(500); if (GlobalVariableGet(def_GlobalVariableAlert, u_local.value)) { GlobalVariableDel(def_GlobalVariableAlert); if (u_local.info.i1 == 0) Sound.PlayAlert((C_Sounds::eTypeSound)u_local.info.i0); else Sound.PlayAlert(u_local.info.i1); } } } //+------------------------------------------------------------------+
이 서비스는 MetaTrader 5 글로벌 변수를 감시합니다. 헤더 파일에 이름이 선언된 변수가 스크립트, EA 또는 인디케이터에서 실행되는 즉시 지정된 사운드가 재생됩니다.
여러분은 재생할 파일의 색인을 지정하기만 하면 됩니다. 위의 구조에 따라 총 4,294,967,295개의 다양한 사운드를 재생할 수 있으며 이는 외부 파일에만 해당되는 수입니다. 여러분은 동일한 수의 내부 사운드를 사용할 수 있으므로 많은 작업을 수행할 수 있습니다.
시스템은 재생할 사운드 유형을 알기 위해 u_local.info.i1 변수의 값을 확인합니다: 값이 0이면 재생할 사운드가 서비스 파일에 포함되고 사운드의 인덱스는 u_local.info.i0 변수로 표시되지만 이 값은 C_Sound 클래스 내부의 열거자를 나타냅니다.
이제 우리는 서비스를 컴파일하고 실행할 수 있습니다. 위의 조건이 충족되면 서비스는 전역 변수를 캡처하는 때를 기억하면서 작업을 수행하며 다른 시간에 사용할 수 있도록 제거된다는 점을 기억합니다.
더 나아가기 전에 잠시 생각해 봅시다. EA와만 통신하는 차트 트레이드 인디케이터와 달리 사운드 시스템은 MetaTrader 5 플랫폼의 모든 유형의 프로그램과 통신할 수 있습니다. 원하는 사운드를 재생하려면 변수 값을 설정해야 하며 이 값은 항상 두 배가 됩니다.
여러분은 이러한 작도이 쉽다고 생각할 수도 있지만 직접 해보시면 그렇지 않다는 것을 알게 될 것입니다. 여러분은 매번 올바른 이름으로 전역 변수를 계속 생성해야 합니다. 따라서 이전에 저장한 사운드를 재생하려면 매번 많은 작업을 수행해야 합니다.
하지만 이 모든 번거로움을 피할 수 있는 실용적인 솔루션이 있습니다. 이 솔루션은 매우 훌륭하기 때문에 우리는 초기 단계에서는 가장 기본적인 수준에서 사용할 것입니다. 어떻게 수행되는지 알아보기 위해 다음 주제로 넘어가겠습니다.
사운드 서비스에 액세스하기 위한 라이브러리 만들기
라이브러리를 만드는 이유는 어떤 식으로 든 우리의 삶을 더 편하게 하기 위해서 입니다. 라이브러리는 우리의 삶을 더 편하게 만들어 줄 것입니다. 이전 주제에서 저는 프로그램이 사운드 서비스에 액세스할 때 서비스에 대한 액세스 권한을 부여하는 전역 변수의 이름을 알 필요가 없다고 언급했습니다. 이상하게 들리 실지 모르지만 프로세스 간에 정보를 전달하는 가장 좋은 방법은 시스템에 레이어를 추가하는 것입니다. 이 레이어가 라이브러리입니다.
이러한 라이브러리는 프로세스 간 데이터 모델링의 복잡성을 '숨겨주므로' 여러분은 더 이상 모델링이 어떤 형태를 취해야 할지 걱정할 필요가 없습니다. 여러분은 통화 자체와 예상되는 결과에만 신경을 쓰면 됩니다.
라이브러리를 만들 때 우려되는 점은 두 가지 뿐입니다:
- 내보낼 함수를 명확하게 선언합니다.
- 라이브러리 사용자가 무슨 일이 일어나고 있는지 알 필요가 없도록 내부 모델링의 복잡성을 최대한 숨기세요. 사용자는 들어오는 데이터와 나오는 결과만 볼 수 있어야 합니다.
따라서 라이브러리 내의 모든 프로시저나 함수는 사용자 관점에서 매우 단순한 동작을 갖도록 설계됩니다. 그러나 내부적으로는 최종 결과에 다다를 때까지 매우 복잡한 수준의 작업이 있을 수 있습니다. 하지만 라이브러리를 사용할 프로그래머는 라이브러리 내부에서 어떤 일이 일어나는지 알 필요가 없습니다. 결과가 올바르게 제공되는지 확인하는 것이 중요합니다.
이제 사운드 서비스에 사용되는 데이터 모델링을 숨길 수 있는 라이브러리에 대해 살펴보겠습니다. 모든 프로그램은 두 가지를 보고해야 합니다: 첫 번째는 소리가 내부 소리인지 외부 소리인지, 두 번째는 소리의 인덱스입니다. 복잡한가요? 라이브러리 내부에서 이러한 호출 코드를 살펴보겠습니다:
void Sound_WAV(uint index) export { Sound(0, index); } void Sound_Alert(uint index) export { Sound(index, 0); }
이 두 함수는 데이터 모델링의 복잡성을 숨깁니다. 컴파일러에 이러한 함수에 대한 심볼릭 링크를 생성하도록 지시하는 키워드 export를 사용하고 있다는 점에 유의하세요. 이들은 값을 반환하지 않기 때문에 실제로는 프로시저입니다. 이렇게 하면 마치 이들 파일들이 DLL인 것처럼 파일 외부에서 볼 수 있습니다.
그러나 코드를 살펴보면 다음과 같은 함수를 찾을 수 없습니다. Sound. 어디에 있는 걸까요? 라이브러리 자체에는 있지만 라이브러리 외부에는 표시되지 않습니다. 아래에서 라이브러리 코드 전체를 확인하세요:
//+------------------------------------------------------------------+ #property library #property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Interprocess\Sound.mqh> //+------------------------------------------------------------------+ void Sound_WAV(uint index) export { Sound(0, index); } //+------------------------------------------------------------------+ void Sound_Alert(uint index) export { Sound(index, 0); } //+------------------------------------------------------------------+ void Sound(uint value00, uint value01) { union u00 { double value; struct s00 { uint i0, i1; }info; }u_local; u_local.info.i0 = value00; u_local.info.i1 = value01; GlobalVariableTemp(def_GlobalVariableAlert); GlobalVariableSet(def_GlobalVariableAlert, u_local.value); } //+------------------------------------------------------------------+
사운드 프로시저에는 스크립트, 지표 또는 EA가 요청하는 작업을 서비스가 실행할 수 있도록 적절한 값을 조합하는 데 필요한 모든 복잡성이 포함된다는 점에 유의하세요. 하지만 우리는 이 코드를 서비스에 액세스하는 프로그램 내부에 배치하는 대신 단순화된 호출만을 사용하므로 프로그램 디버깅이 더 편하고 덜 피곤합니다.
작동 방식을 이해하기 위해 예제 스크립트를 살펴보겠습니다:
#property copyright "Daniel Jose" #property script_show_inputs #import "Service_Sound.ex5" void Sound_WAV(uint); void Sound_Alert(uint); #import //+------------------------------------------------------------------+ input uint value00 = 1; //Internal sound service... input uint value01 = 10016; //Sound in WAV file... //+------------------------------------------------------------------+ void OnStart() { Sound_WAV(value01); Sound_Alert(value00); } //+------------------------------------------------------------------+
위의 코드를 보세요. 어떤 유형의 통신이 구현되는지, 사운드 이벤트가 언제 어디서 발생할지 알 필요가 없습니다. 사운드 이벤트는 플랫폼 내, 운영 체제 내 또는 원격으로 어디서나 발생할 수 있습니다. 그리고 이는 중요하지 않습니다. 우리가 알려야 할 것은 소리가 시스템 내부인지 외부인지와 그 색인뿐입니다.
이제 계속하기 전에 한 가지 실험을 해보겠습니다. 스왑 함수. 이 경우 Sound_WAV를 실행한 다음 Sound_Alert를 실행합니다. 실행하고 결과를 확인하세요. 그런 다음 순서를 변경하여 Sound_Alert를 실행한 다음 Sound_WAV를 실행하고 결과를 확인합니다. 이해가 안 되는 분들을 위해 설명하자면 첫 번째 상황에서는 OnStart 이벤트 내부의 코드가 다음과 같이 보일 것입니다:
void OnStart() { Sound_WAV(value01); Sound_Alert(value00); }
두 번째 경우도 이와 같습니다:
void OnStart() { Sound_Alert(value00); Sound_WAV(value01); }
어리석어 보일 수도 있지만 이 실험은 몇 가지를 이해하기 위해 필요합니다. 이 부분을 무시하지 마세요. 결과를 보는 것이 흥미로울 것입니다.
이제 사운드를 재생하기 위해 프로그램에 추가해야 할 사항을 확인했으므로 다음의 코드를 추가하기만 하면 됩니다:
#import "Service_Sound.ex5" void Sound_WAV(uint); void Sound_Alert(uint); #import
사운드를 재생해야 할 때마다 어떻게 재생할지 걱정할 필요 없이 올바른 값으로 올바른 함수를 사용하면 됩니다. 시스템이 모든 것이 완벽하게 작동하도록 할 것입니다. EA에서는 코드가 다음과 같이 표시됩니다:
// ... #import "Service_Sound.ex5" void Sound_WAV(uint); void Sound_Alert(uint); #import //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh> #include <NanoEA-SIMD\Interprocess\Sound.mqh> // ...
의문이 들기 시작합니다: 강조 표시된 코드는 어떤 기능을 하나요? 라이브러리만 이용하면 안 되나요? 예, 하지만 이전처럼 사운드나 경고의 수가 매우 적은 경우가 아니라면 여러분은 열거형을 사용하여 숫자 코드를 식별할 수 있으며, 코드만 보고는 각 코드가 무엇을 나타내는지 파악하기가 매우 어려울 수 있습니다. 이러한 이유로 헤더 파일 Sound.mqh에 아래 코드에 강조 표시된 부분이 추가되었습니다:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalVariableAlert "Sound Alert" //+------------------------------------------------------------------+ enum eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END}; //+------------------------------------------------------------------+
따라서 우리는 다음과 같은 코드로 완성할 수 있습니다:
int OnInit() { if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Sound_Alert(TRADE_ALLOWED); return INIT_FAILED; } // ... The rest of the function
열거형 대신 인덱스를 사용하는 코드보다 훨씬 더 대표적인 코드입니다:
int OnInit() { if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Sound_Alert(0); return INIT_FAILED; } // ... Rest of the code
어느 쪽이 더 이해하기 쉽나요?
이 모든 작업이 끝나면 아래 그림과 같이 플랫폼의 정보의 흐름이 완성됩니다:
보시다시피 누가 신호를 제공하든 우리는 항상 동일한 목적지를 갖게 됩니다.
결론
별것 아닌 것 같지만 이 글에서 소개한 내용은 코드, 프로그램 및 정보의 유용성을 높이는 데 큰 도움이 될 것입니다. 프로그래밍을 하면서 생산성이 높아짐과 동시에 다양한 시나리오에서 재사용과 테스트가 반복되면서 코드의 보안과 안정성이 향상됩니다.
여기서 우리는 이전 기사에서 본 것과는 다른 또 다른 경로를 확인했지만 그럼에도 불구하고 이 방법을 개선하여 새로운 많은 가능성을 얻을 수 있습니다. 다음에는 다른 시리즈에서 여기 소개된 방법보다 훨씬 높은 수준의 보안, 사용성, 안정성을 갖춘 훨씬 더 모듈화 된 MetaTrader 5에서 프로그램과 프로젝트를 만드는 방법에 대해서 살펴볼 것입니다.
그러나 가장 중요한 것은 어떤 이유로든 한 솔루션이 다른 솔루션보다 나은 경우가 있기 때문에 다양한 솔루션을 설계하고 사용하는 방법을 이해하는 것입니다.
모든 코드는 첨부 파일에서 확인할 수 있습니다. 라이브러리를 사용하는 이러한 프로그래밍 방식에 익숙하지 않은 분들을 위해 EA를 개발하는 것과 관련하여 이 단계를 잘 공부하는 것이 좋습니다. 오늘 할 수 있는 일을 내일로 미루지 마세요. 내일은 예상한 것과 다를 수도 있기 때문입니다.
이제 이 문서로 이 EA의 개발 단계를 완료합니다. 곧 복잡성이 훨씬 더 높지만 훨씬 더 흥미로운 다른 유형의 상황에 초점을 맞춘 또 다른 유형의 자료를 소개하겠습니다. 모두 감사드립니다 나중에 다시 뵙겠습니다.
MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10678



