실시간 가상자산 시세 보드를 다시 짠다면 — 2021년 강의 복기

2021년 12월 14일, 「실시간 가상자산 트레이딩 만들기」라는 제목으로 강의를 적었습니다. 목표는 자동매매가 아니었습니다. BTC·ETH·SOL 시세를 모으고, 그래프로 보고, 거래소 사이 가격 차이가 나면 알림을 울리는 관측 보드였습니다. 파이썬 라이브러리는 requests, selenium, finance-datareader, pandas, pyupbit를 pip로 깔았습니다. 이 글은 그 다섯 단계를 2026년 시점에서 다시 검수합니다. 시세를 맞히는 글이 아닙니다.

강의 제목에 ‘트레이딩’이 들어 있어도, 구현의 본체는 수집·시각화·조건 알림입니다. 주문 API를 붙이는 순간 규제·키 관리·손실 한도가 다른 프로젝트가 됩니다.

다섯 단계가 하려던 일

단계 2021년 실습 2026년에 먼저 바꿀 것
1 CoinMarketCap HTML 스크래핑 + Korbit REST 공개 페이지 파싱을 버리고 거래소 공식 REST/WebSocket
2 Selenium 히스토리 + FinanceDataReader 그래프 히스토리는 라이브러리/공식 캔들 API만
3 Qt Designer로 coinTrade.ui, PyQt5 로드 UI 골격은 유지. 시그널-슬롯 이름은 그대로 둬도 됩니다
4 Timer로 실시간 그래프 폴링 간격과 rate limit을 거래소 문서에 맞출 것
5 거래소 간 2%·5% 차이 알람 수수료·출금 지연·호가 잔량을 빼기 전에는 차익이 아닙니다

1단계: 스크래핑이 먼저 죽는 이유

강의에서는 CoinMarketCap 비트코인 페이지를 requests로 받고 BeautifulSoup으로 가격 태그를 집었습니다. 당시 예시 숫자는 56,590.81달러였고, “조회 시점에 따라 다르다”고 적어 두었습니다. 그 주의는 맞습니다. 틀린 것은 클래스 이름에 의존한 파서입니다. 공개 시세 페이지는 레이아웃이 자주 바뀌고, 약관상 자동 수집을 막는 곳이 많습니다. 2026년에 같은 셀렉터를 돌리면 빈 문자열이나 차단 페이지가 돌아옵니다.

같은 장에서 Korbit REST https://api.korbit.co.kr/v1/ticker/detailed 로 JSON을 받아 last와 volume을 읽었습니다. 이 방향이 맞습니다. HTML이 아니라 계약된 필드입니다. 다만 v1 엔드포인트는 거래소가 버전을 올리면 사라집니다. 보드를 다시 짠다면 거래소 개발자 문서의 현재 ticker 경로와 초당 호출 한도를 먼저 적고, 키 없이 되는 공개 ticker와 키가 필요한 잔고·주문을 코드 폴더부터 나눕니다.

  • 시세 수집은 REST 또는 WebSocket. HTML 파서는 비상용으로도 두지 않습니다.
  • User-Agent를 속여 약관을 우회하지 않습니다. 차단되면 그 거래소는 보드에서 뺍니다.
  • 응답을 딕셔너리로 바꾼 뒤 필수 키(last, 시각, 심볼)가 없으면 그 틱은 버립니다.

REST 폴링과 WebSocket, 언제 무엇을 쓰는가

2021년 강의는 타이머로 REST를 주기적으로 호출하는 폴링 방식이었습니다. 지금 다시 짠다면 거래소가 WebSocket ticker를 제공하는지부터 확인하는 것이 순서입니다. 둘의 차이는 단순한 구현 편의가 아니라 운영 특성 자체가 다릅니다.

방식 장점 단점
REST 폴링 구현이 단순, 상태 관리 부담이 적음 초당 호출 한도에 걸리기 쉽고, 폴링 간격만큼 지연 발생
WebSocket 구독 체결마다 실시간 푸시, 호출 한도 부담이 적음 연결 끊김·재연결 로직이 필수, 구현 복잡도 증가

