TradingView 웹훅이 또 씹혔다 — 신호 유실 없이 자동매매를 지키는 방법

지난 몇 달 동안 제 시스템에서 가장 골치 아팠던 문제는 알고리즘 로직이 아니었습니다. TradingView에서 쏜 웹훅이 Flask 서버에 도착하지 않거나, 도착했는데 처리가 안 된 채 조용히 사라지는 일이었습니다. 포지션이 안 열려서 이상하다 싶어 로그를 뒤져보면 웹훅 수신 기록 자체가 없는 경우가 있었고, 반대로 수신은 됐는데 파싱 오류로 묻혀버린 경우도 있었습니다. 이번 칼럼은 그 경험을 바탕으로 웹훅 안정성을 어떻게 설계하면 좋은지 정리한 내용입니다.

웹훅이 왜 유실되는가 — 원인을 먼저 정확히 분리해야 합니다

웹훅 유실을 처음 경험하면 흔히 TradingView를 탓하게 됩니다. 저도 그랬습니다. 그런데 실제로 원인을 하나씩 분리해보니 TradingView 자체 문제보다 제 서버 쪽 문제가 더 많았습니다. 크게 세 가지 구간으로 나눌 수 있습니다. 첫 번째는 TradingView 발신 단계입니다. 알림 조건이 충족됐을 때 TradingView가 웹훅을 보내지 못하는 경우인데, 주로 TradingView 서버 부하나 알림 설정 오류에서 발생합니다. 두 번째는 네트워크 구간입니다. Cafe24 VPS 방화벽 설정이 잘못돼 있거나, 서버가 80/443 포트를 제대로 열지 않으면 패킷이 아예 도달을 못 합니다. 세 번째는 Flask 처리 단계입니다. 웹훅은 왔는데 JSON 파싱 실패, 예외 처리 미흡, 또는 서버가 재시작 중인 타이밍에 수신이 겹쳐서 그냥 사라지는 경우입니다. 제가 실제로 경험한 비율은 대략 TradingView 발신 문제 20%, 네트워크 구간 25%, Flask 처리 오류 55% 수준이었습니다. 즉 내가 통제할 수 있는 영역이 80%에 달했고, 이를 먼저 정비하는 것이 핵심이었습니다.

Flask 서버 방어 설계 — 들어온 신호는 반드시 기록하고 응답부터 보냅니다

제가 웹훅 안정성을 높이기 위해 Flask 서버에 적용한 핵심 원칙은 하나입니다. 신호가 들어오면 처리보다 기록과 응답을 먼저 합니다. TradingView는 웹훅을 보낸 뒤 응답을 받지 못하면 재시도를 하지 않습니다. 그래서 Flask가 무거운 처리를 하는 도중 타임아웃이 나면 신호가 그냥 증발합니다. 이를 막기 위해 저는 수신 즉시 raw payload를 SQLite 또는 텍스트 파일에 타임스탬프와 함께 기록하고, 200 OK를 먼저 리턴한 뒤 실제 주문 처리는 별도 스레드 또는 큐로 분리했습니다. 코드 흐름으로 보면, route 진입 즉시 request.get_data()로 원문 저장, 그 다음 jsonify({‘status’: ‘received’})를 return, 그리고 threading.Thread(target=process_signal, args=(payload,)).start() 순서입니다. 또 하나 중요한 점은 파싱 오류 처리입니다. TradingView 알림 메시지를 JSON으로 파싱할 때 try-except 없이 짜면 필드명 오타 하나로 전체가 터집니다. 저는 모든 키를 .get()으로 꺼내고 기본값을 반드시 지정해 예외가 나더라도 최소한 로그에는 남도록 설계했습니다. 그리고 gunicorn 실행 시 –workers 2 –timeout 30 옵션을 명시해서 단일 워커 블로킹으로 인한 유실도 방지하고 있습니다.

알림 이중화와 수신 검증 — 조용히 죽는 시스템을 막는 구조

Flask 서버를 아무리 잘 짜도 시스템이 조용히 죽어 있는 경우는 막기 어렵습니다. 저는 이걸 ‘무소음 장애’라고 부르는데, 웹훅이 안 오는 건지, 시장이 조건을 충족 안 한 건지, 아니면 서버가 다운된 건지 구분이 안 되는 상태가 가장 위험합니다. 이를 해결하기 위해 저는 세 가지를 병행합니다. 첫째, TradingView 알림을 웹훅 단독이 아닌 이메일 또는 텔레그램 알림과 동시에 발송하도록 설정합니다. 웹훅이 유실돼도 텔레그램에는 신호가 찍히므로 수동 개입 여부를 판단할 수 있습니다. 둘째, Flask 서버에 /health 엔드포인트를 만들어 두고 Make.com의 스케줄 모듈로 10분마다 GET 요청을 보내 200 이외의 응답이 오면 이메일 알림을 받습니다. 셋째, 수신 로그를 일별로 집계해서 신호가 하루 한 건도 안 들어온 날을 감지합니다. 시장 상황에 따라 무신호 구간이 정상일 수도 있지만, 저는 그 판단을 사람이 하도록 매일 아침 수신 건수를 텔레그램으로 보고받고 있습니다. 이 구조를 갖추고 나서 저는 장애를 훨씬 빠르게 인지하게 됐고, 무엇보다 시스템이 살아 있다는 확신을 갖고 본업에 집중할 수 있게 됐습니다. 완벽한 무중단은 없지만, 장애를 빨리 아는 것 자체가 자동매매 인프라의 경쟁력입니다.

💬 운영자 한마디

저는 웹훅 안정성 문제를 겪고 나서 ‘시스템이 작동 중’이라는 상태를 수동으로 확인하는 루틴을 매일 아침 5분씩 갖게 됐습니다. 완전 자동화를 목표로 하지만, 아이러니하게도 그 자동화를 지키는 건 사람의 확인입니다. 자동화할수록 모니터링도 자동화해야 한다는 걸 몸으로 배웠습니다.

— J_River · autoprofit 운영자

✅ 이번 주 체크리스트

  • TradingView 알림을 웹훅 + 텔레그램 동시 발송으로 이중화했는가
  • Flask 라우트에서 payload를 수신 즉시 파일 또는 DB에 기록하는가
  • 주문 처리 로직을 별도 스레드/큐로 분리해 200 응답을 먼저 리턴하는가
  • JSON 파싱에 try-except와 .get() 기본값을 적용했는가
  • gunicorn 실행 옵션에 –workers와 –timeout을 명시했는가
  • /health 엔드포인트를 만들고 외부 모니터링(Make.com 등)으로 주기 점검하는가
  • 하루 수신 건수를 집계해 무신호 구간을 감지하는 알림이 있는가

웹훅 한 줄이 유실되는 건 코드 문제가 아니라 설계 문제입니다. 신호가 왔는지 안 왔는지조차 모르는 상태를 먼저 없애는 것, 그게 자동매매 인프라 안정화의 시작입니다.

※ 본 콘텐츠는 운영자 개인의 경험과 연구를 바탕으로 한 정보 제공 목적이며, 특정 종목에 대한 투자 권유나 매매 시그널이 아닙니다. 암호화폐 및 자동매매는 원금 손실 가능성이 있는 고위험 활동이며, 모든 투자 판단과 그 결과에 대한 책임은 투자자 본인에게 있습니다.