English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MetaTrader5와 MATLAB의 상호 작용

MetaTrader5와 MATLAB의 상호 작용

MetaTrader 5 | 5 7월 2021, 13:11
116 0
Andrey Emelyanov
Andrey Emelyanov

개요

제 첫 글 'MetaTrader4와 MATLAB 엔진 상호작용(가상 MATLAB 기계)가 MQL 커뮤니티에서 인기였죠. 어떤 독자(1Q2W3E4R5T)들은 프로젝트를 볼랜드에서 VS2008로 옮기기까지 했더군요. 하지만 이제는 시간이 흘러(슬프지만 어쩔 수 없죠) MetaTrader4 유저가 점점 줄고 있습니다. 대신 포인터와 동적 메모리 기능을 가진 MQL5로 된 MetaTrader5가 그 자리를 차지하게 되었죠. 

MetaTrader5의 이런 혁신 덕분에 이제는 MATLAB 엔진 가상 머신과 상호 작용을 하는 범용 라이브러리를 작성하고, MATLAB이 생성한 라이브러리에 직접 링크를 걸 수 있게 되었습니다. 본문에서는 위에 언급된 기능을 다루게 될 것입니다. 이 글은 직전 글과 논리적으로 연결되며 MetaTrader5와 MATLAB 사이의 상호 작용에 대해 보다 상세한 설명을 담고 있습니다.

독자들의 편의를 위해 본문을 이론, 레퍼런스, 실전의 세 파트로 나누었습니다. 이론 파트는 MQL5와 MATLAB에서 사용되는 데이터형과 데이터 변형에 대해 이야기합니다. 레퍼런스 파트에서는 DLL 생성에 필요한 언어 구조와 함수 구문을 배우게 됩니다. 그리고 실전 파트에서는 상호 작용의 '위험'을 분석해 보겠습니다.

숙련된 독자들은 이론과 레퍼런스를 생략하고 바로 실전 파트를 시작해도 좋습니다. 나머지 독자들에게는 반드시 이론과 레퍼런스 파트를 모두 읽고 실전 파트로 넘어가기를 권장합니다. '참고 문헌'에 포함된 책들을 읽어 보는 것도 도움이 될 겁니다.

1. 이론

1.1 MATLAB과 MQL5의 데이터형

1.1.1 원시 자료형

시작해 봅시다.

우선 MQL5와 MQTLAB에 대해 익숙해질 필요가 있겠네요. 변수형 비교를 통해 이 둘이 거의 동일한 형식을 취한다는 것을 알 수 있습니다.

MQL5
크기(바이트)
최솟값
최대값
 MATLAB
char
1
-128
127
int8/char 배열
uchar
1
0
255
int8/char 배열
bool
1
0(false)
1(true)
logical 배열
short
2
-32768
32767
int16 배열
ushort
2
0
65535
int16 배열
int
4
-2147483648
2147483647
int32 배열
uint
4
0
4294967295
int32 배열
long 8
-9223372036854775808
9223372036854775807 int64 배열
ulong 8
0
18446744073709551615
int64 배열
float 4
1.175494351e-38
3.402823466e+38
single 배열
double
8
2.225073858507201e-308
1.7976931348623158e+308
double 배열

표 1. MATLAB과 MQL5의 데이터형

한 가지 큰 차이가 있는데요. 바로 MQL5에서는 단순 변수와 복합 변수가 모두 사용 가능하지만 MATLAB에서는 모든 변수가 다차원(복합 변수)이라는 것입니다(예: 행열). 이러한 차이점을 꼭 기억하세요!

1.1.2 복합 데이터 유형

MQL5에는 배열, 문자열, 구조 및 클래스의 4가지 복합 데이터 유형이 존재합니다. 복합 데이터형은 단순 변수 세트가 특정한 길이의 메모리 블록에 더해진 형식으로 이루어져 있습니다. 이 데이터를 다룰 때는 메모리 블록의 바이트 크기 또는 엘리먼트의 개수(클래스 제외) 중 하나를 반드시 알고 있어야 합니다. 우리는 배열과 문자열만 살펴볼 겁니다. 클래스와 MQL 구조를 MATLAB으로 전송하는 건 의미가 없으니까요.

어떤 형식이든 배열을 전달할 때에는 ArraySize() 함수를 이용해 형(차원)과 엘리먼트 개수를 구해야 합니다. MetaTrader5에서는 인덱싱에 특히 주의를 기울여야 하는데요. 보통 반대로 되어있기 때문입니다(예: 첫 번째 엘리먼트가 다음 엘리먼트보다 더 최근의 데이터를 포함). ArrayIsSeries() 함수를 이용해 확인하세요. MATLAB의 인덱싱의 경우 첫 번째 엘리먼트가 다음 엘리먼트보다 더 오래된 데이터를 가지고 있습니다. 따라서 플래그 변수 AS_SERIES가 참인 경우, MATLAB으로 전달하기에 앞서 모든 배열을 '뒤집어야' 합니다. 위를 바탕으로 다음의 사항에 합의할 수 있습니다.

  • 문자형과 2차원 배열을 제외한 모든 배열을 MQL5 프로그램으로 '보이지 않게' '뒤집을 것'.
  • 문자형과 2차원 배열을 제외한 MATLAB의 모든 배열을 '보이지 않게' 뒤집고 플래그 변수 AS_SERIES에 참값을 부여할 것.
  • 문자형과 2차원 배열을 제외한 '뒤집힌' 인덱세이션을 따르는 모든 MQL5 프로그램 배열에서 플래그 변수 AS_SERIES는 참값을 가져야 함.  

배열을 다룰 때에는 또 다른 제한 사항이 있습니다. 다차원 배열, 혹은 행렬을 다룰 경우, 그리고 특히 MATLAB의 경우, 2차원 배열에만 제한이 적용됩니다. 따라서 플래그 변수 AS_SERIES는 참값을 가질 수 없으므로 해당 배열 또한 '뒤집어'지지 않습니다.

MQL5 문자열은 문자형 엘리먼트가 아님에 주의하세요. 문자열 전달 시 약간의 문제가 발생하게 됩니다 MQL5 스트링은 유니코드를 사용하지만 MATLAB은 ANSI를 쓰기 때문이죠. 따라서 문자열 전달에 앞서 StringToCharArray() 함수를 이용해 ANSI 형식을 사용하는 배열로 전환해야 합니다. 반대의 경우도 마찬가지고요. MATLAB에서 문자형 배열을 가져올 때는 CharArrayToString() 함수(표 2 참조)를 이용해 변환하세요. 혼동을 피하기 위해 MQL5 프로그램의 모든 문자열을 유니코드를 이용해 저장하고 문자형 배열을 만들지 마세요.

1.2 MQL5와 MATLAB의 데이터형 비교

함수의 개수를 줄이고 라이브러리 알고리즘을 간소화하기 위해 자동 변환을 이용해 자료형의 개수를 줄일 겁니다. 데이터의 무결성에는 아무런 영향이 가지 않습니다. 다음의 표는 MQL5에서 MATLAB으로의 데이터형 변환 법칙을 설명합니다.

 MQL5 
MATLAB
char 
uchar
char 배열
bool
logical 배열
short
ushort
int
uint
int32 배열
long
ulong
int64* 배열
float
double
double 배열
string
char 배열(StringToCharArray() <=> CharArrayToString() 함수 사용)

