본문 바로가기

코인/코인 개발

업비트(Upbit) 웹소켓(WebSocket)+파이썬(Python)으로 실시간 데이터 수신하기

이전 포스팅에서는 업비트 API를 사용해 차트 데이터를 얻는 방법을 설명했었습니다.

2021.07.07 - [코인/코인 개발] - 업비트 API를 이용한 코인 거래량 급등 알리미 프로그램

 

업비트 API를 이용한 코인 거래량 급등 알리미 프로그램

거래량 급등 알리미가 있으면 좋겠다는 말을 듣고 빠르게 작성해본 파이썬 프로그램. 일단 API 작성이야 어려운 건 아닌데 거래량 급등 조건 알림 방식 이 2가지가 관건이다. 거래량 급등 조건은

blog.coinali.me

 

해당 API는 REST API로, 만약 실시간으로 변경되는 시세를 확인하기 위해선 주기적인 호출(Polling)이 필요하게 되고 이는 서버에 과부하를 줄 수 있어 업비트에서는 요청 제한을 걸어두고 있습니다.

2021.07.09 - [코인/코인 개발] - 업비트(Upbit) API를 이용해 프로그램 개발 시 주의 사항

 

업비트(Upbit) API를 이용해 프로그램 개발 시 주의 사항

업비트 API 키 발급 및 거래량 급등 알리미 프로그램 등의 관련 사항을 지난 블로그에서 다뤘습니다만, 주의 사항 또한 알릴 필요가 있겠다 싶어 글을 남깁니다. 2021.07.07 - [코인/코인 개발] - 업비

blog.coinali.me

 

때문에 실시간 시세를 확인하기 위해서는 웹소켓(Websocket)을 이용하는 것이 바람직한데, 이번 포스팅에서는 파이썬으로 업비트 실시간 시세를 확인하는 방법을 알아보겠습니다.


* 코드는 Python 3.8에서 테스트되었습니다.

 

먼저 websockets 라이브러리를 설치합니다.

pip install websockets

 

업비트 웹소켓 연결이 보안 연결을 필요로 하는데, 요청 측에서는 자기 서명 인증서(Self-Signed Certificate)를 사용할 수 있습니다. 인증서는 openssl을 사용해 다음과 같이 간단히 발급할 수 있습니다.

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.crt

Generating a RSA private key
........................+++++
.................................................+++++
writing new private key to 'selfsigned.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Seoul
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:coinali.me
Email Address []:

$

 

먼저 업비트 웹소켓에서 제공하는 데이터 형식 중 OHLCV(Open, High, Low, Close, Volume)만 다음과 같이 가져올 수 있도록 했습니다. 보다 자세한 데이터 제공 사항은 업비트 문서에서 확인하실 수 있습니다.

class Ticker:

    def __init__(self, code, timestamp, open, high, low, close, volume):
        self.code = code
        self.timestamp = timestamp
        self.open = open
        self.high = high
        self.low = low
        self.close = close
        self.volume = volume

    @staticmethod
    def from_json(json):
        return Ticker(
            code=json['code'],
            timestamp=datetime.fromtimestamp(json['trade_timestamp'] / 1000, tz=pytz.timezone('Asia/Seoul')),
            open=json['opening_price'],
            high=json['high_price'],
            low=json['low_price'],
            close=json['trade_price'],
            volume=json['acc_trade_price']
        )

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return f'Ticker <code: {self.code}, timestamp: {self.timestamp.strftime("%Y-%m-%d %H:%M:%S")}, ' \
               f'open: {self.open}, high: {self.high}, ' \
               f'low: {self.low}, close: {self.close}, volume: {self.volume}>'

 

 

업비트 웹소켓 연결, 요청 및 데이터 수신은 다음과 같습니다. 예제에서는 원화 마켓에서 비트코인(KRW-BTC)과 이더리움(KRW-ETH) 데이터를 수신하는 요청을 보냅니다.

import asyncio
import json
import pathlib
import signal
import ssl
import uuid
from datetime import datetime
from functools import partial

import pytz
import websockets


ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
self_signed_cert = pathlib.Path(__file__).with_name("selfsigned.crt")
ssl_context.load_verify_locations(self_signed_cert)


async def recv_ticker():
    uri = 'wss://api.upbit.com/websocket/v1'
    markets = ['KRW-BTC', 'KRW-ETH']

    async with websockets.connect(uri, ssl=ssl_context) as websocket:
        var = asyncio.Event()

        def sigint_handler(var, signal, frame):
            print(f'< recv SIG_INT')
            var.set()

        signal.signal(signal.SIGINT, partial(sigint_handler, var))

        req = [{
            'ticket': str(uuid.uuid4()),
        }, {
            'type': 'ticker',
            'codes': markets
        }]
        print(f"> {req}")
        await websocket.send(json.dumps(req))

        while not var.is_set():
            recv_data = await websocket.recv()
            res = Ticker.from_json(json.loads(recv_data))
            print(f"> {res}")


asyncio.get_event_loop().run_until_complete(recv_ticker())

 

요청 정보는 다음과 같습니다.

 

  • 연결 URI: wss://api.upbit.com/websocket/v1
  • 데이터 요청:
    • ticket: 업비트에서 사용하는 요청 식별 값
    • type: 수신할 시세 타입 ('ticker': 현재가, 'trade': 체결, 'orderbook': 호가)
    • codes: 수신할 시세 종목 정보 (예: KRW-BTC)

 

또한, SIG_INT 시그널을 받을 때까지 지속적으로 데이터를 수신합니다. 수행 결과는 다음과 같습니다.

 

> [{'ticket': '5b50a885-c703-4b97-ad26-e9359163bf4e'}, {'type': 'ticker', 'codes': ['KRW-BTC', 'KRW-ETH']}]
> Ticker <code: KRW-BTC, timestamp: 2021-08-03 22:53:31, open: 45451000.0, high: 46150000.0, low: 44440000.0, close: 45145000.0, volume: 252967719495.32422>
> Ticker <code: KRW-ETH, timestamp: 2021-08-03 22:53:29, open: 3024000.0, high: 3057000.0, low: 2865000.0, close: 2951000.0, volume: 255937299191.82175>
...
> Ticker <code: KRW-ETH, timestamp: 2021-08-03 22:53:55, open: 3024000.0, high: 3057000.0, low: 2865000.0, close: 2951000.0, volume: 256085728038.51974>
> Ticker <code: KRW-BTC, timestamp: 2021-08-03 22:53:55, open: 45451000.0, high: 46150000.0, low: 44440000.0, close: 45145000.0, volume: 253102651324.1243>
> Ticker <code: KRW-BTC, timestamp: 2021-08-03 22:53:56, open: 45451000.0, high: 46150000.0, low: 44440000.0, close: 45141000.0, volume: 253102656818.68683>
< recv SIG_INT
> Ticker <code: KRW-BTC, timestamp: 2021-08-03 22:53:56, open: 45451000.0, high: 46150000.0, low: 44440000.0, close: 45141000.0, volume: 253102836675.77878>

Process finished with exit code 0

 

궁금한 점은 댓글로 문의주세요.