NOONOO TRADING무료 채팅방 입장

웹소켓 끊김 — 시세가 멈춘 줄 모르고 도는 봇이 가장 위험하다

자동매매를 돌리다 보면 이런 일이 생깁니다. 프로그램은 멀쩡히 켜져 있고, 에러도 없고, 로그도 조용합니다. 그런데 가격이 30분째 똑같습니다. 시세를 받아오는 웹소켓 연결이 끊겼는데, 끊겼다는 사실을 아무도 알려주지 않은 것입니다.

웹소켓이 끊기면 에러가 아니라 침묵이 온다

REST 방식은 내가 물어보고 답을 받습니다. 답이 안 오면 즉시 실패로 잡힙니다. 웹소켓은 반대입니다. 한 번 연결해두면 거래소가 알아서 밀어주는 구조라, 내 쪽은 가만히 기다리는 상태가 정상입니다.

두 방식의 실패 모습

REST (내가 물어봄)
  요청 → 응답 없음
  → 타임아웃 에러
  → 코드가 바로 안다

웹소켓 (거래소가 밀어줌)
  연결 → 데이터 옴 → 데이터 옴 →
  → 아무 일도 안 일어남
  → 코드는 기다리는 중이라고 판단

─────────────
핵심
  끊김과 한산한 시장
  코드 입장에선 구별되지 않는다

중간에 있는 공유기, 회사 방화벽, 이동통신망은 오래 조용한 연결을 임의로 끊습니다. 이때 정중하게 "끊습니다"라고 알려주고 끊는 경우도 있지만, 그냥 패킷을 버리기만 하는 경우도 많습니다. 후자면 내 프로그램은 연결이 살아 있다고 믿은 채 영원히 대기합니다.

얼어붙은 마지막 가격이 하는 일

대부분의 봇은 "마지막으로 받은 가격"을 변수에 담아두고 그걸로 판단합니다. 스트림이 멈추면 그 변수는 마지막 값 그대로 고정됩니다. 문제는 봇이 그 값을 계속 현재가로 쓴다는 점입니다.

손절이 안 걸리는 흐름

롱 진입 100,000
손절선 98,000

12:00 마지막 수신가 99,800
12:00 연결 끊김 (봇은 모름)

12:05 실제 시장가 97,000
  봇이 보는 값 = 99,800
  99,800 > 98,000 → 손절 안 함

12:20 실제 시장가 95,500
  봇이 보는 값 = 여전히 99,800

─────────────
손실 −4,500 (−4.5%)
봇 화면상 손실 −200

봇은 아무 문제 없다고 판단 중

여기서 배율이 붙으면 결과가 달라집니다. 20배라면 원가격 −4.5%는 증거금 기준 −90%입니다. 손절을 코드에 넣어두었다는 사실은 손절이 실행된다는 뜻이 아닙니다. 데이터가 들어오고 있을 때만 실행됩니다. 거래소에 미리 걸어두는 주문과 봇이 판단해서 내는 주문의 차이가 여기서 드러납니다 — 손절 주문을 거래소 쪽에 미리 올려두면 내 프로그램이 죽어도 살아 있습니다.

놓친 30초가 지표를 통째로 어긋나게 한다

가격뿐 아니라 캔들도 마찬가지입니다. 스트림이 끊긴 동안의 캔들은 그냥 존재하지 않게 됩니다. 봇은 빈 자리를 인식하지 못하고 앞뒤를 붙여버립니다.

1분봉 20개 이동평균 예시

정상
  최근 20개 = 12:00 ~ 12:19

12:05 ~ 12:12 연결 끊김 (8개 유실)

봇이 가진 배열
  11:52~12:04 + 12:13~12:19
  = 20개 (개수는 맞다)

─────────────
실제로는
  28분치를 20개로 계산
  평균 기간이 1.4배 늘어남

더 나쁜 것
  유실 구간이 급락 구간이었다면
  그 하락은 지표에서 사라진다

개수가 맞기 때문에 어떤 검사도 걸리지 않습니다. 이동평균, RSI, ATR 전부 조용히 틀린 값을 냅니다. 그리고 유실은 보통 가장 바쁜 순간에 생깁니다. 거래가 폭증하면 데이터량도 폭증하고, 연결이 그때 끊기기 때문입니다. 결국 가장 중요한 구간이 빠진 채 지표가 계산됩니다.