* 변환 과정에서 정확도가 떨어지게 됩니다. 이 글에서는 사용하지 않을 것이지만 프로그램 작성 시 얼마든지 사용해도 좋습니다.

표 2. MQL5와 MATLAB의 데이터형 비교

이제 MQL5와 MATLAB의 데이터형에 대해 배웠습니다. 데이터 전달 시 어떤 '위험'이 발생할 수 있는지도 알고 '위험'을 적절히 피해갈 줄도 알게 되었죠. 아직 MATLAB 엔진 API에 대해 배우고 MATLAB 컴파일러4에 익숙해질 일이 남았습니다.

2. MATLAB 엔진 API 레퍼런스, MATLAB 컴파일러4 레퍼런스 및 C++ 인풋/아웃풋 라이브러리 레퍼런스

이번에는 MATLAB 엔진 API에서 가장 중요한 함수들과 MATLAB 컴파일러4의 기능, 그리고 C++ 표준 인풋/아웃풋 라이브러리의 몇 가지 유용한 함수에 대해 알아보겠습니다. 시작하죠.

2.1 MATLAB 엔진 API 및 MCR 함수

MATLAB 엔진은 다른 프로그램들이 MATLAB 데스크톱을 사용할 수 있도록 해주는 외부 인터페이스입니다. 아무런 제한 없이 사용 가능한 모든 MATLAB 패키지를 제공하죠.

따로 설명이 되어 있지는 않지만 프로그래머의 입장에서는 그저 PHP나 MySQL 등과 같은 가상 머신입니다. MetaTrader4, MetaTrader5, 그리고 MATLAB 사이의 데이터 교환을 가능하게 해주는 간단하면서 상대적으로 빠른 방법이죠.  

개발자들이 추천하는 외부 프로그램 연결 방식이기도 합니다. 인터페이스는 여섯 가지 함수로 이루어집니다.

Engine *pEng = engOpen(NULL)—MATLAB 데스크톱 호출, NULL값 변수, 데스크톱 디스크립터로 포인터 반환

int exitCode = engClose(Engine *pEng)—데스크톱 종료, MATLAB 데스크톱의 남은 유저 수를 반환

  • Engine *pEng—데스크톱 디스크립터 포인터  

mxArray *mxVector = mxCreateDoubleMatrix(int m, int n, int ComplexFlag)—MATLAB 데스크톱 변수(행렬) 생성, 변수(행렬)로 포인터 반환

  • mxArray *mxVector—행렬 변수 포인터  
  • int m—행의 개수  
  • int n—열의 개수  
  • ComplexFlag—MetaTrader4&5의 mxREAL에 사용되는 복소수
void = mxDestroyArray(mxArray *mxVector)—메모리 확보를 위한 MATLAB 행렬 제거
  • mxArray *mxVector—행렬 변수 포인터  
int = engPutVariable(Engine *pEng, char *Name, mxArray *mxVector)—데스크톱으로 변수 전달 mxArray형 변수를 생성하고 나면 반드시 MATLAB으로 전달해야 합니다.
  • Engine *pEng—데스크톱 디스크립터 포인터  
  • char *Name—MATLAB 데스크톱 문자형 변수 이름  
  • mxArray *mxVector—행렬 변수 포인터  
mxArray *mxVector = engGetVariable(Engine *pEng, char *Name)—데스크톱에서 변수 전달 다음과 같은 mxArray형 변수만 허용됩니다.
  • mxArray *mxVector—행렬 변수 포인터  
  • Engine *pEng—데스크톱 디스크립터 포인터  
  • char *Name—MATLAB 데스크톱 문자형 변수 이름  
double *p = mxGetPr(mxArray *mxVector)—값 배열로 포인터 전달, memcpy() 함수와 함께 데이터 복사(2.3 C++ 표준 인풋/아웃풋 라이브러리 참조)
  • double *p—더블형 배열 포인터  
  • mxArray *mxVector—행렬 변수 포인터  
int = engEvalString(Engine *pEng, char *Command)—MATLAB 데스크톱으로 명령어 전송
  • Engine *pEng—데스크톱 디스크립터 포인터  
  • char *Command—MATLAB 문자형 문자열 명령어  

MATLAB 엔진 API는 더블형의 경우에만 mxArray 구조 생성을 허락한다는 걸 아마 눈치채셨을 겁니다. 라이브러리 알고리즘에는 영향을 미치겠지만 그렇다고 여러분의 능력을 제한하는 건 아니죠.
MCR(MCR 인스턴스)—MATLAB 패키지의 특수 라이브러리, MATLAB 환경에서 생성된 모든 독립 애플리케이션 및 공개 라이브러리 실행 전체 MATLAB 패키지가 있어도 <MATLAB>\Toolbox\compiler\deploy\win32에 위치한 MCRInstaller.exe를 실행해 MCR 라이브러리를 설치해야 한다는 걸 기억하세요. 다시 말해 MATLAB 환경에서 생성된 공개 라이브러리 함수를 호출하기에 앞서 MCR 초기화 함수를 호출해야 합니다.
 
bool = mclInitializeApplication(const char **option, int count)–MCR 시작이 성공적인 경우 'TRUE' 값, 실패한 경우 'FLASE' 값 반환

  • const char **option—mcc-r과 같은 옵션 문자열, 값은 보통 NULL  
  • int count—크기 옵션 문자열, 값은 보통 0

공개 라이브러리를 끝낼 때는 반드시 다음의 함수를 호출합니다. bool = mclTerminateApplication(void)—MCR 종료가 성공적인 경우 'TRUE' 값 반환

2.2 MATLAB 컴파일러4

MATLAB 컴파일러의 M 함수를 이용하면 다음을 생성할 수 있습니다.  

  • MATLAB이 설치되지 않아도 실행 가능한 독립 애플리케이션
  • 최종 사용자 시스템에 MATLAB이 없어도 사용 가능한 C/C++ 공유 라이브러리

컴파일러는 대부분의 MATLAB 패키지와 명령어를 지원하지만, 전부를 지원하지는 않습니다. 제한 목록은 MATLAB 웹사이트에서 확인 가능합니다. 이 방법을 사용하면 MetaTrader5와 MATLAB의 '소프트웨어 분리 번들' 만들 수 있지만 MATLAB 엔진과는 다르게 컴파일에 대한 이해도가 뛰어난 훈련된 프로그래머가 필요합니다.

MATLAB 컴파일러는 다음의 C/C++ 컴파일러 중 최소 한 가지를 요구합니다.

  • Lcc C(보통 MATLAB에 동반) C 컴파일러  
  • 볼랜드 C++ 버전 5.3, 5.4, 5.5, 5.6
  • 마이크로소프트 비주얼 스튜디오 C/C++ 버전 6.0, 7.0, 7.1

MATLAB 컴파일러4는 이전 버전들과 다르게 인터페이스 코드(래퍼) 생성만을 담당하며 M 함수를 이진 코드 또는 C/C++ 코드로 번역하지는 않습니다. 하지만 CTF 기술을 이용해 M 함수 지원에 필요한 여러 패키지가 통합된 특수 파일을 생성합니다. 또한, MATLAB 컴파일러는 해당 파일을 중복되지 않는 유니크한 1024비트 키로 암호화합니다.