WebSocket을 쓰기로 했다면 재연결 로직은 선택이 아니라 필수입니다. 연결이 끊겼을 때 곧바로 재시도하면 거래소 서버에 부담을 주고 차단으로 이어질 수 있으므로, 재시도 간격을 점진적으로 늘리는 지수 백오프(exponential backoff)를 기본으로 둡니다. 아래는 하루 만에 다시 짜는 수준의 최소 구현입니다.

# 의사코드: WebSocket 재연결 - 지수 백오프, 상한 60초
delay = 1
while True:
    try:
        ws = connect(exchange_ws_url)
        subscribe(ws, symbols=["BTC", "ETH", "SOL"])
        delay = 1  # 연결 성공 시 초기화
        for tick in ws:
            if not has_required_fields(tick):
                continue  # 필수 키 없는 틱은 버림
            on_tick(normalize(tick))
    except ConnectionError:
        sleep(delay)
        delay = min(delay * 2, 60)

2단계: 히스토리 그래프

강의 후반부는 Selenium으로 자바스크립트 렌더 이후 DOM을 읽고, matplotlib로 그렸습니다. requests.text가 정적 HTML만 보는 한계를 설명하려고 Selenium을 넣은 것입니다. 실습으로는 유효합니다. 운영 보드로는 무겁습니다. 브라우저 프로세스가 떠 있으면 타이머마다 메모리와 차단 위험이 같이 올라갑니다.

대안으로 FinanceDataReader로 BTC·ETH·SOL 시계열을 받았습니다. 설치는 pip install -U finance-datareader였습니다. 라이브러리는 편하지만, 소스 거래소와 시간대가 문서에 명시되지 않으면 그래프 축을 믿을 수 없습니다. 캔들 주기, UTC인지 KST인지, 수정 가격 여부를 로그 한 줄에 남기십시오.

3–4단계: PyQt5 보드와 타이머

아나콘다의 Qt Designer로 Main Window를 만들고, 콤보박스(코인), 조회·분봉 버튼, 2%·5% 차이 체크박스, 이벤트 리스트, 그래프 영역을 coinTrade.ui로 저장했습니다. 파이썬은 uic.loadUiType으로 다중 상속해 setupUi를 호출했습니다. 조회 클릭은 pushButton.clicked, 알람 체크는 stateChanged에 연결했습니다. 이 골격은 지금도 읽기 좋습니다.

타이머 실시간 그래프는 거래소 API를 주기적으로 치는 구조입니다. 여기서 강의에 없던 운영 규칙을 붙입니다.

  • 타이머 기본값을 1초로 두지 마십시오. 공개 ticker라도 초당 호출 한도가 있습니다.
  • 실패 틱을 그래프로 이어 그리지 마십시오. 마지막 정상 값과 공백을 구분해야 장애가 보입니다.
  • 창을 닫을 때 타이머와 네트워크를 같이 끕니다. 강의의 closeEvent가 그 자리입니다.

API 키 관리 — 저장소에 절대 넣지 않는다

강의는 관측 보드였지만, 이후 주문 API로 확장하고 싶어지는 순간 키 관리가 첫 번째 관문이 됩니다. 최소한 다음 세 가지는 지켜야 합니다.

  • API 키·시크릿은 소스 코드나 Git 저장소에 절대 커밋하지 않습니다. 환경변수 또는 별도 시크릿 관리 도구(.env + .gitignore, 혹은 OS 키체인)를 씁니다.
  • 거래소가 IP 화이트리스트, 출금 권한 분리 기능을 제공하면 관측용 키에는 반드시 읽기 전용 권한만 부여합니다.
  • 키를 실수로 커밋했다면 즉시 폐기하고 재발급합니다. 히스토리에서 지웠다고 안전해지지 않습니다.

5단계: 가격 차이 알림은 차익이 아니다

