본문 바로가기
Algorithm Trading/ComDon 프로그램 개발이야기

4. CCXT 라이브러리

by 컴돈AI 2023. 12. 30.

목차

    CCXT 라이브러리 소개

    • CCXT(CryptoCurrency eXchange Trading) 라이브러리는 암호화폐 거래를 위한 다양한 거래소의 API를 통합한 라이브러리입니다.
    • 다양한 거래소 중 가장 유명한 바이낸스 거래소에 대해서 살펴보도록하겠습니다. 우선적으로 바이낸스 API 발급과정이 필요합니다.

    바이낸스 API 등록

    • 바이낸스 홈페이지의 API Management에 접속한 뒤 Create API 를 클릭해줍니다.
    • 이메일과 핸드폰 인증을 해주면 손쉽게 API Key를 발급받을 수 있습니다.
    • API 가 생성되면 Edit restrictions를 눌러 추가적인 설정을 진행해야합니다.
      • 우선적으로 IP access restrictions에서 Restrict access to trusted IPs only를 체크헤주고 현재 내 IP(외부 IP 주소)를 입력해줍니다. (다른 IP에서 누군가 내 API를 통해서 거래 주문을 하는 것을 방지해줍니다.)
      • IP 등록을 하면 API restrictions에서 다음 항목을 체크해줍니다.
        • Enable Spot & Martgin Trading
        • Enable Futures
      • 설정이 완료되었으면 API Key와 Secret Key를 복사하여 저장한 뒤, Save 버튼을 눌러줍니다.
    • 참고 : 바이낸스 선물 API 제한
      • 분당 최대 1,200개의 주문.
      • 10초당 최대 300개의 주문.
      • https://www.binance.com/en/support/faq/rate-limits-on-binance-futures-281596e222414cdd9051664ea621cdc3
      • ccxt 의 API 제한 방지 기능
        • API 제한을 준수하기 위해서는 ccxt의 enableRateLimit 을 True로 설정해주면 됩니다.
        • ccxt 내부적으로 API 요청이 거래소의 제한을 초과하지 않도록 도와줍니다. 
        • 만약 나는 API 제한을 넘길정도의 주문을 하지 않는다고 한다면, enableRateLimit 을 False로 지정하고 프로그램을 작성해도 상관없습니다. 약간의 속도향상이 일어날 수 있습니다. 하지만 API 제한을 초과하는 위험은 사용자가 스스로 관리해야합니다.

    시장가 주문

    • BTCUSDT 기준으로 테스트를 진행하도록 하겠습니다. BTCUSDT 의 경우 최소 주문 금액이 0.003BTC입니다.
    • 선물의 경우 Long과 Short으로 양방향으로 배팅을 진행할 수 있습니다. 하지만 Long 진입 / Short 진입 / Long 청산 / Short 청산 으로 할 경우 다소 복잡할 수 있기 때문에 Buy와 Sell로 나누어서 간단하게 살펴보도록 하겠습니다.
    • Buy (= Long 진입 / Short 청산)
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            # buy, sell
            order = exchange.create_order("BTC/USDT", "MARKET", "buy", 0.003)
            pprint.pprint(order)
        
        
        main()
         
        • apiKey와 secret에는 Binance API Key를 작성해주면 됩니다.
    • Sell (= Short 진입 / Long 청산)
      • 위 코드에서 buy 부분을 sell로만 변경해주면 됩니다.
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            # buy, sell
            order = exchange.create_order("BTC/USDT", "MARKET", "sell", 0.003)
            pprint.pprint(order)
        
        
        main()

    지정가 주문

    • 지정가 주문은 시장가 주문코드에서 'MARKET' 을 'LIMIT'으로 변경하고 price를 추가해주면 됩니다.
      • 주의할점은 long의 경우 현재가보다 limit주문의 price가 높다면 시장가 주문이 됩니다. 마찬가지로 short의 경우 현재가보다 limit 주문의 price가 낮다면 시장가 주문이 됩니다.
    • 코드
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            symbol = "BTC/USDT"
            type = "LIMIT"
            side = "buy"
            amount = 0.003  # USDT 기준이 아닌 해당 코인 개수 기준
            price = 42200
        
            order = exchange.create_order(symbol, type, side, amount, price)
            pprint.pprint(order)
        
        
        main()

    StopLoss / TakeProfit 주문

    • StopLoss / TakeProfit 정의
      • StopLoss는 손절매를 의미합니다. 즉 손실을 제한하는 목적으로 특정 가격에 도달하면 자동으로 주문이 실행되어 더 큰 손실을 방지합니다.
      • TakeProfit은 익절매를 의미합니다. 즉, 이익을 확정하기 위한 목적으로 특정 가격에 도달하면 이익을 실현하기 위해 자동으로 주문이 체결됩니다.
      • Long의 경우 : StopLoss가격 < 현재가 < TakeProfit
      • Short의 경우 : StopLoss가격 > 현재가 > TakeProfit
    • StopLoss / TakeProfit 주문 코드
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            symbol = "BTC/USDT"
            type = "MARKET"
            side = "buy"
            amount = 0.003  # USDT 기준이 아닌 해당 코인 개수 기준
            price = None
            stopLossPrice = 40000
            takeProfitPrice = 45000
        
            order = exchange.create_order(symbol, type, side, amount)
            pprint.pprint(order)
        
            inverted_side = "sell" if side == "buy" else "buy"
        
            stopLossParams = {"stopPrice": stopLossPrice}
            stopLossOrder = exchange.create_order(
                symbol, "STOP_MARKET", inverted_side, amount, price, stopLossParams
            )
            pprint.pprint(stopLossOrder)
        
            takeProfitParams = {"stopPrice": takeProfitPrice}
            takeProfitOrder = exchange.create_order(
                symbol, "TAKE_PROFIT_MARKET", inverted_side, amount, price, takeProfitParams
            )
            pprint.pprint(takeProfitOrder)
        
        
        main()
      • 위 코드는 현재 포지션이 종료되더라도, stoploss 주문과 takeprofit 주문은 그대로 남아 있게 됩니다. 또는 만약 stoploss나 takeprofit 주문이 체결되어서 현재 포지션이 종료되었을 경우 체결되지 않은 다른 한개의 주문이 그대로 Open Orders에 남아있게 됩니다. 
        • stoploss 주문이나 takeprofit 주문이 남아있게 된다면 해당 주문의 가격에 도달하게 될 경우 원하지 않는 포지션이 오픈될수도 있습니다.
      • 이와 같은 상황을 방지하기 위해서 두가지 방법이 존재합니다.
        • 주기적으로 주문 취소해주기
        • "reduceOnly" : True 입력인자를 추가해서 기존 포지션의 크기를 초과하여 새로운 포지션을 생성하지 않도록 설정해주기.
      • "reduceOnly" : True를 추가한 코드는 다음과 같습니다.
        • import ccxt
          import pprint
          
          
          def main():
              exchange = ccxt.binanceusdm(
                  {
                      "apiKey": "",
                      "secret": "",
                      "enableRateLimit": True,
                  }
              )
          
              symbol = "BTC/USDT"
              type = "MARKET"
              side = "buy"
              amount = 0.003  # USDT 기준이 아닌 해당 코인 개수 기준
              price = None
              stopLossPrice = 40000
              takeProfitPrice = 45000
          
              order = exchange.create_order(symbol, type, side, amount)
              pprint.pprint(order)
          
              inverted_side = "sell" if side == "buy" else "buy"
          
              stopLossParams = {"stopPrice": stopLossPrice, "reduceOnly": True}
              stopLossOrder = exchange.create_order(
                  symbol, "STOP_MARKET", inverted_side, amount, price, stopLossParams
              )
              pprint.pprint(stopLossOrder)
          
              takeProfitParams = {"stopPrice": takeProfitPrice, "reduceOnly": True}
              takeProfitOrder = exchange.create_order(
                  symbol, "TAKE_PROFIT_MARKET", inverted_side, amount, price, takeProfitParams
              )
              pprint.pprint(takeProfitOrder)
          
          
          main()
        • "reduceOnly" : True 를 추가해주게 되면 만약 stoploss가 실행되어 현재 보유중인 수량은 없고 takeprofit 주문이 남아 있을때, 시간이 지나 takeprofit 가격에 도달하더라도 새로운 포지션이 오픈되지 않습니다. (기존 코드는 이럴 경우 새로운 포지션이 오픈됩니다.) 반대의 경우도 마찬가지입니다.(takeprofit이 실행되어 현재 보유중인 수량이 없는데, 시간이 지나 stoploss 가격에 도달한 경우)
        • 따라서 기본적으로 "reduceOnly" : True 입력인자를 추가해주는 것이 안전합니다.
    • 참고 : 지정가 주문의 SL/TP
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            symbol = "BTC/USDT"
            type = "LIMIT"
            side = "buy"
            amount = 0.003  # USDT 기준이 아닌 해당 코인 개수 기준
            price = 42200
            stopLossPrice = 40000
            takeProfitPrice = 45000
        
            order = exchange.create_order(symbol, type, side, amount, price)
            pprint.pprint(order)
        
            inverted_side = "sell" if side == "buy" else "buy"
        
            stopLossParams = {"stopPrice": stopLossPrice, "reduceOnly": True}
            stopLossOrder = exchange.create_order(
                symbol, "STOP_MARKET", inverted_side, amount, price, stopLossParams
            )
            pprint.pprint(stopLossOrder)
        
            takeProfitParams = {"stopPrice": takeProfitPrice, "reduceOnly": True}
            takeProfitOrder = exchange.create_order(
                symbol, "TAKE_PROFIT_MARKET", inverted_side, amount, price, takeProfitParams
            )
            pprint.pprint(takeProfitOrder)
        
        
        main()

    주문 취소하기

    • 특정 코인의 체결되지 않은 주문을 취소하는 코드입니다. 
      • import ccxt
        import pprint
        
        
        def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            exchange.cancel_all_orders('BTC/USDT')
        
        
        main()

    비동기 코드

    • 지정가/ 시장가 주문
      • 지정가의 경우 아래 코드에서 type을 'LIMIT'으로 변경후 price를 지정해주면 됩니다.
      • import asyncio
        import pprint
        
        import ccxt.async_support as ccxt  # noqa: E402
        
        
        async def main():
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
            try:
                symbol = "BTC/USDT"
                type = "MARKET"
                side = "sell"
                amount = 0.003  # USDT 기준이 아닌 해당 코인 개수 기준
                price = None
        
                order = await exchange.create_order(symbol, type, side, amount, price)
                pprint.pprint(order)
        
            except ccxt.InsufficientFunds as e:
                print("create_order() failed – not enough funds")
                print(e)
            except Exception as e:
                print("create_order() failed")
                print(e)
            await exchange.close()
        
        
        asyncio.run(main())
        • 잔고가 부족할 경우 ccxt.InsufficientFunds 에러가 발생합니다.
        • await exchange.close()의 경우 서버가 종료될 때만 호출하면 됩니다.