트레이딩뷰에서 전략을 만들고 나면 다음 질문은 항상 같습니다. 이 알림을 어떻게 실제 주문으로 바꾸는가. 트레이딩뷰는 신호를 만드는 도구이지 주문을 넣는 도구가 아니기 때문에, 중간에 무언가가 하나 더 필요합니다.
먼저 결론: 세 조각으로 나뉩니다
구조는 항상 이 세 개입니다. 트레이딩뷰(신호 생성) → 내 중계 서버(판단·검증) → MT4·MT5 계좌(체결). 트레이딩뷰가 직접 브로커에 주문을 넣는 경로는 없습니다. 중계 서버가 웹훅을 받아 내용을 확인하고, 주문 실행 API를 호출하는 방식이 사실상 표준입니다.
중계 서버라고 해서 거창한 것이 필요하지는 않습니다. 웹훅 하나를 받아 HTTP 요청 하나를 보내는 함수면 충분하고, 서버리스 함수로도 됩니다. 중요한 것은 서버의 규모가 아니라 그 안에서 무엇을 검증하는지입니다.
웹훅 페이로드를 어떻게 설계할까
트레이딩뷰 알림 메시지는 자유 문자열이므로 JSON을 그대로 넣을 수 있습니다. 파싱 실패를 줄이려면 필드를 미리 고정하는 편이 낫습니다.
{
"secret": "긴-난수-문자열",
"symbol": "EURUSD",
"side": "buy",
"volume": 0.10,
"sl": 1.0820,
"tp": 1.0960,
"strategy": "ema-cross-v3",
"bar_time": "{{timenow}}"
}
secret은 반드시 넣습니다. 웹훅 주소는 공개 인터넷에 열려 있고 트레이딩뷰 IP만으로 걸러내는 방식은 취약합니다. bar_time은 중복 처리 방지에 씁니다. 이유는 다음 절에 있습니다.
중복 체결은 왜 생기고 어떻게 막는가
실전에서 가장 많이 터지는 문제입니다. 트레이딩뷰 알림은 재전송되거나, 캔들이 확정되기 전에 조건이 켜졌다 꺼지면서 여러 번 발사될 수 있습니다. 중계 서버가 받는 대로 주문을 넣으면 같은 신호로 포지션이 두세 개 생깁니다.
실무적으로 세 가지를 겹쳐 씁니다.
- 멱등 키:
strategy + symbol + side + bar_time을 하나의 키로 만들어 이미 처리한 키면 무시합니다. 저장은 메모리든 Redis든 상관없지만, 서버가 재시작되어도 남는 곳이 안전합니다. - 캔들 확정 후 실행: 트레이딩뷰 알림 옵션에서 봉 종료 시점 기준으로 설정하면 신호가 번쩍이는 문제가 크게 줄어듭니다.
- 현재 포지션 확인: 주문 직전에 계좌의 보유 포지션을 조회해, 이미 같은 방향 포지션이 있으면 진입을 건너뜁니다. 신호를 신뢰하기보다 계좌 상태를 신뢰하는 편이 안전합니다.
주문 실행 부분은 얼마나 복잡한가
이 지점이 원래 가장 번거로운 부분이었습니다. 예전 방식은 MQL로 EA를 작성해 터미널에 올리고, 그 터미널을 VPS에서 계속 띄워두고, 웹훅을 파일이나 소켓으로 EA에 전달하는 식이었습니다. 조각이 많고 무엇이 멈췄는지 확인하기도 어렵습니다.
REST API 계층을 쓰면 이 부분이 HTTP 요청 하나로 줄어듭니다.
# 1) 계좌 상태 확인 — 마진이 충분한지, 이미 포지션이 있는지
curl -s -H "x-api-key: $KEY" \
"https://api.metatraderapi.net/AccountSummary?id=$ACCOUNT_ID"
# 2) 시장가 주문 실행
curl -s -X POST -H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{"id":"'$ACCOUNT_ID'","symbol":"EURUSD","operation":"buy","volume":0.1,"stoploss":1.0820,"takeprofit":1.0960}' \
"https://api.metatraderapi.net/OrderSend"
엔드포인트 이름과 파라미터는 API 문서에서 확인하는 것이 정확합니다. 요점은 언어와 무관하게 HTTP 클라이언트만 있으면 된다는 점입니다. Python, Node.js, Go, PHP 어느 쪽이든 동일합니다.
실패 처리를 빼놓으면 반드시 후회합니다
주문은 실패할 수 있습니다. 시장이 닫혀 있거나, 마진이 부족하거나, 심볼 이름이 브로커마다 다르거나(EURUSD vs EURUSD.m vs EURUSDpro), 스프레드가 벌어져 지정가가 거부되기도 합니다.
최소한 이 세 가지는 만들어 두는 편이 좋습니다.
- 응답 코드와 본문을 모두 로그로 남깁니다. 나중에 “신호는 왔는데 주문이 없다”를 추적할 유일한 근거입니다.
- 재시도는 조건부로만. 네트워크 타임아웃은 재시도할 수 있지만, ‘마진 부족’ 같은 거부는 재시도하면 안 됩니다. 같은 주문을 반복해서 밀어넣는 사고가 여기서 나옵니다.
- 실패 알림: 텔레그램 봇으로 실패만 보내도 충분합니다. 성공까지 다 보내면 며칠 안에 알림을 무시하게 됩니다.
심볼 이름 문제는 미리 표를 만들어 해결합니다
브로커마다 심볼 표기가 다릅니다. 전략은 EURUSD로 신호를 보내지만 실제 계좌에서는 다른 이름일 수 있습니다. 중계 서버 안에 매핑 표를 하나 두고, 신호의 심볼을 계좌 심볼로 바꿔서 주문하는 방식이 가장 단순합니다. 계좌를 여러 브로커에 두고 있으면 이 표가 필수입니다.
다계좌로 확장할 때
신호 하나를 여러 계좌에 반영하려면 중계 서버가 계좌 목록을 돌면서 각각 주문을 넣습니다. 이때 계좌마다 자금 규모가 다르므로 volume을 그대로 복사하면 안 되고, 계좌 잔고 대비 비율로 환산해야 합니다. 이 구조가 커지면 사실상 카피 트레이딩이 되며, 그때 필요한 것들은 카피트레이딩 시스템 직접 만들기에서 따로 정리했습니다.
계좌가 늘어날 때 비용이 계좌 수에 비례해 늘어나는지도 미리 확인하는 편이 좋습니다. 계좌 단위로 과금하는 구조와 플랜 단위로 과금하는 구조는 계좌 열 개 지점부터 차이가 커집니다. 요금제에서 기준을 확인할 수 있습니다.
정리
트레이딩뷰 웹훅 자동매매의 난이도는 주문을 넣는 코드가 아니라 그 주변에 있습니다. 인증, 중복 방지, 심볼 매핑, 실패 처리 — 이 네 가지를 처음부터 넣어두면 나머지는 단순한 HTTP 호출입니다. 터미널과 VPS를 관리하는 부분까지 덜어내면, 신호를 만드는 일에만 집중할 수 있습니다.
자주 묻는 질문
트레이딩뷰에서 직접 MT4로 주문을 보낼 수 있나요?
아닙니다. 트레이딩뷰는 알림(웹훅)만 보낼 수 있으므로 그 웹훅을 받아 주문 API를 호출하는 중계 서버가 필요합니다. 서버리스 함수 정도로도 충분합니다.
중복 주문을 막는 가장 확실한 방법은 무엇인가요?
멱등 키(전략+심볼+방향+캔들시각)로 이미 처리한 신호를 걸러내고, 주문 직전에 계좌의 보유 포지션을 조회해 중복 진입을 차단하는 방식을 함께 쓰는 것이 안전합니다.