이번에는 MATLAB 컴파일러4 작업 알고리즘을 배워 보겠습니다. 이걸 제대로 모르면 컴파일 과정에서 이상한 실수를 하게 되니까요.

  1. 의존성 분석—컴파일된 M 함수가 의존하는 모든 함수, MEX 파일 및 P 파일 결정  
  2. 아카이브 생성-암호화 및 압축된 CTF 파일 생성  
  3. 래퍼 객체 코드 생성-컴포넌트에 필요한 모든 소스 코드 생성
    • 명령 라인(NameFile_main.c) M 함수에 대한 C/C++ 인터페이스 코드
    • M 코드 실행에 필요한 모든 정보를 포함하는 컴포넌트 파일(NameFile_component.dat), CTF 형식으로 저장된 암호화 키 및 경로 포함  
  4. C/C++ 번역 해당 단계에서 C/C++ 소스 코드 파일을 객체 파일로 컴파일
  5. 링크 프로젝트 빌딩 최종 단계

이제 MATLAB 컴파일러 알고리즘을 익혔으니 컴파일러(mcc) 사용에 필요한 키를 익혀야 합니다.   


목적
    파일명
 <파일명> 파일을 아카이브에 추가, CTF 아카이브에 추가될 파일 선택
     l
 함수 라이브러리 생성 마크로
    N
 최소한의 디렉토리 세트를 제외한 모든 경로 삭제
    p <directory>
 절차에 따른 번역 경로 추가 N 키 필요
    R -nojvm
 MCR 옵션 취소(MATLAB 컴포넌트 런타임, MATLAB 도움말 참조)
    W
 함수 래퍼 생성 관리
    lib
 초기화 및 컴플리션 함수 생성
    main
 main() 함수의 POSIX 쉘 생성
    T
 아웃풋 단계 설정
    codegen
 독립 애플리케이션용 래퍼 코드 생성
    compile:exe
 codegen과 동일
    compile:lib
 공개 DLL용 래퍼 코드 생성
    link:exe
 compile:exe와 동일, 링크 추가
    link:lib
 compile:exe와 동일, 링크 추가

표 3. MATLAB mcc 컴파일러(버전 4)에서 사용되는 키

표 3은 자주 발생하는 문제를 해결하는 데에 도움이 되는 기본 키를 포함합니다. MATLAB에서 help mcc 또는 doc mcc 명령어를 이용해 더 많은 정보를 찾아 보세요.

MATLAB 링커에 대해 알아 보았으니 주요 키(mbuild)에 대해서도 알아 봅시다.

 키
목적
 -setup
 인터랙티브 모드에서 mbuild 호출에 디폴트 값으로 사용될 컴파일러 옵션 설정
 -g
 디버깅 정보로 프로그램 생성 파일 끝에 DEBUGFLAGS 첨부
 -O
 객체 코드 최적화

표 4. MATLAB mbuild 링커(버전 4) 키

표4에는 주요 키가 나와 있습니다. help mbuild 또는 doc mbuild 명령어를 이용해 더 많은 정보를 얻어 보세요.

2.3 C++ 표준 인풋/아웃풋 라이브러리

표준 인풋/아웃풋 라이브러리를 이용하면 정확한 데이터 복사가 가능합니다. 프로그램 디자인 과정에서 발생할 수 있는 '멍청한' 실수들을 줄여주죠(예: 많은 초보 프로그래머들이 메모리 블록 전체를 복사하지 않고 포인터만 복사합니다). 전체 인풋/아웃풋 라이브러리를 통틀어 우리가 관심을 가져야 할 건 단 한 가지 함수입니다.
void *pIn = memcpy(void *pIn, void *pOut, int nSizeByte)–nSizeByte 바이트 크기로 pOut에서 pln으로 변수 및 배열 복사

  • void *pIn—복사 대상 배열 포인터  
  • void *pOut—복사 중인 배열 포인터  
  • int nSizeByte—복사된 데이터의 크기가 pln 배열의 크기보다 클 경우 메모리 액세스 에러 발생  

3. 실습

이론을 살펴보았으니 이제 MetaTrader5와 MATLAB의 상호 작용을 직접 실현해 보겠습니다.

아마 이미 아시겠지만 두 가지 방법을 이용할 겁니다. MATLAB 엔진 가상 머신과 MATLAB 컴파일러가 생성한 라이브러리를 각각 이용하는 거죠. 우선 MATLAB 엔진을 통한 간단하고 빠른 다목적 상호 작용을 떠올려 봅니다.

해당 파트는 반드시 처음부터 끝까지 순서대로 읽어야 합니다. 상호 작용 메소드 간의 차이가 있긴 하지만 공통의 발상과 언어 구조를 가지고 있으므로 간단한 예제를 통해 차근차근 배우는 것이 좋습니다.

3.1 MetaTrader5와 MATLAB 엔진 상호 작용 범용 라이브러리 개발

이 인터랙션 메소드는 깔끔하거나 빠르지는 않지만 MATLAB 패키지 전체를 커버하며 가장 안정성이 높은 방법이기도 합니다. 물론 최종 모델 개발 속도 또한 중요한 부분이죠. 라이브러리 개발의 핵심 목표는 MetaTrader4, MetaTrader5 그리고 MATLAB 엔진의 상호 작용을 위한 범용 라이브러리 래퍼의 작성입니다. 그러고 나면 MetaTrader4와 MetaTrader5의 스크립트, 인디케이터, 그리고 액스퍼트 어드바이저 모두 MATLAB 가상 데스크톱을 다룰 수 있게 됩니다. 게다가 전체 연산 알고리즘을 문자열로 MQL 프로그램에 저장할 수 있기 때문에 여러분의 지적 재산권 보호에도 도움이 됩니다(더 많은 관련 정보는 'Protect Yourselves, Developers!'에서 찾아 볼 수 있습니다). <MetaTrader 5>\MQL5\Libraries 폴더에 별개의 M 함수 또는 P 함수 파일로 저장될 수도 있죠.  

상호 작용 적용 가능 분야

  • 복잡한 프로그램 작성 없이도 가능한 '연산 모델' 테스트(MATLAB 패키지의 P 함수를 이용해 MQL 프로그램으로 지적 재산권 보호 가능)  
  • MATLAB 전체 기능을 이용한 복잡한 연산 모델 작성
  • 스크립트, 인디케이터 및 액스퍼트 어드바이저 배포를 원하지 않는 경우

시작해 봅시다. 1.1 MATLAB과 MQL5의 데이터형, 1.2 MQL5와 MATLAB의 데이터형 비교, 2.1 MATLAB 엔진 API 및 MCR 함수2.3 C++ 표준 인풋/아웃풋 라이브러리를 미리 읽으셨길 바랍니다. 더이상 해당 섹션과 관련된 설명은 하지 않을 겁니다. 라이브러리 알고리즘을 나타내는 다음의 블록 다이어그램을 꼼꼼하게 읽어 보세요.  

그림 1. 라이브러리 알고리즘 블록 다이어그램

그림 1. 라이브러리 알고리즘 블록 다이어그램

그림 1에서 볼 수 있듯, 라이브러리는 세 개의 메인 블록으로 구성됩니다. 각각의 목적을 생각해 봅시다.

  • 전송 및 수신된 데이터 예비 준비 과정인 MQL5 블록  
    • 배열 반전
    • 형 변환
    • 문자열 인코딩 변환
  • C/C++블록
    • 배열을 mxArray 구조로 전환
    • MATLAB 엔진 명령어 전달
  • MATLAB 엔진 블록—연산 시스템  

