863 lines
33 KiB
Python
863 lines
33 KiB
Python
# -*- 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
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Int, Market, 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 NotSupported
|
|
|
|
|
|
class exmo(ccxt.async_support.exmo):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(exmo, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchBalance': True,
|
|
'watchTicker': True,
|
|
'watchTickers': True,
|
|
'watchTrades': True,
|
|
'watchMyTrades': True,
|
|
'watchOrders': True,
|
|
'watchOrderBook': True,
|
|
'watchOHLCV': False,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'public': 'wss://ws-api.exmo.com:443/v1/public',
|
|
'spot': 'wss://ws-api.exmo.com:443/v1/private',
|
|
'margin': 'wss://ws-api.exmo.com:443/v1/margin/private',
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
},
|
|
'streaming': {
|
|
},
|
|
'exceptions': {
|
|
},
|
|
})
|
|
|
|
def request_id(self):
|
|
requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
|
|
self.options['requestId'] = requestId
|
|
return requestId
|
|
|
|
async def watch_balance(self, params={}) -> Balances:
|
|
"""
|
|
watch balance and get the amount of funds available for trading or funds locked in orders
|
|
: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.authenticate(params)
|
|
type, query = self.handle_market_type_and_params('watchBalance', None, params)
|
|
messageHash = 'balance:' + type
|
|
url = self.urls['api']['ws'][type]
|
|
subscribe: dict = {
|
|
'method': 'subscribe',
|
|
'topics': [type + '/wallet'],
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(subscribe, query)
|
|
return await self.watch(url, messageHash, request, messageHash, request)
|
|
|
|
def handle_balance(self, client: Client, message):
|
|
#
|
|
# spot
|
|
# {
|
|
# "ts": 1654208766007,
|
|
# "event": "snapshot",
|
|
# "topic": "spot/wallet",
|
|
# "data": {
|
|
# "balances": {
|
|
# "ADA": "0",
|
|
# "ALGO": "0",
|
|
# ...
|
|
# },
|
|
# "reserved": {
|
|
# "ADA": "0",
|
|
# "ALGO": "0",
|
|
# ...
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
# margin
|
|
# {
|
|
# "ts": 1624370076651,
|
|
# "event": "snapshot",
|
|
# "topic": "margin/wallets",
|
|
# "data": {
|
|
# "RUB": {
|
|
# "balance": "1000000",
|
|
# "used": "0",
|
|
# "free": "1000000"
|
|
# },
|
|
# "USD": {
|
|
# "balance": "1000000",
|
|
# "used": "1831.925",
|
|
# "free": "998168.075"
|
|
# }
|
|
# }
|
|
# }
|
|
# {
|
|
# "ts": 1624370185720,
|
|
# "event": "update",
|
|
# "topic": "margin/wallets",
|
|
# "data": {
|
|
# "USD": {
|
|
# "balance": "1000123",
|
|
# "used": "1831.925",
|
|
# "free": "998291.075"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
parts = topic.split('/')
|
|
type = self.safe_string(parts, 0)
|
|
if type == 'spot':
|
|
self.parse_spot_balance(message)
|
|
elif type == 'margin':
|
|
self.parse_margin_balance(message)
|
|
messageHash = 'balance:' + type
|
|
client.resolve(self.balance, messageHash)
|
|
|
|
def parse_spot_balance(self, message):
|
|
#
|
|
# {
|
|
# "balances": {
|
|
# "BTC": "3",
|
|
# "USD": "1000",
|
|
# "RUB": "0"
|
|
# },
|
|
# "reserved": {
|
|
# "BTC": "0.5",
|
|
# "DASH": "0",
|
|
# "RUB": "0"
|
|
# }
|
|
# }
|
|
#
|
|
event = self.safe_string(message, 'event')
|
|
data = self.safe_value(message, 'data')
|
|
self.balance['info'] = data
|
|
if event == 'snapshot':
|
|
balances = self.safe_value(data, 'balances', {})
|
|
reserved = self.safe_value(data, 'reserved', {})
|
|
currencies = list(balances.keys())
|
|
for i in range(0, len(currencies)):
|
|
currencyId = currencies[i]
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(balances, currencyId)
|
|
account['used'] = self.safe_string(reserved, currencyId)
|
|
self.balance[code] = account
|
|
elif event == 'update':
|
|
currencyId = self.safe_string(data, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(data, 'balance')
|
|
account['used'] = self.safe_string(data, 'reserved')
|
|
self.balance[code] = account
|
|
self.balance = self.safe_balance(self.balance)
|
|
|
|
def parse_margin_balance(self, message):
|
|
#
|
|
# {
|
|
# "RUB": {
|
|
# "balance": "1000000",
|
|
# "used": "0",
|
|
# "free": "1000000"
|
|
# },
|
|
# "USD": {
|
|
# "balance": "1000000",
|
|
# "used": "1831.925",
|
|
# "free": "998168.075"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data')
|
|
self.balance['info'] = data
|
|
currencies = list(data.keys())
|
|
for i in range(0, len(currencies)):
|
|
currencyId = currencies[i]
|
|
code = self.safe_currency_code(currencyId)
|
|
wallet = self.safe_value(data, currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(wallet, 'free')
|
|
account['used'] = self.safe_string(wallet, 'used')
|
|
account['total'] = self.safe_string(wallet, 'balance')
|
|
self.balance[code] = account
|
|
self.balance = self.safe_balance(self.balance)
|
|
|
|
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://documenter.getpostman.com/view/10287440/SzYXWKPi#fd8f47bc-8517-43c0-bb60-1d61a86d4471
|
|
|
|
:param str symbol: 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()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
url = self.urls['api']['ws']['public']
|
|
messageHash = 'ticker:' + symbol
|
|
message: dict = {
|
|
'method': 'subscribe',
|
|
'topics': [
|
|
'spot/ticker:' + market['id'],
|
|
],
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(message, params)
|
|
return await self.watch(url, messageHash, request, messageHash, request)
|
|
|
|
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://documenter.getpostman.com/view/10287440/SzYXWKPi#fd8f47bc-8517-43c0-bb60-1d61a86d4471
|
|
|
|
: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()
|
|
symbols = self.market_symbols(symbols, None, False)
|
|
messageHashes = []
|
|
args = []
|
|
for i in range(0, len(symbols)):
|
|
market = self.market(symbols[i])
|
|
messageHashes.append('ticker:' + market['symbol'])
|
|
args.append('spot/ticker:' + market['id'])
|
|
url = self.urls['api']['ws']['public']
|
|
message: dict = {
|
|
'method': 'subscribe',
|
|
'topics': args,
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(message, params)
|
|
await self.watch_multiple(url, messageHashes, request, messageHashes, request)
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# spot
|
|
# {
|
|
# "ts": 1654205085473,
|
|
# "event": "update",
|
|
# "topic": "spot/ticker:BTC_USDT",
|
|
# "data": {
|
|
# "buy_price": "30285.84",
|
|
# "sell_price": "30299.97",
|
|
# "last_trade": "30295.01",
|
|
# "high": "30386.7",
|
|
# "low": "29542.76",
|
|
# "avg": "29974.16178449",
|
|
# "vol": "118.79538518",
|
|
# "vol_curr": "3598907.38200826",
|
|
# "updated": 1654205084
|
|
# }
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
topicParts = topic.split(':')
|
|
marketId = self.safe_string(topicParts, 1)
|
|
symbol = self.safe_symbol(marketId)
|
|
ticker = self.safe_value(message, 'data', {})
|
|
market = self.safe_market(marketId)
|
|
parsedTicker = self.parse_ticker(ticker, market)
|
|
messageHash = 'ticker:' + symbol
|
|
self.tickers[symbol] = parsedTicker
|
|
client.resolve(parsedTicker, messageHash)
|
|
|
|
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
|
|
: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://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
url = self.urls['api']['ws']['public']
|
|
messageHash = 'trades:' + symbol
|
|
message: dict = {
|
|
'method': 'subscribe',
|
|
'topics': [
|
|
'spot/trades:' + market['id'],
|
|
],
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(message, params)
|
|
trades = await self.watch(url, messageHash, request, messageHash, request)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
def handle_trades(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "ts": 1654206084001,
|
|
# "event": "update",
|
|
# "topic": "spot/trades:BTC_USDT",
|
|
# "data": [{
|
|
# "trade_id": 389704729,
|
|
# "type": "sell",
|
|
# "price": "30310.95",
|
|
# "quantity": "0.0197",
|
|
# "amount": "597.125715",
|
|
# "date": 1654206083
|
|
# }]
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
parts = topic.split(':')
|
|
marketId = self.safe_string(parts, 1)
|
|
symbol = self.safe_symbol(marketId)
|
|
market = self.safe_market(marketId)
|
|
trades = self.safe_value(message, 'data', [])
|
|
messageHash = 'trades:' + symbol
|
|
stored = self.safe_value(self.trades, symbol)
|
|
if stored is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
stored = ArrayCache(limit)
|
|
self.trades[symbol] = stored
|
|
for i in range(0, len(trades)):
|
|
trade = trades[i]
|
|
parsed = self.parse_trade(trade, market)
|
|
stored.append(parsed)
|
|
self.trades[symbol] = stored
|
|
client.resolve(self.trades[symbol], messageHash)
|
|
|
|
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of trades associated with the user
|
|
: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://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
await self.authenticate(params)
|
|
type, query = self.handle_market_type_and_params('watchMyTrades', None, params)
|
|
url = self.urls['api']['ws'][type]
|
|
messageHash = None
|
|
if symbol is None:
|
|
messageHash = 'myTrades:' + type
|
|
else:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = 'myTrades:' + market['symbol']
|
|
message: dict = {
|
|
'method': 'subscribe',
|
|
'topics': [
|
|
type + '/user_trades',
|
|
],
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(message, query)
|
|
trades = await self.watch(url, messageHash, request, messageHash, request)
|
|
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
|
|
|
def handle_my_trades(self, client: Client, message):
|
|
#
|
|
# spot
|
|
# {
|
|
# "ts": 1654210290219,
|
|
# "event": "update",
|
|
# "topic": "spot/user_trades",
|
|
# "data": {
|
|
# "trade_id": 389715807,
|
|
# "type": "buy",
|
|
# "price": "30527.77",
|
|
# "quantity": "0.0001",
|
|
# "amount": "3.052777",
|
|
# "date": 1654210290,
|
|
# "order_id": 27352777112,
|
|
# "client_id": 0,
|
|
# "pair": "BTC_USDT",
|
|
# "exec_type": "taker",
|
|
# "commission_amount": "0.0000001",
|
|
# "commission_currency": "BTC",
|
|
# "commission_percent": "0.1"
|
|
# }
|
|
# }
|
|
#
|
|
# margin
|
|
# {
|
|
# "ts":1624369720168,
|
|
# "event":"snapshot",
|
|
# "topic":"margin/user_trades",
|
|
# "data":[
|
|
# {
|
|
# "trade_id":"692844278081167054",
|
|
# "trade_dt":"1624369773990729200",
|
|
# "type":"buy",
|
|
# "order_id":"692844278081167033",
|
|
# "pair":"BTC_USD",
|
|
# "quantity":"0.1",
|
|
# "price":"36638.5",
|
|
# "is_maker":false
|
|
# }
|
|
# ]
|
|
# }
|
|
# {
|
|
# "ts":1624370368612,
|
|
# "event":"update",
|
|
# "topic":"margin/user_trades",
|
|
# "data":{
|
|
# "trade_id":"692844278081167693",
|
|
# "trade_dt":"1624370368569092500",
|
|
# "type":"buy",
|
|
# "order_id":"692844278081167674",
|
|
# "pair":"BTC_USD",
|
|
# "quantity":"0.1",
|
|
# "price":"36638.5",
|
|
# "is_maker":false
|
|
# }
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
parts = topic.split('/')
|
|
type = self.safe_string(parts, 0)
|
|
messageHash = 'myTrades:' + type
|
|
event = self.safe_string(message, 'event')
|
|
rawTrades = []
|
|
myTrades = None
|
|
if self.myTrades is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
myTrades = ArrayCacheBySymbolById(limit)
|
|
self.myTrades = myTrades
|
|
else:
|
|
myTrades = self.myTrades
|
|
if event == 'snapshot':
|
|
rawTrades = self.safe_value(message, 'data', [])
|
|
elif event == 'update':
|
|
rawTrade = self.safe_value(message, 'data', {})
|
|
rawTrades = [rawTrade]
|
|
trades = self.parse_trades(rawTrades)
|
|
symbols: dict = {}
|
|
for j in range(0, len(trades)):
|
|
trade = trades[j]
|
|
myTrades.append(trade)
|
|
symbols[trade['symbol']] = True
|
|
symbolKeys = list(symbols.keys())
|
|
for i in range(0, len(symbolKeys)):
|
|
symbol = symbolKeys[i]
|
|
symbolSpecificMessageHash = 'myTrades:' + symbol
|
|
client.resolve(myTrades, symbolSpecificMessageHash)
|
|
client.resolve(myTrades, messageHash)
|
|
|
|
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
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://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
url = self.urls['api']['ws']['public']
|
|
messageHash = 'orderbook:' + symbol
|
|
params = self.omit(params, 'aggregation')
|
|
subscribe: dict = {
|
|
'method': 'subscribe',
|
|
'id': self.request_id(),
|
|
'topics': [
|
|
'spot/order_book_updates:' + market['id'],
|
|
],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
orderbook = await self.watch(url, messageHash, request, messageHash)
|
|
return orderbook.limit()
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "ts": 1574427585174,
|
|
# "event": "snapshot",
|
|
# "topic": "spot/order_book_updates:BTC_USD",
|
|
# "data": {
|
|
# "ask": [
|
|
# ["100", "3", "300"],
|
|
# ["200", "4", "800"]
|
|
# ],
|
|
# "bid": [
|
|
# ["99", "2", "198"],
|
|
# ["98", "1", "98"]
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# {
|
|
# "ts": 1574427585174,
|
|
# "event": "update",
|
|
# "topic": "spot/order_book_updates:BTC_USD",
|
|
# "data": {
|
|
# "ask": [
|
|
# ["100", "1", "100"],
|
|
# ["200", "2", "400"]
|
|
# ],
|
|
# "bid": [
|
|
# ["99", "1", "99"],
|
|
# ["98", "0", "0"]
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
parts = topic.split(':')
|
|
marketId = self.safe_string(parts, 1)
|
|
symbol = self.safe_symbol(marketId)
|
|
orderBook = self.safe_value(message, 'data', {})
|
|
messageHash = 'orderbook:' + symbol
|
|
timestamp = self.safe_integer(message, 'ts')
|
|
if not (symbol in self.orderbooks):
|
|
self.orderbooks[symbol] = self.order_book({})
|
|
orderbook = self.orderbooks[symbol]
|
|
event = self.safe_string(message, 'event')
|
|
if event == 'snapshot':
|
|
snapshot = self.parse_order_book(orderBook, symbol, timestamp, 'bid', 'ask')
|
|
orderbook.reset(snapshot)
|
|
else:
|
|
asks = self.safe_list(orderBook, 'ask', [])
|
|
bids = self.safe_list(orderBook, 'bid', [])
|
|
self.handle_deltas(orderbook['asks'], asks)
|
|
self.handle_deltas(orderbook['bids'], bids)
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_delta(self, bookside, delta):
|
|
bidAsk = self.parse_bid_ask(delta, 0, 1)
|
|
bookside.storeArray(bidAsk)
|
|
|
|
def handle_deltas(self, bookside, deltas):
|
|
for i in range(0, len(deltas)):
|
|
self.handle_delta(bookside, deltas[i])
|
|
|
|
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://documenter.getpostman.com/view/10287440/SzYXWKPi#85f7bc03-b1c9-4cd2-bd22-8fd422272825
|
|
https://documenter.getpostman.com/view/10287440/SzYXWKPi#95e4ed18-1791-4e6d-83ad-cbfe9be1051c
|
|
|
|
watches information on multiple orders made by the user
|
|
: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
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
await self.authenticate(params)
|
|
type, query = self.handle_market_type_and_params('watchOrders', None, params)
|
|
url = self.urls['api']['ws'][type]
|
|
messageHash = None
|
|
if symbol is None:
|
|
messageHash = 'orders:' + type
|
|
else:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = 'orders:' + market['symbol']
|
|
message: dict = {
|
|
'method': 'subscribe',
|
|
'topics': [
|
|
type + '/orders',
|
|
],
|
|
'id': self.request_id(),
|
|
}
|
|
request = self.deep_extend(message, query)
|
|
orders = await self.watch(url, messageHash, request, messageHash, request)
|
|
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
|
|
|
def handle_orders(self, client: Client, message):
|
|
#
|
|
# spot
|
|
# {
|
|
# "ts": 1574427585174,
|
|
# "event": "snapshot",
|
|
# "topic": "spot/orders",
|
|
# "data": [
|
|
# {
|
|
# "order_id": "14",
|
|
# "client_id":"100500",
|
|
# "created": "1574427585",
|
|
# "pair": "BTC_USD",
|
|
# "price": "7750",
|
|
# "quantity": "0.1",
|
|
# "amount": "775",
|
|
# "original_quantity": "0.1",
|
|
# "original_amount": "775",
|
|
# "type": "sell",
|
|
# "status": "open"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# margin
|
|
# {
|
|
# "ts":1624371281773,
|
|
# "event":"snapshot",
|
|
# "topic":"margin/orders",
|
|
# "data":[
|
|
# {
|
|
# "order_id":"692844278081168665",
|
|
# "created":"1624371250919761600",
|
|
# "type":"limit_buy",
|
|
# "previous_type":"limit_buy",
|
|
# "pair":"BTC_USD",
|
|
# "leverage":"2",
|
|
# "price":"10000",
|
|
# "stop_price":"0",
|
|
# "distance":"0",
|
|
# "trigger_price":"10000",
|
|
# "init_quantity":"0.1",
|
|
# "quantity":"0.1",
|
|
# "funding_currency":"USD",
|
|
# "funding_quantity":"1000",
|
|
# "funding_rate":"0",
|
|
# "client_id":"111111",
|
|
# "expire":0,
|
|
# "src":1,
|
|
# "comment":"comment1",
|
|
# "updated":1624371250938136600,
|
|
# "status":"active"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
topic = self.safe_string(message, 'topic')
|
|
parts = topic.split('/')
|
|
type = self.safe_string(parts, 0)
|
|
messageHash = 'orders:' + type
|
|
event = self.safe_string(message, 'event')
|
|
if self.orders is None:
|
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
self.orders = ArrayCacheBySymbolById(limit)
|
|
cachedOrders = self.orders
|
|
rawOrders = []
|
|
if event == 'snapshot':
|
|
rawOrders = self.safe_value(message, 'data', [])
|
|
elif event == 'update':
|
|
rawOrder = self.safe_dict(message, 'data', {})
|
|
rawOrders.append(rawOrder)
|
|
symbols: dict = {}
|
|
for j in range(0, len(rawOrders)):
|
|
order = self.parse_ws_order(rawOrders[j])
|
|
cachedOrders.append(order)
|
|
symbols[order['symbol']] = True
|
|
symbolKeys = list(symbols.keys())
|
|
for i in range(0, len(symbolKeys)):
|
|
symbol = symbolKeys[i]
|
|
symbolSpecificMessageHash = 'orders:' + symbol
|
|
client.resolve(cachedOrders, symbolSpecificMessageHash)
|
|
client.resolve(cachedOrders, messageHash)
|
|
|
|
def parse_ws_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# order_id: '43226756791',
|
|
# client_id: 0,
|
|
# created: '1730371416',
|
|
# type: 'market_buy',
|
|
# pair: 'TRX_USD',
|
|
# quantity: '0',
|
|
# original_quantity: '30',
|
|
# status: 'cancelled',
|
|
# last_trade_id: '726480870',
|
|
# last_trade_price: '0.17',
|
|
# last_trade_quantity: '30'
|
|
# }
|
|
#
|
|
id = self.safe_string(order, 'order_id')
|
|
timestamp = self.safe_timestamp(order, 'created')
|
|
orderType = self.safe_string(order, 'type')
|
|
side = self.parseSide(orderType)
|
|
marketId = self.safe_string(order, 'pair')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
amount = self.safe_string(order, 'quantity')
|
|
if amount is None:
|
|
amountField = 'in_amount' if (side == 'buy') else 'out_amount'
|
|
amount = self.safe_string(order, amountField)
|
|
price = self.safe_string(order, 'price')
|
|
clientOrderId = self.omit_zero(self.safe_string(order, 'client_id'))
|
|
triggerPrice = self.omit_zero(self.safe_string(order, 'stop_price'))
|
|
type = None
|
|
if (orderType != 'buy') and (orderType != 'sell'):
|
|
type = orderType
|
|
trades = None
|
|
if 'last_trade_id' in order:
|
|
trade = self.parse_ws_trade(order, market)
|
|
trades = [trade]
|
|
return self.safe_order({
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'datetime': self.iso8601(timestamp),
|
|
'timestamp': timestamp,
|
|
'lastTradeTimestamp': None,
|
|
'status': self.parseStatus(self.safe_string(order, 'status')),
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': None,
|
|
'postOnly': None,
|
|
'side': side,
|
|
'price': price,
|
|
'stopPrice': triggerPrice,
|
|
'triggerPrice': triggerPrice,
|
|
'cost': None,
|
|
'amount': self.safe_string(order, 'original_quantity'),
|
|
'filled': None,
|
|
'remaining': self.safe_string(order, 'quantity'),
|
|
'average': None,
|
|
'trades': trades,
|
|
'fee': None,
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_ws_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
id = self.safe_string(trade, 'order_id')
|
|
orderType = self.safe_string(trade, 'type')
|
|
side = self.parseSide(orderType)
|
|
marketId = self.safe_string(trade, 'pair')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
type = None
|
|
if (orderType != 'buy') and (orderType != 'sell'):
|
|
type = orderType
|
|
return self.safe_trade({
|
|
'id': self.safe_string(trade, 'last_trade_id'),
|
|
'symbol': symbol,
|
|
'order': id,
|
|
'type': type,
|
|
'side': side,
|
|
'price': self.safe_string(trade, 'last_trade_price'),
|
|
'amount': self.safe_string(trade, 'last_trade_quantity'),
|
|
'cost': None,
|
|
'fee': None,
|
|
}, market)
|
|
|
|
def handle_message(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "ts": 1654206362552,
|
|
# "event": "info",
|
|
# "code": 1,
|
|
# "message": "connection established",
|
|
# "session_id": "7548931b-c2a4-45dd-8d71-877881a7251a"
|
|
# }
|
|
#
|
|
# {
|
|
# "ts": 1654206491399,
|
|
# "event": "subscribed",
|
|
# "id": 1,
|
|
# "topic": "spot/ticker:BTC_USDT"
|
|
# }
|
|
event = self.safe_string(message, 'event')
|
|
events: dict = {
|
|
'logged_in': self.handle_authentication_message,
|
|
'info': self.handle_info,
|
|
'subscribed': self.handle_subscribed,
|
|
}
|
|
eventHandler = self.safe_value(events, event)
|
|
if eventHandler is not None:
|
|
eventHandler(client, message)
|
|
return
|
|
if (event == 'update') or (event == 'snapshot'):
|
|
topic = self.safe_string(message, 'topic')
|
|
if topic is not None:
|
|
parts = topic.split(':')
|
|
channel = self.safe_string(parts, 0)
|
|
handlers: dict = {
|
|
'spot/ticker': self.handle_ticker,
|
|
'spot/wallet': self.handle_balance,
|
|
'margin/wallet': self.handle_balance,
|
|
'margin/wallets': self.handle_balance,
|
|
'spot/trades': self.handle_trades,
|
|
'margin/trades': self.handle_trades,
|
|
'spot/order_book_updates': self.handle_order_book,
|
|
'spot/orders': self.handle_orders,
|
|
'margin/orders': self.handle_orders,
|
|
'spot/user_trades': self.handle_my_trades,
|
|
'margin/user_trades': self.handle_my_trades,
|
|
}
|
|
handler = self.safe_value(handlers, channel)
|
|
if handler is not None:
|
|
handler(client, message)
|
|
return
|
|
raise NotSupported(self.id + ' received an unsupported message: ' + self.json(message))
|
|
|
|
def handle_subscribed(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "method": "subscribe",
|
|
# "id": 2,
|
|
# "topics": ["spot/orders"]
|
|
# }
|
|
#
|
|
return message
|
|
|
|
def handle_info(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "ts": 1654215731659,
|
|
# "event": "info",
|
|
# "code": 1,
|
|
# "message": "connection established",
|
|
# "session_id": "4c496262-e259-4c27-b805-f20b46209c17"
|
|
# }
|
|
#
|
|
return message
|
|
|
|
def handle_authentication_message(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "method": "login",
|
|
# "id": 1,
|
|
# "api_key": "K-************************",
|
|
# "sign": "******************************************************************",
|
|
# "nonce": 1654215729887
|
|
# }
|
|
#
|
|
messageHash = 'authenticated'
|
|
client.resolve(message, messageHash)
|
|
|
|
async def authenticate(self, params={}):
|
|
messageHash = 'authenticated'
|
|
type, query = self.handle_market_type_and_params('authenticate', None, params)
|
|
url = self.urls['api']['ws'][type]
|
|
client = self.client(url)
|
|
future = self.safe_value(client.subscriptions, messageHash)
|
|
if future is None:
|
|
time = self.milliseconds()
|
|
self.check_required_credentials()
|
|
requestId = self.request_id()
|
|
signData = self.apiKey + str(time)
|
|
sign = self.hmac(self.encode(signData), self.encode(self.secret), hashlib.sha512, 'base64')
|
|
request: dict = {
|
|
'method': 'login',
|
|
'id': requestId,
|
|
'api_key': self.apiKey,
|
|
'sign': sign,
|
|
'nonce': time,
|
|
}
|
|
message = self.extend(request, query)
|
|
future = await self.watch(url, messageHash, message, messageHash)
|
|
client.subscriptions[messageHash] = future
|
|
return future
|