체크박스 두 개는 “2% 차이”, “5% 차이”였습니다. 거래소 A와 B의 last를 빼 비율이 넘으면 리스트에 한 줄을 넣었습니다. 관측으로는 충분합니다. 그 숫자를 주문으로 옮기면 거의 항상 집니다. 수수료 왕복, 출금 확인 시간, 호가 잔량, 네트워크 수수료가 last 차이보다 큰 경우가 많습니다.

알림이 말해 주는 것 알림이 말해 주지 않는 것
두 소스의 last가 벌어졌다 그 가격에 체결 가능한 수량
임계값을 넘긴 시각 출금이 몇 분 걸리는지
어느 심볼인지 수수료를 뺀 순차이

지능형 알림이라는 말은 강의 표현입니다. 구현은 임계값 비교입니다. 2026년에 살을 붙인다면 스프레드에서 예상 수수료를 빼고, 같은 방향으로 N틱 연속일 때만 울리게 하는 정도가 한계입니다. 그 이상은 자동매매이고, 이 글의 범위 밖입니다.

하루 만에 다시 짜는 순서

  • 거래소 두 곳의 공식 ticker 문서를 읽고, 키 없는 GET만 허용합니다.
  • 심볼·가격·시각을 같은 스키마로 정규화합니다.
  • PyQt 창에 마지막 정상 시각을 표시합니다. 지연이 보이면 보드가 죽은 것입니다.
  • 임계값 알림은 로그 파일에도 남깁니다. 창을 닫아도 남아야 복기가 됩니다.
  • 주문·출금·이체 API는 연결하지 않습니다. API 키를 저장소에 넣지 않습니다.

시세 보드가 거짓말하는 세 가지 시각

거래소 A의 last와 B의 last를 같은 초로 빼면, 둘 중 하나는 이미 지난 체결입니다. 보드에는 last만이 아니라 수신 시각과 거래소 시각을 같이 그립니다. 차이가 1초를 넘으면 스프레드가 아니라 지연입니다. 중간값(mid)과 last를 섞어 그리지 마십시오. 호가가 비어 있는 last는 체결 불가능한 숫자입니다.

이벤트 보드가 할 일 하지 말 일
타임아웃 마지막 정상 칸을 비우고 적색 직전 값으로 선을 이어 그림
부분 필드 누락 그 틱 폐기 0으로 채워 평균
재연결 지수 백오프, 상한 60초 1초마다 재시도

2% 알림을 주문으로 옮기지 않는 검수

왕복 수수료, 출금 확인, 호가 잔량을 빼기 전의 비율은 관측입니다. 알림 로그에 예상 수수료를 한 칸 더 넣고, 순차이가 음수면 색을 바꿉니다. 같은 방향으로 N틱이 연속일 때만 울리게 하면 깜빡임은 줄어듭니다. 그 다음 칸에 주문 API를 두면 이 글의 범위가 아닙니다. API 키는 보드 저장소에 두지 않습니다. 2021년 강의의 PyQt 골격은 유지하되, 수집 계층만 REST/WebSocket으로 교체하는 것이 하루 작업의 상한입니다.

Takeaway

  • 2021년 강의의 본체는 트레이딩 봇이 아니라 시세 관측 보드입니다.
  • CoinMarketCap 같은 공개 HTML 파서는 수업용입니다. 운영에는 공식 API만 둡니다.
  • PyQt5 UI와 타이머 골격은 재사용하되, rate limit과 실패 틱 처리를 먼저 넣습니다.
  • WebSocket을 쓴다면 지수 백오프 재연결 로직은 선택이 아니라 필수입니다.
  • 거래소 간 last 차이는 수수료와 출금을 빼기 전에는 차익이 아닙니다.
  • API 키는 코드 저장소에 절대 넣지 않고, 관측용 키는 읽기 전용 권한만 부여합니다.
  • 투자 조언이 아닙니다. 자동매매와 키 관리가 필요하면 별도 설계입니다.

관련 글: 블랙-숄즈 프라이서 검수 노트, 실무 Python 체크리스트.

댓글 남기기