침묵과 고장을 구분하는 방법 — 하트비트

해결 원리는 단순합니다. 조용한 것이 정상인 채널에서는, 아무 일 없어도 주기적으로 신호가 오게 만들면 됩니다.

연결 감시 구조

거래소 → 주기적 ping 전송
내 쪽 → 즉시 pong 응답

내 쪽 타이머
  마지막 수신 시각 기록
  N초 넘게 아무것도 없으면
  → 연결이 죽었다고 단정
  → 끊고 재연결

─────────────
기준 잡기 (예시)
  ping 주기 20초라면
  임계치 60초 (3회 놓침)

너무 짧으면 → 멀쩡한 연결을 끊는다
너무 길면 → 얼어붙은 채 오래 방치

중요한 건 임계치를 넘으면 의심하지 말고 끊는 것입니다. "혹시 살아 있을지도 모르니 조금 더 기다리자"는 판단이 앞의 손절 사고를 만듭니다. 재연결 비용은 몇 초지만, 얼어붙은 데이터로 도는 비용은 포지션 전체입니다. 이 원리는 API 시간 오차를 다룰 때와 같습니다 — 애매한 상태를 정상으로 가정하지 않습니다.

데이터가 낡았으면 새 진입을 멈춘다

재연결이 끝날 때까지 봇을 완전히 멈출 필요는 없습니다. 다만 행동의 종류를 나눠야 합니다.

데이터 신선도 게이트

마지막 수신 후 경과 시간으로 판단

0 ~ 5초   → 정상 · 전부 허용
5 ~ 30초 → 신규 진입 중단
    · 기존 포지션 관리는 유지
    · 청산·손절은 계속 허용
30초 초과 → 강제 재연결 · 알림

─────────────
왜 청산은 막지 않나
  낡은 데이터로 들어가는 것은 도박
  낡은 데이터로 나오는 것은 방어

둘을 같은 규칙으로 묶으면
  위험한 쪽에 갇힌다

진입과 청산을 한 덩어리로 막아버리는 설계가 흔한데, 이러면 데이터가 끊긴 동안 포지션에서 나올 수단이 사라집니다. 나가는 길은 언제나 열어둡니다.

재연결 후 가장 먼저 할 일 — 상태 다시 묻기

다시 연결되면 시세는 곧 들어옵니다. 그런데 끊겨 있던 동안 내 계정에 무슨 일이 있었는지는 스트림이 알려주지 않습니다.

끊긴 사이에 벌어질 수 있는 일

· 미리 걸어둔 손절이 체결됐다
· 지정가가 부분 체결됐다
· 강제청산이 발생했다
· 자금 조달 수수료가 빠져나갔다

스트림 재연결로는
  이 과거 사건들을 못 받는다
  (밀어주는 건 지금부터의 일)

─────────────
재연결 직후 필수 절차
  1) REST로 포지션 조회
  2) REST로 미체결 주문 조회
  3) 내부 기록과 다르면 거래소 쪽을 채택
  4) 차이를 로그에 남긴다

내부 기록에는 롱 0.1이 있는데 실제로는 이미 청산돼 0인 상태에서 봇이 "청산해야지" 하고 매도를 내면 반대 방향 신규 포지션이 생깁니다. 이런 어긋남을 다루는 방법은 포지션 대조에서 더 자세히 다룹니다. 원칙은 하나입니다 — 거래소가 정답이고 내 기록은 추정입니다.

재연결 폭풍 — 고치려다 더 막히는 경우

끊기면 바로 다시 붙는 코드는 상황에 따라 스스로를 막습니다.

즉시 재시도의 결과

거래소 점검으로 60초 다운
재연결 간격 = 0.1초

60초 ÷ 0.1초 = 600회 시도

거래소 접속 한도 초과
  → IP 차단 10분
  → 복구됐는데 내가 못 붙는다

─────────────
백오프 (간격 늘리기)
  1초 → 2초 → 4초 → 8초 →
  16초 → 30초 (상한)

+ 무작위 지연 0~1초 추가
  (여러 프로세스가 동시에
  몰려드는 것을 흩는다)