이제 알고리즘을 살펴봅시다. MQL5 블록을 먼저 볼게요. 눈치가 빠른 독자라면 MATLAB과 MQL5의 데이터형 섹션에서 다룬 내용을 구현해 볼 것이라는 걸 이미 알아챘을 거예요. 아직 잘 모르시겠다면 다음 내용의 필요성도 거의 느끼지 못하실 겁니다.

mlInput <variable_type> 함수의 알고리즘은 거의 똑같습니다. MATLAB 가상 머신에 double 형 변수를 제공하는 mlInputDouble() 함수에 대해 알아 볼게요.

다음은 프로토타입입니다.

bool mlInputDouble(double &array[],int sizeArray, string NameArray)

  • arraydouble 형 변수 또는 배열에 대한 레퍼런스
  • sizeArray—배열 크기(바이트 단위가 아닌 엘리먼트의 수) 
  • NameArray—MATLAB 가상 머신 전용 변수 이름을 포함하는 문자열(MATLAB 요구 사항에 맞는 이름일 것)

알고리즘

  1. StringToCharArray() 함수를 이용하여 NameArray 문자열을 char 배열로 변환합니다.
  2. ArrayIsSeries() 함수를 이용해 인덱싱 타입을 확인합니다. 노멀 인덱싱의 경우—mlxInputDouble() 함수로 값을 전달하세요.
    나머지 인덱싱 타입의 경우:
    배열을 '뒤집어서' mlxInputDouble()함수
    로 변수 값을 전달하세요.
  3. 함수를 종료하고 mlxInputDouble() 함수에 반환 값을 전달하세요.

mlGet <variable_type> 함수의 알고리즘 또한 거의 동일합니다. MATLAB 가상 머신으로부터 더블형 변수를 반환하는 mlGetDouble() 함수에 대해 알아봅시다.

프로토타입

int mlGetDouble(double &array[],int sizeArray, string NameArray)

  • arraydouble 형 변수 또는 배열에 대한 레퍼런스
  • sizeArray—배열 크기(바이트 단위가 아닌 엘리먼트의 수) 
  • NameArray—MATLAB 가상 머신 전용 변수 이름을 포함하는 문자열

알고리즘

  1. StringToCharArray() 함수를 이용하여 NameArray 문자열을 char 배열로 변환합니다.   
  2. mlxGetSizeOfName() 함수로 배열의 크기를 찾습니다.
    • 크기 값이 0보다 큰 경우 ArrayResize() 함수를 이용해 수신자 배열에 필요한 크기를 할당하고 mlxGetDouble()를 이용해 데이터를 얻은 후 배열의 크기 값을 반환하세요.
    • 크기 값이 0인 경우 에러가 반환됩니다(예: NULL 값).  

이게 끝입니다! mlGetInt() 함수와 mlGetLogical() 함수는 double ->; int/bool 형에 대한 '섀도우' 변환을 수행합니다.. 이를 위해 해당 함수는 내부에 임시 메모리 버퍼를 생성합니다. 안타깝게도 MATLAB API에서는 double 형 데이터만 mxArray 구조를 생성할 수 있으므로 이는 강제로 실행된 것입니다. 그렇다고 해서 MATLAB이 double 형만 계산하는 건 아닙니다.

C/C++ 블록은 훨씬 쉽습니다. double 형을 mxArray 구조로 변환하는 데이터가 제공되거든요. mxCreateDoubleMatrix(), mxGetPr()memcpy() 함수를 이용합니다. 그 다음 engPutVariable() 함수를 이용해 MATLAB 가상 머신에 데이터를 전달하고, engGetVariable() 함수로 데이터를 추출합니다. 다시 말하지만, 이름 앞에 Int가 붙는 함수나 논리 함수에는 각별한 주의를 기울이세요. 블록 다이어그램에서 보았듯 해당 형식의 함수는 MATLAB과 직접적인 상호 작용은 하지 않지만 mlxInputDouble/mlxGetDouble 함수와 mlxInputChar() 함수는 여전히 사용합니다. 알고리즘은 간단합니다. mlxInputDouble/mlxGetDouble 함수를 호출하여 입력 및 출력 변수를 double(!) 형으로 설정하고 '섀도우' MATLAB 명령어를 전송해 mlxInputChar() 함수로 데이터형을 변환하면 됩니다.

MATLAB 엔진 블록은 더 쉽습니다. 연산 함수만 실행되거든요. 여러분의 명령과 M 함수, P 함수에 따라 움직입니다.  

이제 모든 세부 사항을 알아봤으니 직접 프로젝트를 작성해 볼 시간입니다.

우선 메인 라이브러리를 생성해야 하는데요. 우리의 경우 C/C++ 블록이 여기에 해당합니다. 아무 ANSI 에디터(노트패드, Bred 등)를 열어 DEF 확장자를 갖는 파일을 생성합니다. 파일명은 빈 칸이나 문장 부호 없이 알파벳으로 작성하는 것이 좋습니다. 이 파일은 함수의 영속성을 제공합니다. 파일이 없을 경우, C/C++ 컴파일러는 함수를 내보내기 위해 복잡한 파일명을 생성할 것입니다.

이 파일에는 다음이 포함됩니다: LIBRARY—컨트롤 워드, LibMlEngine—라이브러리명, EXPORTS—두 번째 컨트롤 워드 및 함수 이름 아마 아시겠지만 내보내기 함수의 이름에는 빈 칸이나 문장 부호가 올 수 없습니다. 다음은 MATLABEngine.zip 아카이브에서 가져온 DllUnit.def 텍스트입니다.  

LIBRARY LibMlEngine
EXPORTS
mlxClose
mlxInputChar
mlxInputDouble
mlxInputInt
mlxInputLogical
mlxGetDouble
mlxGetInt
mlxGetLogical
mlxGetSizeOfName
mlxOpen

프로젝트의 첫 번째 파일이 되겠습니다. 이제 윈도우 익스플로러를 열고 <MATLAB>\Extern\include 폴더로 갑니다. 프로젝트가 생성되고 있는 engine.h 파일(MATLAB 가상 머신 헤더 파일)을 복사하세요(이 단계를 거치지 않으면 컴파일 단계에서 경로를 직접 지정해야 합니다).

이제 C/C++ 블록을 만들 차례입니다. MATLABEngine.zip의 DllUnit.cpp 파일에 전체 소스 코드가 잘 설명되어 있으니 이 글에서는 언급하지 않겠습니다. __stdcall 컨벤션을 이용해 함수를 생성하는 것이 보다 선호됨을 기억하세요(예: 매개 변수가 스택에 전달되면 함수가 스택을 삭제). Win32/64 API의 경우 애초부터 그렇게 설정되어 있습니다.

어떻게 함수를 선언하는지 생각해 봅시다.

"C" __declspec(dllexport) <variable_type> __stdcall 외부 함수(<type> <name>)

  1. extern "C" __declspec(dllexport)—C++ 컴파일러에 함수가 외부 함수임을 알림  
  2. <variable_type>—반환 값의 타입: void, bool, int, double 형 또는 복합 타입(DLL과 호출 프로그램에 사용) 및 포인터
  3.  __stdcall—함수로 매개 변수 전달 및 수신 선언, Win32/64 API의 경우 기본 설정  
  4. Funcion—함수 이름 설정  
  5. <type> <name>— 인풋 변수 형 및 이름, 최대 변수 개수는 64

