MT4 카피 트레이딩 시스템 구축기: 로컬 통신 뚫기부터 422 에러 해결까지

소개

시도하고자 했던 것과 그 이유

MT4 터미널 간의 카피 트레이딩 시스템을 직접 구축하고자 했습니다. Leader EA가 매매 신호를 보내면 Python 서버가 이를 받아 Follower EA에게 전달하는 구조입니다. 상용 솔루션 대신 직접 구축하여 **커스터마이징(자금 관리 로직 등)**을 자유롭게 하고, 로컬 환경에서 빠르고 안전하게 테스트하고 싶었습니다.


진행 방법

사용 도구 및 활용법

  • Cursor (AI Code Editor) => Antigravity 변경: MQL4 및 Python 코드 작성, 에러 디버깅 가이드

  • Python (FastAPI): 중계 서버 구축 (Pydantic을 이용한 데이터 검증)

  • Ngrok: 로컬 서버(localhost)를 외부 HTTPS URL로 노출하여 MT4 접속 허용

  • MQL4: Leader/Follower EA 제작

  • 다양한 유형의 데이터를 보여주는 컴퓨터 화면의 스크린샷
Azure 콘솔의 스크린샷
항목 목록을 보여주는 컴퓨터 화면의 스크린샷
한국어 웹사이트 스크린샷

💡 Tip: 사용한 프롬프트 (예시)

문제가 발생했을 때 AI에게 구체적인 상황과 에러 코드를 전달하는 것이 중요했습니다.

"MT4 WebRequest 함수로 로컬 서버(127.0.0.1:8787)에 접속하려는데 Error 5200이 발생해. MT4 설정에서 URL을 추가했는데도 안 되는데, 로컬 네트워크 차단을 우회할 방법이 있을까?"

"서버로 데이터를 보냈는데 422 Unprocessable Content 에러가 떠. 서버 로그에는 'Extra data'라고 나오는데 MQL4에서 JSON을 만들 때 주의해야 할 점이 있어?"

📸 활용 이미지

1. 로컬 통신 실패 (Error 5200) MT4는 보안상 로컬 IP(127.0.0.1, 10.x.x.x) 접속을 차단하는 경우가 많습니다.

2. 422 에러 발생 (JSON 포맷 문제) 통신은 성공했으나, 데이터 형식이 맞지 않아 서버가 거부하는 상황입니다.

다양한 유형의 정보가 포함된 테이블의 스크린샷

💻 코드: MQL4 WebRequest 데이터 전송 핵심

MQL4에서 JSON을 보낼 때 **NULL 문자(\0)**가 포함되어 서버에서 파싱 에러가 발생하는 것을 방지하는 코드입니다.

// MQL4: JSON 페이로드 전송 시 NULL 문자 제거 및 배열 크기 조절

bool HttpPostJson(const string url, const string payload, string &response)

{

char data[];

// 문자열을 char 배열로 변환 (NULL 문자 포함됨)

int dataSize = StringToCharArray(payload, data, 0, WHOLE_ARRAY, CP_UTF8);

// 끝에 붙은 NULL 문자 제거 (중요!)

while(dataSize > 0 && data[dataSize-1] == 0)

dataSize--;

// 배열 크기를 실제 데이터 크기로 조절 (Error 5203 방지)

ArrayResize(data, dataSize);

// ... WebRequest 호출 ...

}


결과와 배운 점

배운 점과 나만의 꿀팁

  1. Ngrok으로 통신 가능해짐: MT4처럼 로컬 통신 제약이 심한 환경에서 Ngrok을 쓰면 별도 호스팅 없이도 로컬 서버와 통신 테스트가 가능합니다. 개발 속도가 훨씬 빨라집니다.

  2. MQL4의 StringToCharArray 함정: 이 함수는 문자열 끝에 항상 NULL 문자를 포함합니다. 이를 그대로 전송하면 Python(FastAPI) 등 엄격한 서버에서는 **"Extra data"**라며 JSON 파싱 에러(422)를 뱉습니다. 반드시 ArrayResize로 잘라내야 합니다.

서버 로그의 중요성: 422 에러가 났을 때 클라이언트(MT4)만 봐선 모릅니다. 서버에서 요청 바디(Body)를 찍어봐야 "아, 뒤에 이상한 문자가 붙었구나"를 알 수 있습니다.

🔍 Pydantic을 이용한 데이터 검증

Python 서버에서는 Pydantic 모델을 사용하여 들어오는 데이터의 무결성을 검증했습니다. 하지만 너무 엄격한 타입 검사는 오히려 독이 되기도 했습니다.

문제 상황: MT4가 보내는 날짜 형식(YYYY.MM.DD HH:MM:SS)과 Pydantic의 datetime 기본 파싱 형식이 미묘하게 달라 422 에러가 발생했습니다.

해결책: Pydantic 모델에서 datetime 대신

str로 타입을 완화하여 일단 데이터를 받은 후, 내부 로직에서 파싱하도록 변경했습니다.

# 변경 전 (422 에러 발생)

class PositionSnapshot(BaseModel):

open_time: datetime # 엄격한 검사

# 변경 후 (해결)

class PositionSnapshot(BaseModel):

open_time: str # 유연한 수용

📉 슬리피지(Slippage) 처리

카피 트레이딩에서 가장 중요한 것은 Leader와 Follower 간의 가격 차이(Slippage)를 제어하는 것입니다. Follower EA에 Slippage 파라미터를 추가하여, 허용 범위를 벗어난 가격에서는 체결되지 않도록 설정했습니다.

// Follower EA: 주문 전송 시 Slippage 적용

input int Slippage = 3; // 허용 슬리피지 (Pips)

void ExecuteOpen(...) {

// ...

int ticket = OrderSend(symbol, cmd, lots, price, Slippage, 0, 0, ...);

// ...

}

이렇게 하면 급격한 변동성으로 인해 Leader보다 훨씬 불리한 가격에 체결되는 것을 방지할 수 있습니다.

시행착오

  • Error 5200: 로컬 IP 차단 문제. -> Ngrok으로 해결.

  • Error 422: JSON 포맷 불일치 및 NULL 문자 포함 문제. -> Pydantic 모델 완화(datetime -> str)MQL4 배열 크기 수정으로 해결.

  • Error 5203: WebRequest 파라미터 오류. -> ArrayResize를 안 해서 배열 크기가 맞지 않아 발생했던 문제.

앞으로의 계획

  1. 전체 매매 사이클 검증: 통신은 성공했지만, 실제로 Leader가 진입/청산했을 때 Follower가 정확한 타이밍에 따라하는지 실전 테스트가 필요합니다.

  2. 부분 청산(Partial Close) 처리: 현재 로직은 전량 청산만 고려되어 있습니다. Leader가 부분 청산을 했을 때 Follower도 비례해서 부분 청산하는 로직을 추가해야 합니다.

  3. 네트워크 재연결 로직: Ngrok URL이 변경되거나 네트워크가 끊겼을 때 자동으로 복구하거나 사용자에게 알림을 보내는 기능을 강화해야 합니다.


도움 받은 글 (옵션)

  • (지피터스 19기 투자자동 게시판 사례들과 스터디장님의 2주차 실습 가이드를 참고했습니다.)

1

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요