여러 봇을 한 IP에서 돌린다면 백오프에 무작위 지연을 넣는 것이 특히 중요합니다. 안 넣으면 전부 같은 박자로 재시도해 요청 한도 초과(429)를 스스로 만듭니다. 상한도 필요합니다 — 간격이 무한정 늘면 거래소가 복구된 뒤에도 몇 분씩 붙지 않습니다.

호가창은 더 조용히 오염된다

호가창(오더북)을 구독하는 경우 문제가 한 단계 더 있습니다. 호가창은 보통 전체 스냅샷 한 번 + 그 뒤로 변경분만 받는 방식입니다.

증분 누락이 만드는 유령 호가

스냅샷 수신 (기준 번호 1000)
증분 1001 · 1002 · 1003 … 정상

1004 유실 (잠깐의 끊김)
1005부터 다시 수신

내 호가창
  1004에서 사라진 매도벽
  계속 남아 있다

─────────────
결과
  · 없는 벽을 보고 진입을 미룬다
  · 실제 최우선호가와 어긋난 값으로
    지정가를 낸다 → 체결 안 됨

대책
  증분에 붙은 순번을 매번 확인
  하나라도 건너뛰면
  → 버리고 스냅샷부터 다시

순번 검사를 넣지 않으면 호가창은 시간이 갈수록 실제와 벌어지는데, 화면에는 그럴듯한 숫자가 계속 표시됩니다. 여기서 나온 값으로 지정가를 계산하면 체결률이 이유 없이 나빠지고, 시장가로 바꾸면 슬리피지가 예상보다 커집니다.

점검 항목

자동매매를 돌린다면 아래는 한 번 넣어두면 끝나는 것들입니다. 자세한 연결 방식은 거래소 API 연동 가이드에 정리돼 있습니다.

넣어둘 것

· 마지막 수신 시각을 항상 기록
· 임계치 초과 시 즉시 끊고 재연결
· 데이터가 낡으면 신규 진입만 차단
· 재연결 직후 REST로 포지션·주문 재조회
· 백오프 + 무작위 지연 + 상한
· 호가창은 순번 검사 · 어긋나면 재구독
· 손절은 가능하면 거래소에 미리 걸어둔다
· 끊김 횟수·지속 시간을 로그로 남긴다

─────────────
가장 중요한 것
  "조용하다"를 정상으로 가정하지 않기

끊김 기록을 남겨두면 나중에 손실 거래를 검토할 때 그 시각에 데이터가 있었는지 확인할 수 있습니다. 남기지 않으면 전략이 틀린 것인지 데이터가 없었던 것인지 영영 구분되지 않습니다.

정리

웹소켓은 끊길 때 에러가 아니라 침묵으로 실패한다
코드 입장에서 끊김과 한산한 시장은 같아 보인다
마지막 가격이 얼어붙으면 손절 조건이 영원히 안 걸린다
유실 캔들은 개수가 맞아 어떤 검사도 통과한다
유실은 보통 가장 바쁜 구간에 생긴다
하트비트와 임계치로 침묵과 고장을 구분한다
임계치를 넘으면 의심하지 말고 끊는다
낡은 데이터에선 진입만 막고 청산은 열어둔다
재연결 직후 REST로 포지션·주문을 다시 묻는다
즉시 재시도는 스스로를 차단한다 — 백오프와 무작위 지연
호가창은 순번이 끊기면 스냅샷부터 다시 받는다

한 줄로 줄이면 이렇습니다. 봇이 보는 화면과 시장이 같다는 보장은 어디에도 없습니다. 데이터가 살아 있다는 사실 자체를 매 순간 확인하는 코드가 있어야, 나머지 전략이 의미를 갖습니다.

주의

본문의 임계치, ping 주기, 백오프 간격, 가격과 수량은 구조를 설명하기 위한 가정 예시이며 특정 거래소의 실제 측정값이 아닙니다. ping·pong 주기, 연결 유지 시간, 접속 횟수 한도, 호가창 증분 순번 규칙, 재구독 방식은 거래소마다 다르므로 본인이 쓰는 거래소 문서에서 확인하고 소액으로 직접 시험해야 합니다. 레버리지 거래는 원금 전액을 잃을 수 있으며, 투자 판단과 그 결과는 본인 책임입니다.

NOONOO TRADING 무료 채팅방에서 실시간 트레이딩을 같이 보세요.

무료 채팅방 입장 →

📈 OKX 신규 가입 시 거래 수수료 할인

OKX 수수료 할인 가입 →