Files
ccxt_with_mt5/ccxt/pro/bithumb.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

623 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
from ccxt.base.types import Any, Balances, Bool, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
class bithumb(ccxt.async_support.bithumb):
def describe(self) -> Any:
return self.deep_extend(super(bithumb, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchOrders': True,
'watchTicker': True,
'watchTickers': True,
'watchTrades': True,
'watchOrderBook': True,
'watchOHLCV': False,
},
'urls': {
'api': {
'ws': {
'public': 'wss://pubwss.bithumb.com/pub/ws', # v1.2.0
'publicV2': 'wss://ws-api.bithumb.com/websocket/v1', # v2.1.5
'privateV2': 'wss://ws-api.bithumb.com/websocket/v1/private', # v2.1.5
},
},
},
'options': {},
'streaming': {},
'exceptions': {},
})
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
:returns dict: a `ticker structure <https://github.com/ccxt/ccxt/wiki/Manual#ticker-structure>`
"""
url = self.urls['api']['ws']['public']
await self.load_markets()
market = self.market(symbol)
messageHash = 'ticker:' + market['symbol']
request: dict = {
'type': 'ticker',
'symbols': [market['base'] + '_' + market['quote']],
'tickTypes': [self.safe_string(params, 'tickTypes', '24H')],
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash)
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
:param str[] symbols: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
url = self.urls['api']['ws']['public']
marketIds = []
messageHashes = []
symbols = self.market_symbols(symbols, None, False, True, True)
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
marketIds.append(market['base'] + '_' + market['quote'])
messageHashes.append('ticker:' + market['symbol'])
request: dict = {
'type': 'ticker',
'symbols': marketIds,
'tickTypes': [self.safe_string(params, 'tickTypes', '24H')],
}
message = self.extend(request, params)
newTicker = await self.watch_multiple(url, messageHashes, message, messageHashes)
if self.newUpdates:
result: dict = {}
result[newTicker['symbol']] = newTicker
return result
return self.filter_by_array(self.tickers, 'symbol', symbols)
def handle_ticker(self, client: Client, message):
#
# {
# "type" : "ticker",
# "content" : {
# "symbol" : "BTC_KRW", # 통화코드
# "tickType" : "24H", # 변동 기준시간- 30M, 1H, 12H, 24H, MID
# "date" : "20200129", # 일자
# "time" : "121844", # 시간
# "openPrice" : "2302", # 시가
# "closePrice" : "2317", # 종가
# "lowPrice" : "2272", # 저가
# "highPrice" : "2344", # 고가
# "value" : "2831915078.07065789", # 누적거래금액
# "volume" : "1222314.51355788", # 누적거래량
# "sellVolume" : "760129.34079004", # 매도누적거래량
# "buyVolume" : "462185.17276784", # 매수누적거래량
# "prevClosePrice" : "2326", # 전일종가
# "chgRate" : "0.65", # 변동률
# "chgAmt" : "15", # 변동금액
# "volumePower" : "60.80" # 체결강도
# }
# }
#
content = self.safe_dict(message, 'content', {})
marketId = self.safe_string(content, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
ticker = self.parse_ws_ticker(content)
messageHash = 'ticker:' + symbol
self.tickers[symbol] = ticker
client.resolve(self.tickers[symbol], messageHash)
def parse_ws_ticker(self, ticker, market=None):
#
# {
# "symbol" : "BTC_KRW", # 통화코드
# "tickType" : "24H", # 변동 기준시간- 30M, 1H, 12H, 24H, MID
# "date" : "20200129", # 일자
# "time" : "121844", # 시간
# "openPrice" : "2302", # 시가
# "closePrice" : "2317", # 종가
# "lowPrice" : "2272", # 저가
# "highPrice" : "2344", # 고가
# "value" : "2831915078.07065789", # 누적거래금액
# "volume" : "1222314.51355788", # 누적거래량
# "sellVolume" : "760129.34079004", # 매도누적거래량
# "buyVolume" : "462185.17276784", # 매수누적거래량
# "prevClosePrice" : "2326", # 전일종가
# "chgRate" : "0.65", # 변동률
# "chgAmt" : "15", # 변동금액
# "volumePower" : "60.80" # 체결강도
# }
#
date = self.safe_string(ticker, 'date', '')
time = self.safe_string(ticker, 'time', '')
datetime = date[0:4] + '-' + date[4:6] + '-' + date[6:8] + 'T' + time[0:2] + ':' + time[2:4] + ':' + time[4:6]
marketId = self.safe_string(ticker, 'symbol')
return self.safe_ticker({
'symbol': self.safe_symbol(marketId, market, '_'),
'timestamp': self.parse8601(datetime),
'datetime': datetime,
'high': self.safe_string(ticker, 'highPrice'),
'low': self.safe_string(ticker, 'lowPrice'),
'bid': None,
'bidVolume': self.safe_string(ticker, 'buyVolume'),
'ask': None,
'askVolume': self.safe_string(ticker, 'sellVolume'),
'vwap': None,
'open': self.safe_string(ticker, 'openPrice'),
'close': self.safe_string(ticker, 'closePrice'),
'last': None,
'previousClose': self.safe_string(ticker, 'prevClosePrice'),
'change': self.safe_string(ticker, 'chgAmt'),
'percentage': self.safe_string(ticker, 'chgRate'),
'average': None,
'baseVolume': self.safe_string(ticker, 'volume'),
'quoteVolume': self.safe_string(ticker, 'value'),
'info': ticker,
}, market)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure>` indexed by market symbols
"""
await self.load_markets()
url = self.urls['api']['ws']['public']
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orderbook' + ':' + symbol
request: dict = {
'type': 'orderbookdepth',
'symbols': [market['base'] + '_' + market['quote']],
}
orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# {
# "type" : "orderbookdepth",
# "content" : {
# "list" : [
# {
# "symbol" : "BTC_KRW",
# "orderType" : "ask", # 주문타입 bid / ask
# "price" : "10593000", # 호가
# "quantity" : "1.11223318", # 잔량
# "total" : "3" # 건수
# },
# {"symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10596000", "quantity" : "0.5495", "total" : "8"},
# {"symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10598000", "quantity" : "18.2085", "total" : "10"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10532000", "quantity" : "0", "total" : "0"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10572000", "quantity" : "2.3324", "total" : "4"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10571000", "quantity" : "1.469", "total" : "3"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10569000", "quantity" : "0.5152", "total" : "2"}
# ],
# "datetime":1580268255864325 # 일시
# }
# }
#
content = self.safe_dict(message, 'content', {})
list = self.safe_list(content, 'list', [])
first = self.safe_dict(list, 0, {})
marketId = self.safe_string(first, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
timestampStr = self.safe_string(content, 'datetime')
timestamp = self.parse_to_int(timestampStr[0:13])
if not (symbol in self.orderbooks):
ob = self.order_book()
ob['symbol'] = symbol
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
self.handle_deltas(orderbook, list)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
messageHash = 'orderbook' + ':' + symbol
client.resolve(orderbook, messageHash)
def handle_delta(self, orderbook, delta):
#
# {
# symbol: "ETH_BTC",
# orderType: "bid",
# price: "0.07349517",
# quantity: "0",
# total: "0",
# }
#
sideId = self.safe_string(delta, 'orderType')
side = 'bids' if (sideId == 'bid') else 'asks'
bidAsk = self.parse_bid_ask(delta, 'price', 'quantity')
orderbookSide = orderbook[side]
orderbookSide.storeArray(bidAsk)
def handle_deltas(self, orderbook, deltas):
for i in range(0, len(deltas)):
self.handle_delta(orderbook, deltas[i])
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
:param str symbol: unified symbol of the market to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://github.com/ccxt/ccxt/wiki/Manual#public-trades>`
"""
await self.load_markets()
url = self.urls['api']['ws']['public']
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'trade:' + symbol
request: dict = {
'type': 'transaction',
'symbols': [market['base'] + '_' + market['quote']],
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client, message):
#
# {
# "type" : "transaction",
# "content" : {
# "list" : [
# {
# "symbol" : "BTC_KRW",
# "buySellGb" : "1",
# "contPrice" : "10579000",
# "contQty" : "0.01",
# "contAmt" : "105790.00",
# "contDtm" : "2020-01-29 12:24:18.830039",
# "updn" : "dn"
# }
# ]
# }
# }
#
content = self.safe_dict(message, 'content', {})
rawTrades = self.safe_list(content, 'list', [])
for i in range(0, len(rawTrades)):
rawTrade = rawTrades[i]
marketId = self.safe_string(rawTrade, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
if not (symbol in self.trades):
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
stored = ArrayCache(limit)
self.trades[symbol] = stored
trades = self.trades[symbol]
parsed = self.parse_ws_trade(rawTrade)
trades.append(parsed)
messageHash = 'trade' + ':' + symbol
client.resolve(trades, messageHash)
def parse_ws_trade(self, trade, market=None):
#
# {
# "symbol" : "BTC_KRW",
# "buySellGb" : "1",
# "contPrice" : "10579000",
# "contQty" : "0.01",
# "contAmt" : "105790.00",
# "contDtm" : "2020-01-29 12:24:18.830038",
# "updn" : "dn"
# }
#
marketId = self.safe_string(trade, 'symbol')
datetime = self.safe_string(trade, 'contDtm')
# that date is not UTC iso8601, but exchange's local time, -9hr difference
timestamp = self.parse8601(datetime) - 32400000
sideId = self.safe_string(trade, 'buySellGb')
return self.safe_trade({
'id': None,
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': self.safe_symbol(marketId, market, '_'),
'order': None,
'type': None,
'side': 'buy' if (sideId == '1') else 'sell',
'takerOrMaker': None,
'price': self.safe_string(trade, 'contPrice'),
'amount': self.safe_string(trade, 'contQty'),
'cost': self.safe_string(trade, 'contAmt'),
'fee': None,
}, market)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# "status" : "5100",
# "resmsg" : "Invalid Filter Syntax"
# }
#
if not ('status' in message):
return True
errorCode = self.safe_string(message, 'status')
try:
if errorCode != '0000':
msg = self.safe_string(message, 'resmsg')
raise ExchangeError(self.id + ' ' + msg)
return True
except Exception as e:
client.reject(e)
return True
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://apidocs.bithumb.com/v2.1.5/reference/%EB%82%B4-%EC%9E%90%EC%82%B0-myasset
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.load_markets()
await self.authenticate()
url = self.urls['api']['ws']['privateV2']
messageHash = 'myAsset'
request = [
{'ticket': 'ccxt'},
{'type': messageHash},
]
balance = await self.watch(url, messageHash, request, messageHash)
return balance
def handle_balance(self, client: Client, message):
#
# {
# "type": "myAsset",
# "assets": [
# {
# "currency": "KRW",
# "balance": "2061832.35",
# "locked": "3824127.3"
# }
# ],
# "asset_timestamp": 1727052537592,
# "timestamp": 1727052537687,
# "stream_type": "REALTIME"
# }
#
messageHash = 'myAsset'
assets = self.safe_list(message, 'assets', [])
if self.balance is None:
self.balance = {}
for i in range(0, len(assets)):
asset = assets[i]
currencyId = self.safe_string(asset, 'currency')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string(asset, 'balance')
account['used'] = self.safe_string(asset, 'locked')
self.balance[code] = account
self.balance['info'] = message
timestamp = self.safe_integer(message, 'timestamp')
self.balance['timestamp'] = timestamp
self.balance['datetime'] = self.iso8601(timestamp)
self.balance = self.safe_balance(self.balance)
client.resolve(self.balance, messageHash)
async def authenticate(self, params={}):
self.check_required_credentials()
wsOptions: dict = self.safe_dict(self.options, 'ws', {})
authenticated = self.safe_string(wsOptions, 'token')
if authenticated is None:
payload: dict = {
'access_key': self.apiKey,
'nonce': self.uuid(),
'timestamp': self.milliseconds(),
}
jwtToken = self.jwt(payload, self.encode(self.secret), 'sha256')
wsOptions['token'] = jwtToken
wsOptions['options'] = {
'headers': {
'authorization': 'Bearer ' + jwtToken,
},
}
self.options['ws'] = wsOptions
url = self.urls['api']['ws']['privateV2']
client = self.client(url)
return client
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
watches information on multiple orders made by the user
https://apidocs.bithumb.com/v2.1.5/reference/%EB%82%B4-%EC%A3%BC%EB%AC%B8-%EB%B0%8F-%EC%B2%B4%EA%B2%B0-myorder
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str[] [params.codes]: market codes to filter orders
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.authenticate()
url = self.urls['api']['ws']['privateV2']
messageHash = 'myOrder'
codes = self.safe_list(params, 'codes', [])
request = [
{'ticket': 'ccxt'},
{'type': messageHash, 'codes': codes},
]
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash = messageHash + ':' + symbol
orders = await self.watch(url, messageHash, request, messageHash)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_orders(self, client: Client, message):
#
# {
# "type": "myOrder",
# "code": "KRW-BTC",
# "uuid": "C0101000000001818113",
# "ask_bid": "BID",
# "order_type": "limit",
# "state": "trade",
# "trade_uuid": "C0101000000001744207",
# "price": 1927000,
# "volume": 0.4697,
# "remaining_volume": 0.0803,
# "executed_volume": 0.4697,
# "trades_count": 1,
# "reserved_fee": 0,
# "remaining_fee": 0,
# "paid_fee": 0,
# "executed_funds": 905111.9000,
# "trade_timestamp": 1727052318148,
# "order_timestamp": 1727052318074,
# "timestamp": 1727052318369,
# "stream_type": "REALTIME"
# }
#
messageHash = 'myOrder'
parsed = self.parse_ws_order(message)
symbol = self.safe_string(parsed, 'symbol')
# orderId = self.safe_string(parsed, 'id')
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
cachedOrders = self.orders
cachedOrders.append(parsed)
client.resolve(cachedOrders, messageHash)
symbolSpecificMessageHash = messageHash + ':' + symbol
client.resolve(cachedOrders, symbolSpecificMessageHash)
def parse_ws_order(self, order, market=None):
#
# {
# "type": "myOrder",
# "code": "KRW-BTC",
# "uuid": "C0101000000001818113",
# "ask_bid": "BID",
# "order_type": "limit",
# "state": "trade",
# "trade_uuid": "C0101000000001744207",
# "price": 1927000,
# "volume": 0.4697,
# "remaining_volume": 0.0803,
# "executed_volume": 0.4697,
# "trades_count": 1,
# "reserved_fee": 0,
# "remaining_fee": 0,
# "paid_fee": 0,
# "executed_funds": 905111.9000,
# "trade_timestamp": 1727052318148,
# "order_timestamp": 1727052318074,
# "timestamp": 1727052318369,
# "stream_type": "REALTIME"
# }
#
marketId = self.safe_string(order, 'code')
symbol = self.safe_symbol(marketId, market, '-')
timestamp = self.safe_integer(order, 'order_timestamp')
sideId = self.safe_string(order, 'ask_bid')
side = ('buy') if (sideId == 'BID') else ('sell')
typeId = self.safe_string(order, 'order_type')
type = None
if typeId == 'limit':
type = 'limit'
elif typeId == 'price':
type = 'market'
elif typeId == 'market':
type = 'market'
stateId = self.safe_string(order, 'state')
status = None
if stateId == 'wait':
status = 'open'
elif stateId == 'trade':
status = 'open'
elif stateId == 'done':
status = 'closed'
elif stateId == 'cancel':
status = 'canceled'
price = self.safe_string(order, 'price')
amount = self.safe_string(order, 'volume')
remaining = self.safe_string(order, 'remaining_volume')
filled = self.safe_string(order, 'executed_volume')
cost = self.safe_string(order, 'executed_funds')
feeCost = self.safe_string(order, 'paid_fee')
fee = None
if feeCost is not None:
marketForFee = self.safe_market(marketId, market)
feeCurrency = self.safe_string(marketForFee, 'quote')
fee = {
'cost': feeCost,
'currency': feeCurrency,
}
return self.safe_order({
'info': order,
'id': self.safe_string(order, 'uuid'),
'clientOrderId': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': self.safe_integer(order, 'trade_timestamp'),
'symbol': symbol,
'type': type,
'timeInForce': None,
'postOnly': None,
'side': side,
'price': price,
'stopPrice': None,
'triggerPrice': None,
'amount': amount,
'cost': cost,
'average': None,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': fee,
'trades': None,
}, market)
def handle_message(self, client: Client, message):
if not self.handle_error_message(client, message):
return
topic = self.safe_string(message, 'type')
if topic is not None:
methods: dict = {
'ticker': self.handle_ticker,
'orderbookdepth': self.handle_order_book,
'transaction': self.handle_trades,
'myAsset': self.handle_balance,
'myOrder': self.handle_orders,
}
method = self.safe_value(methods, topic)
if method is not None:
method(client, message)