1167 lines
47 KiB
Python
1167 lines
47 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, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
|
|
from ccxt.base.types import Any, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Trade
|
|
from ccxt.async_support.base.ws.client import Client
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import BadRequest
|
|
|
|
|
|
class deepcoin(ccxt.async_support.deepcoin):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(deepcoin, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchTicker': True,
|
|
'watchMarkPrice': False,
|
|
'watchMarkPrices': False,
|
|
'watchTickers': False,
|
|
'watchBidsAsks': False,
|
|
'watchOrderBook': True,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': False,
|
|
'watchOrderBookForSymbols': False,
|
|
'watchBalance': False,
|
|
'watchLiquidations': False,
|
|
'watchLiquidationsForSymbols': False,
|
|
'watchMyLiquidations': False,
|
|
'watchMyLiquidationsForSymbols': False,
|
|
'watchOHLCV': True,
|
|
'watchOHLCVForSymbols': False,
|
|
'watchOrders': True,
|
|
'watchMyTrades': True,
|
|
'watchPositions': True,
|
|
'watchFundingRate': False,
|
|
'watchFundingRates': False,
|
|
'createOrderWs': False,
|
|
'editOrderWs': False,
|
|
'cancelOrderWs': False,
|
|
'cancelOrdersWs': False,
|
|
'cancelAllOrdersWs': False,
|
|
'unWatchTicker': True,
|
|
'unWatchTrades': True,
|
|
'unWatchOHLCV': True,
|
|
'unWatchOrderBook': True,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'public': {
|
|
'spot': 'wss://stream.deepcoin.com/streamlet/trade/public/spot?platform=api',
|
|
'swap': 'wss://stream.deepcoin.com/streamlet/trade/public/swap?platform=api',
|
|
},
|
|
'private': 'wss://stream.deepcoin.com/v1/private',
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'lastRequestId': None,
|
|
'listenKey': None,
|
|
'listenKeyExpiryTimestamp': None,
|
|
'authenticate': {
|
|
'method': 'privateGetDeepcoinListenkeyExtend', # refresh existing listen key or 'privateGetDeepcoinListenkeyAcquire' - get a new one
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'4h': '4h',
|
|
'12h': '12h',
|
|
'1d': '1d',
|
|
'1w': '1w',
|
|
'1M': '1o',
|
|
'1y': '1y',
|
|
},
|
|
},
|
|
'streaming': {
|
|
'ping': self.ping,
|
|
},
|
|
})
|
|
|
|
def ping(self, client: Client):
|
|
url = client.url
|
|
if url.find('private') >= 0:
|
|
client.lastPong = self.milliseconds()
|
|
# prevent automatic disconnects on private channel
|
|
return 'ping'
|
|
|
|
def handle_pong(self, client: Client, message):
|
|
client.lastPong = self.milliseconds()
|
|
return message
|
|
|
|
def request_id(self):
|
|
previousValue = self.safe_integer(self.options, 'lastRequestId', 0)
|
|
newValue = self.sum(previousValue, 1)
|
|
self.options['lastRequestId'] = newValue
|
|
return newValue
|
|
|
|
def create_public_request(self, market: Market, requestId: float, topicID: str, suffix: str = '', unWatch: bool = False):
|
|
marketId = market['symbol'] # spot markets use symbol with slash
|
|
if market['type'] == 'swap':
|
|
marketId = market['baseId'] + market['quoteId'] # swap markets use symbol without slash
|
|
action = '1' # subscribe
|
|
if unWatch:
|
|
action = '0' # unsubscribe
|
|
request = {
|
|
'sendTopicAction': {
|
|
'Action': action,
|
|
'FilterValue': 'DeepCoin_' + marketId + suffix,
|
|
'LocalNo': requestId,
|
|
'ResumeNo': -1, # -1 from the end, 0 from the beginning
|
|
'TopicID': topicID,
|
|
},
|
|
}
|
|
return request
|
|
|
|
async def watch_public(self, market: Market, messageHash: str, topicID: str, params: dict = {}, suffix: str = '') -> Any:
|
|
url = self.urls['api']['ws']['public'][market['type']]
|
|
requestId = self.request_id()
|
|
request = self.create_public_request(market, requestId, topicID, suffix)
|
|
subscription = {
|
|
'subHash': messageHash,
|
|
'id': requestId,
|
|
}
|
|
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash, subscription)
|
|
|
|
async def un_watch_public(self, market: Market, messageHash: str, topicID: str, params: dict = {}, subscription: dict = {}, suffix: str = '') -> Any:
|
|
url = self.urls['api']['ws']['public'][market['type']]
|
|
requestId = self.request_id()
|
|
client = self.client(url)
|
|
existingSubscription = self.safe_dict(client.subscriptions, messageHash)
|
|
if existingSubscription is None:
|
|
raise BadRequest(self.id + ' no subscription for ' + messageHash)
|
|
subId = self.safe_integer(existingSubscription, 'id')
|
|
request = self.create_public_request(market, subId, topicID, suffix, True) # unsubscribe message uses the same id original subscribe message
|
|
unsubHash = 'unsubscribe::' + messageHash
|
|
subscription = self.extend(subscription, {
|
|
'subHash': messageHash,
|
|
'unsubHash': unsubHash,
|
|
'symbols': [market['symbol']],
|
|
'id': requestId,
|
|
})
|
|
return await self.watch(url, unsubHash, self.deep_extend(request, params), unsubHash, subscription)
|
|
|
|
async def watch_private(self, messageHash: str, params: dict = {}) -> Any:
|
|
listenKey = await self.authenticate()
|
|
url = self.urls['api']['ws']['private'] + '?listenKey=' + listenKey
|
|
return await self.watch(url, messageHash, None, 'private', params)
|
|
|
|
async def authenticate(self, params={}):
|
|
self.check_required_credentials()
|
|
time = self.milliseconds()
|
|
listenKeyExpiryTimestamp = self.safe_integer(self.options, 'listenKeyExpiryTimestamp', time)
|
|
expired = (time - listenKeyExpiryTimestamp) > 60000 # 1 minute before expiry
|
|
listenKey = self.safe_string(self.options, 'listenKey')
|
|
response = None
|
|
if listenKey is None:
|
|
response = await self.privateGetDeepcoinListenkeyAcquire(params)
|
|
elif expired:
|
|
method = self.safe_string(self.options, 'method', 'privateGetDeepcoinListenkeyExtend')
|
|
getNewKey = (method == 'privateGetDeepcoinListenkeyAcquire')
|
|
if getNewKey:
|
|
response = await self.privateGetDeepcoinListenkeyAcquire(params)
|
|
else:
|
|
request: dict = {
|
|
'listenkey': listenKey,
|
|
}
|
|
response = await self.privateGetDeepcoinListenkeyExtend(self.extend(request, params))
|
|
if response is not None:
|
|
data = self.safe_dict(response, 'data', {})
|
|
listenKey = self.safe_string(data, 'listenkey')
|
|
listenKeyExpiryTimestamp = self.safe_timestamp(data, 'expire_time')
|
|
self.options['listenKey'] = listenKey
|
|
self.options['listenKeyExpiryTimestamp'] = listenKeyExpiryTimestamp
|
|
return listenKey
|
|
|
|
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://www.deepcoin.com/docs/publicWS/latestMarketData
|
|
|
|
: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)
|
|
messageHash = 'ticker' + '::' + market['symbol']
|
|
return await self.watch_public(market, messageHash, '7', params)
|
|
|
|
async def un_watch_ticker(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://www.deepcoin.com/docs/publicWS/latestMarketData
|
|
|
|
: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)
|
|
messageHash = 'ticker' + '::' + market['symbol']
|
|
subscription = {
|
|
'topic': 'ticker',
|
|
}
|
|
return await self.un_watch_public(market, messageHash, '7', params, subscription)
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# a: 'PO',
|
|
# m: 'Success',
|
|
# tt: 1760913034780,
|
|
# mt: 1760913034780,
|
|
# r: [
|
|
# {
|
|
# d: {
|
|
# I: 'BTC/USDT',
|
|
# U: 1760913034742,
|
|
# PF: 0,
|
|
# E: 0,
|
|
# O: 108479.9,
|
|
# H: 109449.9,
|
|
# L: 108238,
|
|
# V: 789.3424915,
|
|
# T: 43003872.3705223,
|
|
# N: 109345,
|
|
# M: 87294.7,
|
|
# D: 0,
|
|
# V2: 3086.4496105,
|
|
# T2: 332811624.339836,
|
|
# F: 0,
|
|
# C: 0,
|
|
# BP1: 109344.9,
|
|
# AP1: 109345.2
|
|
# }
|
|
# }
|
|
# ]
|
|
#
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
parsedTicker = self.parse_ws_ticker(data, market)
|
|
messageHash = 'ticker' + '::' + symbol
|
|
self.tickers[symbol] = parsedTicker
|
|
client.resolve(parsedTicker, messageHash)
|
|
|
|
def parse_ws_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# I: 'BTC/USDT',
|
|
# U: 1760913034742,
|
|
# PF: 0,
|
|
# E: 0,
|
|
# O: 108479.9,
|
|
# H: 109449.9,
|
|
# L: 108238,
|
|
# V: 789.3424915,
|
|
# T: 43003872.3705223,
|
|
# N: 109345,
|
|
# M: 87294.7,
|
|
# D: 0,
|
|
# V2: 3086.4496105,
|
|
# T2: 332811624.339836,
|
|
# F: 0,
|
|
# C: 0,
|
|
# BP1: 109344.9,
|
|
# AP1: 109345.2
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(ticker, 'U')
|
|
high = self.safe_number(ticker, 'H')
|
|
low = self.safe_number(ticker, 'L')
|
|
open = self.safe_number(ticker, 'O')
|
|
last = self.safe_number(ticker, 'N')
|
|
bid = self.safe_number(ticker, 'BP1')
|
|
ask = self.safe_number(ticker, 'AP1')
|
|
baseVolume = self.safe_number(ticker, 'V')
|
|
quoteVolume = self.safe_number(ticker, 'T')
|
|
if market['inverse']:
|
|
temp = baseVolume
|
|
baseVolume = quoteVolume
|
|
quoteVolume = temp
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': high,
|
|
'low': low,
|
|
'bid': bid,
|
|
'bidVolume': None,
|
|
'ask': ask,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
watches information on multiple trades made in a market
|
|
|
|
https://www.deepcoin.com/docs/publicWS/lastTransactions
|
|
|
|
:param str symbol: unified market symbol of the market trades were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of trade structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
messageHash = 'trades' + '::' + market['symbol']
|
|
trades = await self.watch_public(market, messageHash, '2', params)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
async def un_watch_trades(self, symbol: str, params={}):
|
|
"""
|
|
unWatches the list of most recent trades for a particular symbol
|
|
|
|
https://www.deepcoin.com/docs/publicWS/lastTransactions
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
: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)
|
|
messageHash = 'trades' + '::' + market['symbol']
|
|
subscription = {
|
|
'topic': 'trades',
|
|
}
|
|
return await self.un_watch_public(market, messageHash, '2', params, subscription)
|
|
|
|
def handle_trades(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "a": "PMT",
|
|
# "b": 0,
|
|
# "tt": 1760968672380,
|
|
# "mt": 1760968672380,
|
|
# "r": [
|
|
# {
|
|
# "d": {
|
|
# "TradeID": "1001056452325378",
|
|
# "I": "BTC/USDT",
|
|
# "D": "1",
|
|
# "P": 111061,
|
|
# "V": 0.00137,
|
|
# "T": 1760968672
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
if not (symbol in self.trades):
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
self.trades[symbol] = ArrayCache(limit)
|
|
strored = self.trades[symbol]
|
|
if data is not None:
|
|
trade = self.parse_ws_trade(data, market)
|
|
strored.append(trade)
|
|
messageHash = 'trades' + '::' + symbol
|
|
client.resolve(strored, messageHash)
|
|
|
|
def parse_ws_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# watchTrades
|
|
# {
|
|
# "TradeID": "1001056452325378",
|
|
# "I": "BTC/USDT",
|
|
# "D": "1",
|
|
# "P": 111061,
|
|
# "V": 0.00137,
|
|
# "T": 1760968672
|
|
# }
|
|
#
|
|
# watchMyTrades
|
|
# {
|
|
# "A": "9256245",
|
|
# "CC": "USDT",
|
|
# "CP": 0,
|
|
# "D": "0",
|
|
# "F": 0.152,
|
|
# "I": "DOGE/USDT",
|
|
# "IT": 1761048103,
|
|
# "M": "9256245",
|
|
# "OS": "1001437462198486",
|
|
# "P": 0.19443,
|
|
# "T": 14.77668,
|
|
# "TI": "1001056459096708",
|
|
# "TT": 1761048103,
|
|
# "V": 76,
|
|
# "f": "DOGE",
|
|
# "l": 1,
|
|
# "m": "1",
|
|
# "o": "0"
|
|
# }
|
|
#
|
|
direction = self.safe_string(trade, 'D')
|
|
timestamp = self.safe_timestamp_2(trade, 'TT', 'T')
|
|
matchRole = self.safe_string(trade, 'm')
|
|
fee = None
|
|
feeCost = self.safe_string(trade, 'F')
|
|
if feeCost is not None:
|
|
fee = {
|
|
'cost': feeCost,
|
|
'currency': self.safe_currency_code(self.safe_string(trade, 'f')),
|
|
}
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'id': self.safe_string_2(trade, 'TradeID', 'TI'),
|
|
'order': self.safe_string(trade, 'OS'),
|
|
'type': None,
|
|
'takerOrMaker': self.handle_taker_or_maker(matchRole),
|
|
'side': self.parse_trade_side(direction),
|
|
'price': self.safe_string(trade, 'P'),
|
|
'amount': self.safe_string(trade, 'V'),
|
|
'cost': self.safe_string(trade, 'T'),
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
def parse_trade_side(self, direction: Str) -> Str:
|
|
sides = {
|
|
'0': 'buy',
|
|
'1': 'sell',
|
|
}
|
|
return self.safe_string(sides, direction, direction)
|
|
|
|
def handle_taker_or_maker(self, matchRole: Str) -> Str:
|
|
roles = {
|
|
'0': 'maker',
|
|
'1': 'taker',
|
|
}
|
|
return self.safe_string(roles, matchRole, matchRole)
|
|
|
|
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://www.deepcoin.com/docs/publicWS/KLines
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str [timeframe]: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
timeframes = self.safe_dict(self.options, 'timeframes', {})
|
|
interval = self.safe_string(timeframes, timeframe, timeframe)
|
|
messageHash = 'ohlcv' + '::' + symbol + '::' + timeframe
|
|
suffix = '_' + interval
|
|
ohlcv = await self.watch_public(market, messageHash, '11', params, suffix)
|
|
if self.newUpdates:
|
|
limit = ohlcv.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
|
|
|
async def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}) -> Any:
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/K-Line
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str [timeframe]: the length of time each candle represents
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
timeframes = self.safe_dict(self.options, 'timeframes', {})
|
|
interval = self.safe_string(timeframes, timeframe, timeframe)
|
|
messageHash = 'ohlcv' + '::' + symbol + '::' + timeframe
|
|
suffix = '_' + interval
|
|
subscription = {
|
|
'topic': 'ohlcv',
|
|
'symbolsAndTimeframes': [[symbol, timeframe]],
|
|
}
|
|
return await self.un_watch_public(market, messageHash, '11', params, subscription, suffix)
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "a": "PK",
|
|
# "tt": 1760972831580,
|
|
# "mt": 1760972831580,
|
|
# "r": [
|
|
# {
|
|
# "d": {
|
|
# "I": "BTC/USDT",
|
|
# "P": "1m",
|
|
# "B": 1760972820,
|
|
# "O": 111373,
|
|
# "C": 111382.9,
|
|
# "H": 111382.9,
|
|
# "L": 111373,
|
|
# "V": 0.2414172,
|
|
# "M": 26888.19693324
|
|
# },
|
|
# "t": "LK"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
interval = self.safe_string(data, 'P')
|
|
timeframe = self.find_timeframe(interval)
|
|
if not (symbol in self.ohlcvs):
|
|
self.ohlcvs[symbol] = {}
|
|
if not (timeframe in self.ohlcvs[symbol]):
|
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
|
|
stored = self.ohlcvs[symbol][timeframe]
|
|
if data is not None:
|
|
ohlcv = self.parse_ws_ohlcv(data, market)
|
|
stored.append(ohlcv)
|
|
messageHash = 'ohlcv' + '::' + symbol + '::' + timeframe
|
|
client.resolve(stored, messageHash)
|
|
|
|
def parse_ws_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "I": "BTC/USDT",
|
|
# "P": "1m",
|
|
# "B": 1760972820,
|
|
# "O": 111373,
|
|
# "C": 111382.9,
|
|
# "H": 111382.9,
|
|
# "L": 111373,
|
|
# "V": 0.2414172,
|
|
# "M": 26888.19693324
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_timestamp(ohlcv, 'B'),
|
|
self.safe_number(ohlcv, 'O'),
|
|
self.safe_number(ohlcv, 'H'),
|
|
self.safe_number(ohlcv, 'L'),
|
|
self.safe_number(ohlcv, 'C'),
|
|
self.safe_number(ohlcv, 'V'),
|
|
]
|
|
|
|
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
|
|
|
|
https://www.deepcoin.com/docs/publicWS/25LevelIncrementalMarketData
|
|
|
|
: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)
|
|
messageHash = 'orderbook' + '::' + market['symbol']
|
|
suffix = '_0.1'
|
|
orderbook = await self.watch_public(market, messageHash, '25', params, suffix)
|
|
return orderbook.limit()
|
|
|
|
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://www.deepcoin.com/docs/publicWS/25LevelIncrementalMarketData
|
|
|
|
:param str symbol: unified array of symbols
|
|
: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)
|
|
messageHash = 'orderbook' + '::' + market['symbol']
|
|
suffix = '_0.1'
|
|
subscription = {
|
|
'topic': 'orderbook',
|
|
}
|
|
return await self.un_watch_public(market, messageHash, '25', params, subscription, suffix)
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "a": "PMO",
|
|
# "t": "i", # i - update, f - snapshot
|
|
# "r": [
|
|
# {
|
|
# "d": {"I": "ETH/USDT", "D": "1", "P": 4021, "V": 54.39979}
|
|
# },
|
|
# {
|
|
# "d": {"I": "ETH/USDT", "D": "0", "P": 4021.1, "V": 49.56724}
|
|
# }
|
|
# ],
|
|
# "tt": 1760975816446,
|
|
# "mt": 1760975816446
|
|
# }
|
|
#
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
if not (symbol in self.orderbooks):
|
|
self.orderbooks[symbol] = self.order_book()
|
|
orderbook = self.orderbooks[symbol]
|
|
type = self.safe_string(message, 't')
|
|
if orderbook['timestamp'] is None:
|
|
if type == 'f':
|
|
# snapshot
|
|
self.handle_order_book_snapshot(client, message)
|
|
else:
|
|
# cache the updates until the snapshot is received
|
|
orderbook.cache.append(message)
|
|
else:
|
|
self.handle_order_book_message(client, message, orderbook)
|
|
messageHash = 'orderbook' + '::' + symbol
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_order_book_snapshot(self, client: Client, message):
|
|
entries = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(entries, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
orderbook = self.orderbooks[symbol]
|
|
orderedEntries: dict = {
|
|
'bids': [],
|
|
'asks': [],
|
|
}
|
|
for i in range(0, len(entries)):
|
|
entry = entries[i]
|
|
entryData = self.safe_dict(entry, 'd', {})
|
|
side = self.safe_string(entryData, 'D')
|
|
price = self.safe_number(entryData, 'P')
|
|
volume = self.safe_number(entryData, 'V')
|
|
if side == '0':
|
|
# bid
|
|
orderedEntries['bids'].append([price, volume])
|
|
elif side == '1':
|
|
# ask
|
|
orderedEntries['asks'].append([price, volume])
|
|
timestamp = self.safe_integer(message, 'mt')
|
|
snapshot = self.parse_order_book(orderedEntries, symbol, timestamp)
|
|
orderbook.reset(snapshot)
|
|
cachedMessages = orderbook.cache
|
|
for j in range(0, len(cachedMessages)):
|
|
cachedMessage = cachedMessages[j]
|
|
self.handle_order_book_message(client, cachedMessage, orderbook)
|
|
orderbook.cache = []
|
|
messageHash = 'orderbook' + '::' + symbol
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_order_book_message(self, client: Client, message, orderbook):
|
|
# {
|
|
# "a": "PMO",
|
|
# "t": "i", # i - update, f - snapshot
|
|
# "r": [
|
|
# {
|
|
# "d": {"I": "ETH/USDT", "D": "1", "P": 4021, "V": 54.39979}
|
|
# },
|
|
# {
|
|
# "d": {"I": "ETH/USDT", "D": "0", "P": 4021.1, "V": 49.56724}
|
|
# }
|
|
# ],
|
|
# "tt": 1760975816446,
|
|
# "mt": 1760975816446
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(message, 'mt')
|
|
if timestamp > orderbook['timestamp']:
|
|
response = self.safe_list(message, 'r', [])
|
|
self.handle_deltas(orderbook, response)
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
|
|
def handle_delta(self, orderbook, entry):
|
|
data = self.safe_dict(entry, 'd', {})
|
|
bids = orderbook['bids']
|
|
asks = orderbook['asks']
|
|
side = self.safe_string(data, 'D')
|
|
price = self.safe_number(data, 'P')
|
|
volume = self.safe_number(data, 'V')
|
|
if side == '0':
|
|
# bid
|
|
bids.store(price, volume)
|
|
elif side == '1':
|
|
# ask
|
|
asks.store(price, volume)
|
|
|
|
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
watches information on multiple trades made by the user
|
|
|
|
https://www.deepcoin.com/docs/privateWS/Trade
|
|
|
|
: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>`
|
|
"""
|
|
messageHash = 'myTrades'
|
|
await self.load_markets()
|
|
if symbol is not None:
|
|
symbol = self.symbol(symbol)
|
|
messageHash += '::' + symbol
|
|
trades = await self.watch_private(messageHash, params)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
|
|
|
def handle_my_trade(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "action": "PushTrade",
|
|
# "result": [
|
|
# {
|
|
# "table": "Trade",
|
|
# "data": {
|
|
# "A": "9256245",
|
|
# "CC": "USDT",
|
|
# "CP": 0,
|
|
# "D": "0",
|
|
# "F": 0.152,
|
|
# "I": "DOGE/USDT",
|
|
# "IT": 1761048103,
|
|
# "M": "9256245",
|
|
# "OS": "1001437462198486",
|
|
# "P": 0.19443,
|
|
# "T": 14.77668,
|
|
# "TI": "1001056459096708",
|
|
# "TT": 1761048103,
|
|
# "V": 76,
|
|
# "f": "DOGE",
|
|
# "l": 1,
|
|
# "m": "1",
|
|
# "o": "0"
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(message, 'result', [])
|
|
first = self.safe_dict(result, 0, {})
|
|
data = self.safe_dict(first, 'data', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
messageHash = 'myTrades'
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
if (messageHash in client.futures) or (symbolMessageHash in client.futures):
|
|
if self.myTrades is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
self.myTrades = ArrayCacheBySymbolById(limit)
|
|
stored = self.myTrades
|
|
parsed = self.parse_ws_trade(data, market)
|
|
stored.append(parsed)
|
|
client.resolve(stored, messageHash)
|
|
client.resolve(stored, symbolMessageHash)
|
|
|
|
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://www.deepcoin.com/docs/privateWS/order
|
|
|
|
: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>`
|
|
"""
|
|
messageHash = 'orders'
|
|
await self.load_markets()
|
|
if symbol is not None:
|
|
symbol = self.symbol(symbol)
|
|
messageHash += '::' + symbol
|
|
orders = await self.watch_private(messageHash, params)
|
|
if self.newUpdates:
|
|
limit = orders.getLimit(symbol, limit)
|
|
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
|
|
|
def handle_order(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "action": "PushOrder",
|
|
# "result": [
|
|
# {
|
|
# "table": "Order",
|
|
# "data": {
|
|
# "D": "0",
|
|
# "I": "DOGE/USDT",
|
|
# "IT": 1761051006,
|
|
# "L": "1001437480817468",
|
|
# "OPT": "4",
|
|
# "OS": "1001437480817468",
|
|
# "OT": "0",
|
|
# "Or": "1",
|
|
# "P": 0.19537,
|
|
# "T": 14.84128,
|
|
# "U": 1761051006,
|
|
# "V": 76,
|
|
# "VT": 76,
|
|
# "i": 1,
|
|
# "l": 1,
|
|
# "o": "0",
|
|
# "p": "0",
|
|
# "t": 0.19528
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(message, 'result', [])
|
|
first = self.safe_dict(result, 0, {})
|
|
data = self.safe_dict(first, 'data', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
messageHash = 'orders'
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
if (messageHash in client.futures) or (symbolMessageHash in client.futures):
|
|
if self.orders is None:
|
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
self.orders = ArrayCacheBySymbolById(limit)
|
|
parsed = self.parse_ws_order(data, market)
|
|
self.orders.append(parsed)
|
|
client.resolve(self.orders, messageHash)
|
|
client.resolve(self.orders, symbolMessageHash)
|
|
|
|
def parse_ws_order(self, order, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# "D": "0",
|
|
# "I": "DOGE/USDT",
|
|
# "IT": 1761051006,
|
|
# "L": "1001437480817468",
|
|
# "OPT": "4",
|
|
# "OS": "1001437480817468",
|
|
# "OT": "0",
|
|
# "Or": "1",
|
|
# "P": 0.19537,
|
|
# "T": 14.84128,
|
|
# "U": 1761051006,
|
|
# "V": 76,
|
|
# "VT": 76,
|
|
# "i": 1,
|
|
# "l": 1,
|
|
# "o": "0",
|
|
# "p": "0",
|
|
# "t": 0.19528
|
|
# }
|
|
#
|
|
state = self.safe_string(order, 'Or')
|
|
timestamp = self.safe_timestamp(order, 'IT')
|
|
direction = self.safe_string(order, 'D')
|
|
return self.safe_order({
|
|
'id': self.safe_string(order, 'OS'),
|
|
'clientOrderId': None,
|
|
'datetime': self.iso8601(timestamp),
|
|
'timestamp': timestamp,
|
|
'lastTradeTimestamp': None,
|
|
'lastUpdateTimestamp': self.safe_timestamp(order, 'U'),
|
|
'status': self.parse_ws_order_status(state),
|
|
'symbol': market['symbol'],
|
|
'type': None,
|
|
'timeInForce': None,
|
|
'side': self.parse_trade_side(direction),
|
|
'price': self.safe_string(order, 'P'),
|
|
'average': self.safe_string(order, 't'),
|
|
'amount': self.safe_string(order, 'V'),
|
|
'filled': self.safe_string(order, 'VT'),
|
|
'remaining': None,
|
|
'triggerPrice': None,
|
|
'takeProfitPrice': self.safe_string(order, 'TPT'),
|
|
'stopLossPrice': self.safe_string(order, 'SLT'),
|
|
'cost': self.safe_string(order, 'T'),
|
|
'trades': None,
|
|
'fee': None,
|
|
'reduceOnly': None,
|
|
'postOnly': None,
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_ws_order_status(self, status: Str) -> Str:
|
|
statuses = {
|
|
'1': 'closed',
|
|
'4': 'open',
|
|
'6': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
"""
|
|
watch all open positions
|
|
|
|
https://www.deepcoin.com/docs/privateWS/Position
|
|
|
|
:param str[] [symbols]: list of unified market symbols to watch positions for
|
|
:param int [since]: the earliest time in ms to fetch positions for
|
|
:param int [limit]: the maximum number of positions to retrieve
|
|
:param dict params: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
listenKey = await self.authenticate()
|
|
symbols = self.market_symbols(symbols)
|
|
messageHash = 'positions'
|
|
messageHashes = []
|
|
if symbols is not None:
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
messageHashes.append(symbolMessageHash)
|
|
else:
|
|
messageHashes.append(messageHash)
|
|
url = self.urls['api']['ws']['private'] + '?listenKey=' + listenKey
|
|
positions = await self.watch_multiple(url, messageHashes, params, ['private'])
|
|
if self.newUpdates:
|
|
return positions
|
|
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
|
|
|
|
def handle_position(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "action": "PushPosition",
|
|
# "result": [
|
|
# {
|
|
# "table": "Position",
|
|
# "data": {
|
|
# "A": "9256245",
|
|
# "CP": 0,
|
|
# "I": "DOGE/USDT",
|
|
# "M": "9256245",
|
|
# "OP": 0.198845,
|
|
# "Po": 151.696,
|
|
# "U": 1761058213,
|
|
# "i": 1,
|
|
# "l": 1,
|
|
# "p": "0",
|
|
# "u": 0
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(message, 'result', [])
|
|
first = self.safe_dict(result, 0, {})
|
|
data = self.safe_dict(first, 'data', {})
|
|
marketId = self.safe_string(data, 'I')
|
|
market = self.safe_market(marketId, None, '/')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
messageHash = 'positions'
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
if (messageHash in client.futures) or (symbolMessageHash in client.futures):
|
|
if self.positions is None:
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
parsed = self.parse_ws_position(data, market)
|
|
self.positions.append(parsed)
|
|
client.resolve(self.positions, messageHash)
|
|
client.resolve(self.positions, symbolMessageHash)
|
|
|
|
def parse_ws_position(self, position, market: Market = None) -> Position:
|
|
#
|
|
# {
|
|
# "A": "9256245",
|
|
# "CP": 0,
|
|
# "I": "DOGE/USDT",
|
|
# "M": "9256245",
|
|
# "OP": 0.198845,
|
|
# "Po": 151.696,
|
|
# "U": 1761058213,
|
|
# "i": 1,
|
|
# "l": 1,
|
|
# "p": "0",
|
|
# "u": 0
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(position, 'U')
|
|
direction = self.safe_string(position, 'p')
|
|
marginMode = self.safe_string(position, 'i')
|
|
return self.safe_position({
|
|
'symbol': market['symbol'],
|
|
'id': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'contracts': self.safe_string(position, 'Po'),
|
|
'contractSize': None,
|
|
'side': self.parse_position_side(direction),
|
|
'notional': None,
|
|
'leverage': self.omit_zero(self.safe_string(position, 'l')),
|
|
'unrealizedPnl': None,
|
|
'realizedPnl': None,
|
|
'collateral': None,
|
|
'entryPrice': self.safe_string(position, 'OP'),
|
|
'markPrice': None,
|
|
'liquidationPrice': None,
|
|
'marginMode': self.parse_ws_margin_mode(marginMode),
|
|
'hedged': True,
|
|
'maintenanceMargin': self.safe_string(position, 'u'),
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'marginRatio': None,
|
|
'lastUpdateTimestamp': None,
|
|
'lastPrice': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
'percentage': None,
|
|
'info': position,
|
|
})
|
|
|
|
def parse_position_side(self, direction: Str) -> Str:
|
|
if direction is None:
|
|
return direction
|
|
directions = {
|
|
'0': 'long',
|
|
'1': 'short',
|
|
}
|
|
return self.safe_string(directions, direction, direction)
|
|
|
|
def parse_ws_margin_mode(self, marginMode: Str) -> Str:
|
|
if marginMode is None:
|
|
return marginMode
|
|
modes = {
|
|
'0': 'isolated',
|
|
'1': 'cross',
|
|
}
|
|
return self.safe_string(modes, marginMode, marginMode)
|
|
|
|
def handle_message(self, client: Client, message):
|
|
if message == 'pong':
|
|
self.handle_pong(client, message)
|
|
else:
|
|
m = self.safe_string(message, 'm')
|
|
if (m is not None) and (m != 'Success'):
|
|
self.handle_error_message(client, message)
|
|
action = self.safe_string_2(message, 'a', 'action')
|
|
if action == 'RecvTopicAction':
|
|
self.handle_subscription_status(client, message)
|
|
elif action == 'PO':
|
|
self.handle_ticker(client, message)
|
|
elif action == 'PMT':
|
|
self.handle_trades(client, message)
|
|
elif action == 'PK':
|
|
self.handle_ohlcv(client, message)
|
|
elif action == 'PMO':
|
|
self.handle_order_book(client, message)
|
|
elif action == 'PushTrade':
|
|
self.handle_my_trade(client, message)
|
|
elif action == 'PushOrder':
|
|
self.handle_order(client, message)
|
|
elif action == 'PushPosition':
|
|
self.handle_position(client, message)
|
|
|
|
def handle_subscription_status(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "a": "RecvTopicAction",
|
|
# "m": "Success",
|
|
# "r": [
|
|
# {
|
|
# "d": {
|
|
# "A": "0",
|
|
# "L": 1,
|
|
# "T": "7",
|
|
# "F": "DeepCoin_BTC/USDT",
|
|
# "R": -1
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
action = self.safe_string(data, 'A') # 1 = subscribe, 0 = unsubscribe
|
|
if action == '0':
|
|
subscriptionsById = self.index_by(client.subscriptions, 'id')
|
|
subId = self.safe_integer(data, 'L')
|
|
subscription = self.safe_dict(subscriptionsById, subId, {}) # original watch subscription
|
|
subHash = self.safe_string(subscription, 'subHash')
|
|
unsubHash = 'unsubscribe::' + subHash
|
|
unsubsciption = self.safe_dict(client.subscriptions, unsubHash, {}) # unWatch subscription
|
|
self.handle_un_subscription(client, unsubsciption)
|
|
|
|
def handle_un_subscription(self, client: Client, subscription: dict):
|
|
subHash = self.safe_string(subscription, 'subHash')
|
|
unsubHash = self.safe_string(subscription, 'unsubHash')
|
|
self.clean_unsubscription(client, subHash, unsubHash)
|
|
self.clean_cache(subscription)
|
|
|
|
def handle_error_message(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "a": "RecvTopicAction",
|
|
# "m": "subscription cluster does not "exist": BTC/USD",
|
|
# "r": [
|
|
# {
|
|
# "d": {
|
|
# "A": "1",
|
|
# "L": 1,
|
|
# "T": "7",
|
|
# "F": "DeepCoin_BTC/USD",
|
|
# "R": -1
|
|
# }
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
messageText = self.safe_string(message, 'm', '')
|
|
response = self.safe_list(message, 'r', [])
|
|
first = self.safe_dict(response, 0, {})
|
|
data = self.safe_dict(first, 'd', {})
|
|
requestId = self.safe_integer(data, 'L')
|
|
subscriptionsById = self.index_by(client.subscriptions, 'id')
|
|
subscription = self.safe_dict(subscriptionsById, requestId, {})
|
|
messageHash = self.safe_string(subscription, 'subHash')
|
|
feedback = self.id + ' ' + self.json(message)
|
|
try:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], messageText, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], messageText, feedback)
|
|
raise ExchangeError(feedback)
|
|
except Exception as e:
|
|
client.reject(e, messageHash)
|