관련 내용은 'How to Exchange Data: A DLL for MQL5 in 10 Minutes'에 보다 자세히 설명되어 있습니다.

C/C++ 블록 빌딩: 표준 인풋/아웃풋 라이브러리를 포함시키고 다음의 파일을 프로젝트에 추가해야 합니다(컴파일러 내 프로젝트 메뉴->프로젝트 추가)

  1. DllUnit.def
  2. <MATLAB>\Extern\lib\<win32/64>\<compiler>\ 폴더에 위치 <MATLAB>—MATLAB 메인 폴더 <win32/64>—32-bit OS의 경우 win32 폴더 또는 64-bit OS의 경우 win64 폴더
    <compiler>—볼랜드 C/C++ 함수의 '볼랜드' 폴더 버전 5~6, 마이크로소프트 비주얼 C++ '마이크로소프트' 폴더  
    • libeng.lib
    • libmx.lib

"제 컴파일러 버전은 다른데요!" 혹은 "목록에 있는 컴파일러가 없는데요!" 같은 질문이 발생할 수 있겠네요. (목록에 있는 파일이 없는 경우는 거의 없습니다). 직접 공개 라이브러리를 만드는 방법도 살펴보죠. 비주얼 C++와 볼랜드 C++를 이용할 겁니다.

  1. FAR에서 <MATLAB>\Bin\<win32/64> 폴더를 여세요. <MATLAB>—MATLAB 메인 폴더 <win32/64>— 32-bit OS의 경우 win32 폴더 또는 64-bit OS의 경우 win64 폴더  
  2. Borland C++의 경우 다음을 입력: implib libeng.lib libeng.dll libmx.dll에도 동일하게 적용됩니다.
  3. Visual C++의 경우 다음을 입력:lib libeng.dll libmx.dll에도 동일하게 적용됩니다.
  4. 기타 컴파일러의 경우 라이브러리 매니저가 있을 겁니다. <compiler _folder>\bin\*lib*.exe를 찾으세요.

한 가지 경고할 것이 있는데요. 32-bit 컴파일러에 64-bit 라이브러리를 만들려고 하지는 마세요. 우선 컴파일러 도움말에 64-bit 관련 정보가 있는지 찾아 보세요. 관련 정보가 없으면 32-bit MATLAB DLL을 찾거나 아니면 다른 C/C++ 컴파일러를 선택하세요. 컴파일 후 라이브러리는 terminal_folder\MQL5\Libraries 폴더에 저장되어야 합니다.

이제 MQL 블록을 봅시다. MetaEditor를 실행하고 '새 작업'을 누른 후 다음 그림의 설명을 따르세요.  

그림 2. MQL5 마법사: 라이브러리 만들기

그림 2. MQL5 마법사: 라이브러리 만들기

그림 3. MQL5 마법사: 라이브러리 일반 속성

그림 3. MQL5 마법사: 라이브러리 일반 속성

MQL5 마법사가 템플릿을 생성했다면 이제 편집을 할 차례입니다.

1. 함수 내보내기 설명

//+------------------------------------------------------------------+
//| DECLARATION OF IMPORTED FUNCTIONS                                 |
//+------------------------------------------------------------------+
#import "LibMlEngine.dll"
void   mlxClose(void);                        //void – means: don't pass any parameters!
bool   mlxOpen(void);                         //void – means: don't pass and don't receive any parameters!
bool   mlxInputChar(char &CharArray[]);       //char& CharArray[] – means: pass a reference!
bool   mlxInputDouble(double &dArray[],
                      int sizeArray,
                      char &CharNameArray[]);
bool   mlxInputInt(double &dArray[],
                   int sizeArray,
                   char &CharNameArray[]);
bool   mlxInputLogical(double &dArray[],
                       int sizeArray,
                       char &CharNameArray[]);
int    mlxGetDouble(double &dArray[],
                    int sizeArray,
                    char &CharNameArray[]);
int    mlxGetInt(double &dArray[],
                 int sizeArray,
                 char &CharNameArray[]);
int    mlxGetLogical(double &dArray[],
                     int sizeArray,
                     char &CharNameArray[]);
int    mlxGetSizeOfName(char &CharNameArray[]);
#import    

MQL5에서는 '포인터'를 두 가지 방법으로 사용할 수 있습니다.

  • void NameArray[]: 배열에 상관 없이 데이터 읽기만 가능 하지만 이 레퍼런스를 이용해 '내용을 수정'하려고 하면 메모리 액세스 에러가 발생할 겁니다(최상의 경우 MetaTrader5가 SEH 프레임으로 에러를 처리하겠지만 우리는 SEH 프레임을 작성하지 않았으므로 에러 원인도 모르게 될 지도 모릅니다).
  • void& NameArray[]: 읽기 및 수정이 가능하나 배열 크기 유지 필수

만약 함수가 매개 변수를 받거나 전달하지 않는다면 항상 void 형을 설정하세요.

2. MATLABEngine.zip 파일에서 MatlabEngine.mq5 소스 코드를 찾아볼 수 있으니 MQL 블록의 모든 함수에 대해서 이야기하지는 않을 겁니다.

MQL5 외부 함수 선언 및 정의에 대해 알아볼게요.

bool mlInputChar(string array)export
{
//... body of function
}

위에서 확인 가능하듯, 함수의 선언과 정의가 합쳐진 상태입니다. 이 경우, mlInputChar() 함수를 외부 함수(내보내기)로 선언하면 불리언 타입의 값이 반환되고 배열 문자형이 매개 변수로 받아들여 집니다. 이제 컴파일합니다.

드디어 실제 환경에서 라이브러리 테스트를 진행할 차례입니다.

간단한 테스트 스크립트가 필요합니다(MATLABEngine.zip에서 TestMLEngine.mq5를 불러와도 됩니다).

간단하고 주석이 잘 달린 간단한 스크립트 코드입니다.

#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/ru"
#property version   "1.00"
#import "MatlabEngine.ex5"
bool mlOpen(void);
void mlClose(void);
bool mlInputChar(string array);
bool mlInputDouble(double &array[],
                   int sizeArray,
                   string NameArray);
bool mlInputInt(int &array[],
                int sizeArray,
                string NameArray);
int mlGetDouble(double &array[],
                string NameArray);
int mlGetInt(int &array[],
             string NameArray);
bool mlInputLogical(bool &array[],
                    int sizeArray,
                    string NameArray);
int mlGetLogical(bool &array[],
                 string NameArray);
int mlGetSizeOfName(string strName);
#import
void OnStart()
  {
// Dynamic buffers for MATLAB output
   double dTestOut[];
   int    nTestOut[];
   bool   bTestOut[];
// Variables for MATLAB input
   double dTestIn[] = {   1,     2,    3,     4};
   int    nTestIn[] = {   9,    10,   11,    12};
   bool   bTestIn[] = {true, false, true, false};
   int nSize=0;
// Variables names and command line
   string strComm="clc; clear all;"; // command line - clear screen and variables
   string strA     = "A";            // variable A
   string strB     = "B";            // variable B
   string strC     = "C";            // variable C
/*
   ** 1. RUNNING DLL
   */
   if(mlOpen()==true)
     {
      printf("MATLAB has been loaded");
     }
   else
     {
      printf("Matlab ERROR! Load error.");
      mlClose();
      return;
     }
/*
   ** 2. PASSING THE COMMAND LINE
   */
   if(mlInputChar(strComm)==true)
     {
      printf("Command line has been passed into MATLAB");
     }
   else printf("ERROR! Passing the command line error");
/*
   ** 3. PASSING VARIABLE OF THE DOUBLE TYPE
   */
   if(mlInputDouble(dTestIn,ArraySize(dTestIn),strA)==true)
     {
      printf("Variable of the double type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the double type");
/*
   ** 4. GETTING VARIABLE OF THE DOUBLE TYPE
   */
   if((nSize=mlGetDouble(dTestOut,strA))>0)
     {
      int ind=0;
      printf("Variable A of the double type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nSize; ind++)
        {
         printf("A = %g",dTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the double type double hasn't ben got");
/*
   ** 5. PASSING VARIABLE OF THE INT TYPE
   */
   if(mlInputInt(nTestIn,ArraySize(nTestIn),strB)==true)
     {
      printf("Variable of the int type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the int type");
/*
   ** 6. GETTING VARIABLE OF THE INT TYPE
   */
   if((nSize=mlGetInt(nTestOut,strB))>0)
     {
      int ind=0;
      printf("Variable B of the int type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nSize; ind++)
        {
         printf("B = %i",nTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the int type double hasn't ben got");
/*
   ** 7. PASSING VARIABLE OF THE BOOL TYPE
   */
   if(mlInputLogical(bTestIn,ArraySize(bTestIn),strC)==true)
     {
      printf("Variable of the bool type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the bool type");
/*
   ** 8. GETTING VARIABLE OF THE BOOL TYPE
   */
   if((nSize=mlGetLogical(bTestOut,strC))>0)
     {
      int ind=0;
      printf("Variable C of the bool type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nSize; ind++)
        {
         printf("C = %i",bTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the bool type double hasn't ben got");
/*
   ** 9. ENDING WORK
   */
   mlClose();
  }

스크립트에서 확인할 수 있듯 변수 값을 입력하면 변수 값을 얻게 되죠. 그러나 디자인 단계에서 버퍼 사이즈를 알아야 했던 MetaTrader4와는 달리 MetaTrader5에서는 그럴 필요가 없습니다. 동적 버퍼가 사용되니까요.

이제 MATLAB 가상 머신에 대해 이해했으니 MATLAB 환경에서 만들어진 DLL을 사용해도 되겠습니다.

3.2 MATLAB 컴파일러4에서 생성된 DLL 제작 및 사용 관련 기술 가이드라인

앞서 MATLAB 패키지로 범용 상호 작용 라이브러리를 만드는 방법을 배웠습니다. 하지만 여기에는 한 가지 단점이 있습니다. 바로 최종 사용자가 MATLAB 패키지를 가지고 있어야 한다는 점이죠. 이러한 한계 때문에 완성된 소프트웨어 배포에 어려움이 발생합니다. 바로 이런 이유로 MATLAB 패키지에 패키지와 상관 없는 독립 애플리케이션을 생성할 수 있는 컴파일러가 탑재되어 있는 거죠. 한번 살펴볼게요.

SMA 인디케이터를 예로 들겠습니다. 신경망 필터를 추가해 '백색 소음'을 조절합니다. 새로운 인디케이터를 NeoSMA로 저장하고, 필터는 GRNNFilter로 저장합니다.  

이제 MetaTrader5에서 호출 가능한 DLL 생성에 사용 가능한 M 함수 두 개가 생겼죠.

MetaTrader5의 DLL은 다음의 경로에 저장되어야 함을 기억하세요.

  • <terminal_dir>\MQL5\Libraries  
  • <terminal_dir>  
  • 현재 폴더
  • 시스템 폴더 <windows_dir>\SYSTEM32;  
  • <windows_dir>  
  • 환경 변수 PATH의 디렉토리

위의 디렉토리 중 DLL을 제작할 곳에 두 개의 M 함수를 추가하세요(NeoSMA.m과 GRNNFilter.m). 해당 디렉토리로 함수 파일을 이동하는 이유가 있습니다. 눈치 빠른 독자라면 이미 MATLAB 컴파일러가 컴파일 시 경로를 그대로 유지한다는 사실을 떠올리셨을 겁니다(2.2 MATLAB 컴파일러 4 참조).

  프로젝트 컴파일 시작에 앞서 컴파일러 속성을 정의해야죠. 다음을 따르세요.   

  1. MATLAB 명령어 라인에 다음을 입력: mbuild -setup
  2. Y 키를 눌러 시스템 내 C/C++ 호환 컴파일러 검색 결과 확인
  3. 표준 Lcc-win32 C 컴파일러 선택
  4. Y 키를 눌러 선택된 컴파일러 확인

그림 4. 프로젝트 컴파일링

그림 4. 프로젝트 컴파일링


이제 M 함수 컴파일 과정으로 넘어갑니다.

다음을 입력하세요.

mcc -N -W lib:NeoSMA -T link:lib  NeoSMA.m GRNNFilter.m

키 설명

-N                                     —불필요한 경로 생략
-W lib:NeoSMA                   — 컴파일러에게 라이브러리명이 NeoSMA임을 알림
-T link:lib                           —컴파일러에게 링킹 기능이 있는 공개 라이브러리 생성을 명령
NeoSMA.m and GRNNFilter.m  —M 함수 이름

이제 컴파일러가 어떤 걸 만들었는지 확인해 볼까요?

  • mccExcludedFiles.log  —컴파일러 액션을 포함하는 로그 파일
  • NeoSMA.c  —C 버전 라이브러리(C코드 래퍼 포함)  
  • NeoSMA.ctf  —CTF 파일(2.2 MATLAB 컴파일러4) 참조  
  • NeoSMA.h  — 헤더 파일(라이브러리, 함수 및 상수 선언 포함)  
  • NeoSMA.obj  —객체 파일(머신 및 의사 코드를 포함하는 소스 파일)  
  • NeoSMA.exports  —내보내기된 함수 이름  
  • NeoSMA.dll  —추가 링킹용 DLL  
  • NeoSMA.lib  —C/C++ 프로젝트용 DLL  
  • NeoSMA_mcc_component_data.c  —C 버전 컴포넌트(경로 포함, CTF 파일 등과 사용)  
  • NeoSMA_mcc_component_data.obj  —객체 버전 컴포넌트(머신 및 의사 코드를 포함하는 소스 파일)

이제 DLL의 내부 구조를 다루겠습니다. 기초 함수로만 구성됩니다.

  1. 모든 DLL의 메인 함수인 BOOL WINAPI DllMain()는 DLL 내부에서 발생하는 핸들을 처리합니다(마이크로소프트 제공 스펙). 프로세스의 주소 공간에 DLL 불러오기, 새로운 스트림 생성하기, 스트림 삭제 및 메모리에서 DLL 제거하기 등이 여기에 포함되죠.  
  2. DLL 초기화 및 초기화 해지용 서비스 함수: BOOL <NameLib>Initialize(void)/void <NameLib>Terminate(void) —라이브러리 함수 사용 전후로 매스웍스 환경 구축에 필요
  3. 내보내기된 M 함수–void mlf<NameMfile>(int <number_of_return_values>, mxArray **<return_values>, mxArray *<input_values>, ...)
    • <number_of_return_values>—반환된 변수 개수(don't confuse with array size, etc.).
    • mxArray **<return_values> —M 함수 결과 값이 반환될 mxArray 구조 주소
    • mxArray *<input_values> —M 함수 인풋 변수 mxArray 구조에 대한 포인터
     

보시다시피, 내보내기된 M 함수들은 mxArray 구조에 대한 주소와 포인터를 포함하며 MetaTrader5가 이해하지 못하는 데이터형이므로 직접 호출이 불가능합니다. 따라서 mxArray 구조에 대해서는 설명하지 않겠습니다. MATLAB 개발자들이 아직 어떻게 할지 정하지를 못했거든요. 대신 간단한 DLL 어댑터를 작성하여 사용합니다.

다음은 DLL 어댑터의 블록 다이어그램입니다.

그림 5. DLL 어댑터 블록 다이어그램

그림 5. DLL 어댑터 블록 다이어그램

MATLAB 엔진 DLL과 유사하므로 알고리즘은 살펴보지 않고 바로 코드에 대해 알아볼게요. 우선 C/C++ 컴파일러에서 작은 파일 두 개를 생성하세요.  

nSMA.cpp(DllMatlab.zip에 포함)  

#include <stdio.h>
#include <windows.h>
/* Include MCR header file and library header file */
#include "mclmcr.h"
#include "NEOSMA.h"
/*---------------------------------------------------------------------------
** DLL Global Functions (external)
*/
extern "C" __declspec(dllexport) bool __stdcall IsStartSMA(void);
extern "C" __declspec(dllexport) bool __stdcall nSMA(double *pY,  int  nSizeY,
                                                     double *pIn, int nSizeIn,
                                                     double   dN, double dAd);
/*---------------------------------------------------------------------------
** Global Variables
*/
mxArray *TempY;
mxArray *TempIn;
mxArray *TempN;
mxArray *TempAd;
bool bIsNeoStart;
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
    switch(reason)
    {
        case DLL_PROCESS_ATTACH:
         bIsNeoStart = false;
         TempY  = 0;   //Nullify pointers to buffers
         TempN  = 0;
         TempIn = 0;
         TempAd = 0;
         break;
        case DLL_PROCESS_DETACH:
         NEOSMATerminate();
         //Delete old data before exiting from Dll
         if(TempY  != NULL) mxDestroyArray(TempY);
         if(TempN  != NULL) mxDestroyArray(TempN);
         if(TempIn != NULL) mxDestroyArray(TempIn);
         if(TempAd != NULL) mxDestroyArray(TempAd);
         mclTerminateApplication();
    }
    return 1;
}
//---------------------------------------------------------------------------
bool __stdcall IsStartSMA(void)
{
 if(bIsNeoStart == false)
 {
  if(!mclInitializeApplication(NULL,0) )
  {
   MessageBoxA(NULL, (LPSTR)"Can't start MATLAB MCR!",
               (LPSTR) "MATLAB DLL: ERROR!", MB_OK|MB_ICONSTOP);
   return false;
  }else
   {
    bIsNeoStart = NEOSMAInitialize();
   };
 };
 return bIsNeoStart;
}
//---------------------------------------------------------------------------
bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd)
{
   /*
   ** Create buffers
   */
   if(TempN == NULL){ TempN = mxCreateDoubleMatrix(1, 1, mxREAL);}
   else
   {
     mxDestroyArray(TempN);
     TempN= mxCreateDoubleMatrix(1, 1, mxREAL);
   };
   if(TempIn == NULL){ TempIn = mxCreateDoubleMatrix(1, nSizeIn, mxREAL);}
   else
   {
     mxDestroyArray(TempIn);
     TempIn= mxCreateDoubleMatrix(1, nSizeIn, mxREAL);
   };
   if(TempAd == NULL){ TempAd = mxCreateDoubleMatrix(1, 1, mxREAL);}
   else
   {
     mxDestroyArray(TempAd);
     TempAd= mxCreateDoubleMatrix(1, 1, mxREAL);
   };
   /*
   ** Creating data for processing
   */
   memcpy((char *)mxGetPr(TempIn), (char *) pIn, (nSizeIn)*8);
   memcpy((char *)mxGetPr(TempN), (char *) &dN, 8);
   memcpy((char *)mxGetPr(TempAd), (char *) &dAd, 8);
   /*
   ** Send and receive a response from the m-function
   */
   if(mlfNeoSMA(1, (mxArray **)TempY, (mxArray *)TempIn, (mxArray *)TempN
      , (mxArray *)TempAd) == false) return false;
   /*
   ** Return calculated vector from the m-function and clear buffers
   */
   memcpy((char *) pY, (char *)mxGetPr(TempY), (nSizeY)*8);
   mxDestroyArray((mxArray *)TempY);  TempY  = 0;
   mxDestroyArray((mxArray *)TempN);  TempN  = 0;
   mxDestroyArray((mxArray *)TempIn); TempIn = 0;
   mxDestroyArray((mxArray *)TempAd); TempAd = 0;
   return true;
}

nSMA.def(DllMatlab.zip에 포함)

LIBRARY nnSMA
EXPORTS
IsStartSMA
nSMA


C/C++ 컴파일러에서 프로젝트를 생성하세요. 표준 인풋/아웃풋 라이브러리를 포함시키고 다음의 파일을 프로젝트에 추가하세요(컴파일러 내 프로젝트 메뉴->프로젝트 추가)

  1. nSMA.def
  2. <MATLAB>\Extern\lib\<win32/64>\<compiler>\ 폴더에 위치 <MATLAB>—MATLAB 메인 폴더 <win32/64>—32-bit OS의 경우 win32 폴더 또는 64-bit OS의 경우 win64 폴더
    <compiler>—볼랜드 C/C++ 함수의 '볼랜드' 폴더 버전 5~6, 마이크로소프트 비주얼 C++ '마이크로소프트' 폴더(버전 6용 파일도 있습니다)  
    • libmx.lib
    • mclmcr.lib
  3. NeoSMA.lib —직접 작성(3.1 MetaTrader5와 MATLAB 엔진 상호 작용 범용 라이브러리 개발 참조)  

마지막으로 짚고 넘어가고 싶은 점은 MATLAB이 설치되지 않은 환경으로 프로젝트를 옮길 때 필요한 파일에 대한 내용입니다.

타겟 머신의 경로 및 파일 리스트입니다.

  • MCRInstaller.exe                    아무 폴더(MCR 실행기)
  • extractCTF.exe                      아무 폴더(MCR 실행기)
  • MCRRegCOMComponent.exe  아무 폴더(MCR 실행기)
  • unzip.exe                              아무 폴더(MCR 실행기)
  • NeoSMA.dll                           <terminal_dir>\MQL5\Libraries
  • NeoSMA.ctf                           <terminal_dir>\MQL5\Libraries
  • nnSMA.dll                             <terminal_dir>\MQL5\Libraries

숙련된 프로그래머라면 이미 예상했겠지만, 셋업 파일을 사용하는 것이 좋습니다. 온라인에서 쉽게 찾아볼 수 있죠. 무료인 것도 많고요.

이제 MetaTrader5에서 해당 DLL을 시험해 볼 차례입니다. 간단한 스크립트를 작성합니다(DllMatlab.zip의 TestDllMatlab.mq5).

#property copyright "2010, MetaQuotes Software Corp."
#property link      "nav_soft@mail.ru"
#property version   "1.00"
#import "nnSMA.dll"
bool  IsStartSMA(void);
bool  nSMA(double &pY[],
           int nSizeY,
           double &pIn[],
           int nSizeIn,
           double dN,
           double dAd);
#import
datetime    Time[];    // dynamic array of time coordinates
double      Price[];   // dynamic array of price
double      dNeoSma[]; // dynamic array of price
void OnStart()
  {
   int ind=0;
// run Dll
   if(IsStartSMA()==true)
     {
      //--- create and fill arrays
      CopyTime(Symbol(),0,0,301,Time);   // time array + 1
      ArraySetAsSeries(Time,true);       // get the time chart
      CopyOpen(Symbol(),0,0,300,Price);  // price array
      ArraySetAsSeries(Price,true);      // get the open prices
      ArrayResize(dNeoSma,300,0);        // reserve space for function response
                                         // get data
      if(nSMA(dNeoSma,300,Price,300,1,2)==false) return;
      // specify array orientation
      ArraySetAsSeries(dNeoSma,true);
      // plot data on chart
      for(ind=0; ind<ArraySize(dNeoSma);ind++)
        {
         DrawPoint(IntegerToString(ind,5,'-'),Time[ind],dNeoSma[ind]);
        }
     }
  }
//+------------------------------------------------------------------+
void DrawPoint(string NamePoint,datetime x,double y)
  {  // 100% ready. Plot data on chart. Drawing using arrows.
// Main properties of chart object
   ObjectCreate(0,NamePoint,OBJ_ARROW,0,0,0);
   ObjectSetInteger(0, NamePoint, OBJPROP_TIME, x);        // time coordinate x
   ObjectSetDouble(0, NamePoint, OBJPROP_PRICE, y);        // price coordinate y
// Additional properties of chart object
   ObjectSetInteger(0, NamePoint, OBJPROP_WIDTH, 0);       // line width
   ObjectSetInteger(0, NamePoint, OBJPROP_ARROWCODE, 173); // arrow type
   ObjectSetInteger(0, NamePoint, OBJPROP_COLOR, Red);     // arrow color
  }
//+------------------------------------------------------------------+

결론

이제 MetaTrader5와 MATLAB의 인터랙티브 범용 라이브러리 생성 방법과 MATLAB 환경에서 구축된 DLL을 연결하는 방법을 배웠습니다. 물론 MetaTrader5와 MATLAB 상호 작용이 가능한 인터페이스는 더 있습니다만 이번 글에서는 설명하지 않겠습니다. 이번 글에서는 한 가지 주제를 자세하게 설명해 보았습니다. 어댑터가 필요 없는 가장 효과적인 상호 작용 방법을 선택했죠. .NET 등을 사용해서 다른 방법으로 실행할 수도 있긴 합니다. -WCF 서비스를 통해 MetaTrader5에서 .NET 애플리케이션으로 인용문 내보내기

어떤 메소드를 골라야 하는지 궁금하실 수도 있겠네요. 답은 간단합니다. 연산 모델 디자인 및 디버깅 시 속도는 상관 없기 때문에 둘 다 사용하셔도 됩니다. 하지만 '추가 제작 비용' 없이 MATLAB을 이용할 수는 있어야 하죠. MATLAB 엔진이 도움이 될 겁니다. 하지만 연산 모델을 사용하게 되면 속도가 중요해집니다. 여러 가격 차트에서 멀티태스킹이 가능해야 하죠. 따라서 MATLAB 환경에서 구축된 DLL이 필요하게 됩니다.

하지만 반드시 이와 같은 방법을 따라야 하는 것은 아닙니다. 각자 다른 답을 가지고 있을 것이고, '프로그래밍 비용'과 프로젝트 규모(인디케이터 및 매매 시스템 사용자 수)를 잘 비교해서 결정할 겁니다. 한 두 사람을 위해서 MATLAB 환경의 DLL을 구축할 필요는 전혀 없죠. 각각의 컴퓨터에 MATLAB을 설치하는 게 더 나은 방법이니까요.  

MATLAB에 대해 잘 알지 못하는 독자분들은 '왜 이런 게 다 필요하지?'라는 질문을 던지실 수도 있습니다. MQL5 는 이미 연산 함수를 가지고 있는데 말이예요! MATLAB을 이용하면 아주 쉽게 여러분만의 연산을 구현해 낼 수 있기 때문입니다. 다음은 일부 예시입니다.  

  • 인디케이터 및/또는 자동 매매 시스템의 동적 알고리즘  
  • 자동 매매 시스템의 동적 유전 알고리즘(동적 전략 테스터)
  • 인디케이터 및/또는 자동 매매 시스템의 동적 신경망 알고리즘  
  • 3차원 인디케이터
  • 비선형 관리 시스템 시뮬레이션

그러니까 다음을 꼭 기억하세요. 수학은 과학의 꽃이고 MATLAB 패키지가 바로 여러분의 과학 계산기라는 걸요.

참고문헌

  1. MATLAB 기본 도움말
  2. MQL5 기본 도움말
  3. 제프리 리히터(Jeffrey Richter) 마이크로소프트 윈도우용 프로그래밍 애플리케이션

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

파일 첨부됨 |
dllmatlab_en.zip (955.7 KB)
matlabengine_en.zip (670.57 KB)
뉴비들을 위한 복합 인디케이터 버퍼 만들기 뉴비들을 위한 복합 인디케이터 버퍼 만들기
복잡한 코드는 여러 개의 간단한 코드로 이루어집니다. 익숙한 코드들이라면 별로 복잡해 보이지 않죠. 이 글에서는 복수 개의 인디케이터 버퍼를 이요한 인디케이터 작성법을 알아보겠습니다. 아룬 인디케이터를 예시로 분석했으며, 두 가지 코드가 포함되어 있습니다.
그래픽 컨트롤 옵션이 있는 인디케이터 만들기 그래픽 컨트롤 옵션이 있는 인디케이터 만들기
시장 분위기가 무엇인지 안다면 MACD 인디케이터(이동 평균 수렴 확산 지수)도 아실 겁니다. 컴퓨터 분석이 가능해지면서 투자자들이 사용하기 시작한 가격 변동을 파악할 수 있는 아주 뛰어난 분석 도구죠. 이 글에서는 MACD 지표를 어떤 식으로 변형할 수 있는지 알아보고 그래픽 설정 변경이 가능한 하나의 인디케이터로 변형된 MACD 지표를 구현해 보겠습니다.
바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법 바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법
그래픽 디자인 샘플 프로그램을 생성해 보면 MQL5로는 어떻게 클래스를 고안하고 생성하는지 알 수 있습니다. 이 글은 MT5 애플리케이션을 이용하는 초보 프로그래머들을 위해 작성되었습니다. 객체 지향 프로그래밍 이론을 깊이 파고들지 않아도 클래스를 생성할 수 있도록 간단하고 쉬운 방법을 알려드리겠습니다.
MQL5 이벤트 핸들링: 빠르게 MA 피리어드 바꾸기 MQL5 이벤트 핸들링: 빠르게 MA 피리어드 바꾸기
피리어드가 13인 단일 MA 인디케이터가 차트에 적용되었다고 상상해 봅시다. 피리어드를 20으로 바꾸고 싶은데, 인디케이터 속성 대화 상자에서 13을 20으로 바꾸고 싶지는 않네요. 맨날 쓰는 방법이니까 너무 지루하잖아요. 특히 인디케이터 코드를 열어서 수정하고 싶지가 않습니다. 버튼 하나만 눌러서 해결하고 싶은데요. 키보드의 위쪽 화살표가 딱이겠네요. 이 글에서는 그 방법을 찾아볼게요.