# -*- 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, 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 NotSupported class probit(ccxt.async_support.probit): def describe(self) -> Any: return self.deep_extend(super(probit, self).describe(), { 'has': { 'ws': True, 'watchBalance': True, 'watchTicker': True, 'watchTickers': False, 'watchTrades': True, 'watchTradesForSymbols': False, 'watchMyTrades': True, 'watchOrders': True, 'watchOrderBook': True, 'watchOHLCV': False, }, 'urls': { 'api': { 'ws': 'wss://api.probit.com/api/exchange/v1/ws', }, 'test': { 'ws': 'wss://demo-api.probit.com/api/exchange/v1/ws', }, }, 'options': { 'watchOrderBook': { 'filter': 'order_books_l2', 'interval': 100, # or 500 }, }, 'streaming': { }, }) async def watch_balance(self, params={}) -> Balances: """ watch balance and get the amount of funds available for trading or funds locked in orders https://docs-en.probit.com/reference/balance-1 :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ await self.authenticate(params) messageHash = 'balance' return await self.subscribe_private(messageHash, 'balance', params) def handle_balance(self, client: Client, message): # # { # "channel": "balance", # "reset": False, # "data": { # "USDT": { # "available": "15", # "total": "15" # } # } # } # messageHash = 'balance' self.parse_ws_balance(message) client.resolve(self.balance, messageHash) def parse_ws_balance(self, message): # # { # "channel": "balance", # "reset": False, # "data": { # "USDT": { # "available": "15", # "total": "15" # } # } # } # reset = self.safe_bool(message, 'reset', False) data = self.safe_value(message, 'data', {}) currencyIds = list(data.keys()) if reset: self.balance = {} for i in range(0, len(currencyIds)): currencyId = currencyIds[i] entry = data[currencyId] code = self.safe_currency_code(currencyId) account = self.account() account['free'] = self.safe_string(entry, 'available') account['total'] = self.safe_string(entry, 'total') 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://docs-en.probit.com/reference/marketdata :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 int [params.interval]: Unit time to synchronize market information(ms). Available units: 100, 500 :returns dict: a `ticker structure ` """ channel = 'ticker' return await self.subscribe_public('watchTicker', symbol, 'ticker', channel, params) def handle_ticker(self, client: Client, message): # # { # "channel": "marketdata", # "market_id": "BTC-USDT", # "status": "ok", # "lag": 0, # "ticker": { # "time": "2022-07-21T14:18:04.000Z", # "last": "22591.3", # "low": "22500.1", # "high": "39790.7", # "change": "-1224", # "base_volume": "1002.32005445", # "quote_volume": "23304489.385351021" # }, # "reset": True # } # marketId = self.safe_string(message, 'market_id') symbol = self.safe_symbol(marketId) ticker = self.safe_value(message, 'ticker', {}) 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 https://docs-en.probit.com/reference/trade_history :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 :param int [params.interval]: Unit time to synchronize market information(ms). Available units: 100, 500 :returns dict[]: a list of `trade structures ` """ channel = 'recent_trades' symbol = self.safe_symbol(symbol) trades = await self.subscribe_public('watchTrades', symbol, 'trades', channel, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_trades(self, client: Client, message): # # { # "channel": "marketdata", # "market_id": "BTC-USDT", # "status": "ok", # "lag": 0, # "recent_trades": [ # { # "id": "BTC-USDT:8010233", # "price": "22701.4", # "quantity": "0.011011", # "time": "2022-07-21T13:40:40.983Z", # "side": "buy", # "tick_direction": "up" # } # ... # ] # "reset": True # } # marketId = self.safe_string(message, 'market_id') symbol = self.safe_symbol(marketId) market = self.safe_market(marketId) trades = self.safe_value(message, 'recent_trades', []) if self.safe_bool(message, 'reset', False): return # see comment in handleMessage 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 https://docs-en.probit.com/reference/trade_history :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 ` """ await self.load_markets() await self.authenticate(params) messageHash = 'trades' if symbol is not None: symbol = self.safe_symbol(symbol) messageHash = messageHash + ':' + symbol trades = await self.subscribe_private(messageHash, 'trade_history', params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_my_trades(self, client: Client, message): # # { # "channel": "trade_history", # "reset": False, # "data": [{ # "id": "BTC-USDT:8010722", # "order_id": "4124999207", # "side": "buy", # "fee_amount": "0.0134999868096", # "fee_currency_id": "USDT", # "status": "settled", # "price": "23136.7", # "quantity": "0.00032416", # "cost": "7.499992672", # "time": "2022-07-21T17:09:33.056Z", # "market_id": "BTC-USDT" # }] # } # rawTrades = self.safe_value(message, 'data', []) length = len(rawTrades) if length == 0: return if self.safe_bool(message, 'reset', False): return # see comment in handleMessage messageHash = 'trades' stored = self.myTrades if stored is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) stored = ArrayCacheBySymbolById(limit) self.myTrades = stored trades = self.parse_trades(rawTrades) tradeSymbols: dict = {} for j in range(0, len(trades)): trade = trades[j] # don't include 'executed' state, because it's just blanket state of the trade, emited before actual trade event if self.safe_string(trade['info'], 'status') == 'executed': continue tradeSymbols[trade['symbol']] = True stored.append(trade) unique = list(tradeSymbols.keys()) uniqueLength = len(unique) if uniqueLength == 0: return for i in range(0, len(unique)): symbol = unique[i] symbolSpecificMessageHash = messageHash + ':' + symbol client.resolve(stored, symbolSpecificMessageHash) client.resolve(stored, messageHash) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on an order made by the user https://docs-en.probit.com/reference/open_order :param str symbol: unified symbol of the market the order was made in :param int [since]: timestamp in ms of the earliest order to watch :param int [limit]: the maximum amount of orders to watch :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.channel]: choose what channel to use. Can open_order or order_history. :returns dict: An `order structure ` """ await self.authenticate(params) messageHash = 'orders' if symbol is not None: symbol = self.safe_symbol(symbol) messageHash = messageHash + ':' + symbol orders = await self.subscribe_private(messageHash, 'open_order', params) 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): # # { # "channel": "order_history", # "reset": True, # "data": [{ # "id": "4124999207", # "user_id": "633dc56a-621b-4680-8a4e-85a823499b6d", # "market_id": "BTC-USDT", # "type": "market", # "side": "buy", # "limit_price": "0", # "time_in_force": "ioc", # "filled_cost": "7.499992672", # "filled_quantity": "0.00032416", # "open_quantity": "0", # "status": "filled", # "time": "2022-07-21T17:09:33.056Z", # "client_order_id": '', # "cost": "7.5" # }, # ... # ] # } # rawOrders = self.safe_value(message, 'data', []) length = len(rawOrders) if length == 0: return messageHash = 'orders' reset = self.safe_bool(message, 'reset', False) stored = self.orders if stored is None or reset: limit = self.safe_integer(self.options, 'ordersLimit', 1000) stored = ArrayCacheBySymbolById(limit) self.orders = stored orderSymbols: dict = {} for i in range(0, len(rawOrders)): rawOrder = rawOrders[i] order = self.parse_order(rawOrder) orderSymbols[order['symbol']] = True stored.append(order) unique = list(orderSymbols.keys()) for i in range(0, len(unique)): symbol = unique[i] symbolSpecificMessageHash = messageHash + ':' + symbol client.resolve(stored, symbolSpecificMessageHash) client.resolve(stored, 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 https://docs-en.probit.com/reference/marketdata :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 ` indexed by market symbols """ channel = None channel, params = self.handle_option_and_params(params, 'watchOrderBook', 'filter', 'order_books') orderbook = await self.subscribe_public('watchOrderBook', symbol, 'orderbook', channel, params) return orderbook.limit() async def subscribe_private(self, messageHash, channel, params): url = self.urls['api']['ws'] subscribe: dict = { 'type': 'subscribe', 'channel': channel, } request = self.extend(subscribe, params) subscribeHash = messageHash return await self.watch(url, messageHash, request, subscribeHash) async def subscribe_public(self, methodName: str, symbol: str, dataType, filter, params={}): await self.load_markets() market = self.market(symbol) symbol = market['symbol'] url = self.urls['api']['ws'] client = self.client(url) subscribeHash = 'marketdata:' + symbol messageHash = dataType + ':' + symbol filters = {} if subscribeHash in client.subscriptions: # already subscribed filters = client.subscriptions[subscribeHash] if not (filter in filters): # resubscribe del client.subscriptions[subscribeHash] filters[filter] = True keys = list(filters.keys()) interval = None interval, params = self.handle_option_and_params(params, methodName, 'interval', 100) request: dict = { 'type': 'subscribe', 'channel': 'marketdata', 'market_id': market['id'], 'filter': keys, 'interval': interval, } request = self.extend(request, params) return await self.watch(url, messageHash, request, subscribeHash, filters) def handle_order_book(self, client: Client, message, orderBook): # # { # "channel": "marketdata", # "market_id": "BTC-USDT", # "status": "ok", # "lag": 0, # "order_books": [ # {side: "buy", price: '1420.7', quantity: "0.057"}, # ... # ], # "reset": True # } # marketId = self.safe_string(message, 'market_id') symbol = self.safe_symbol(marketId) dataBySide = self.group_by(orderBook, 'side') messageHash = 'orderbook:' + symbol # orderbook = self.safe_value(self.orderbooks, symbol) if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book({}) orderbook = self.orderbooks[symbol] reset = self.safe_bool(message, 'reset', False) if reset: snapshot = self.parse_order_book(dataBySide, symbol, None, 'buy', 'sell', 'price', 'quantity') orderbook.reset(snapshot) else: self.handle_delta(orderbook, dataBySide) client.resolve(orderbook, messageHash) def handle_bid_asks(self, bookSide, bidAsks): for i in range(0, len(bidAsks)): bidAsk = bidAsks[i] parsed = self.parse_bid_ask(bidAsk, 'price', 'quantity') bookSide.storeArray(parsed) def handle_delta(self, orderbook, delta): storedBids = orderbook['bids'] storedAsks = orderbook['asks'] asks = self.safe_value(delta, 'sell', []) bids = self.safe_value(delta, 'buy', []) self.handle_bid_asks(storedBids, bids) self.handle_bid_asks(storedAsks, asks) def handle_error_message(self, client: Client, message) -> Bool: # # { # "errorCode": "INVALID_ARGUMENT", # "message": '', # "details": { # "interval": "invalid" # } # } # code = self.safe_string(message, 'errorCode') errMessage = self.safe_string(message, 'message', '') details = self.safe_value(message, 'details') feedback = self.id + ' ' + code + ' ' + errMessage + ' ' + self.json(details) if 'exact' in self.exceptions: self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) if 'broad' in self.exceptions: self.throw_broadly_matched_exception(self.exceptions['broad'], errMessage, feedback) raise ExchangeError(feedback) def handle_authenticate(self, client: Client, message): # # {type: "authorization", result: "ok"} # result = self.safe_string(message, 'result') future = client.subscriptions['authenticated'] if result == 'ok': messageHash = 'authenticated' client.resolve(message, messageHash) else: future.reject(message) del client.subscriptions['authenticated'] def handle_market_data(self, client: Client, message): ticker = self.safe_value(message, 'ticker') if ticker is not None: self.handle_ticker(client, message) trades = self.safe_value(message, 'recent_trades', []) tradesLength = len(trades) if tradesLength: self.handle_trades(client, message) orderBook = self.safe_value_n(message, ['order_books', 'order_books_l1', 'order_books_l2', 'order_books_l3', 'order_books_l4'], []) orderBookLength = len(orderBook) if orderBookLength: self.handle_order_book(client, message, orderBook) def handle_message(self, client: Client, message): # # { # "errorCode": "INVALID_ARGUMENT", # "message": '', # "details": { # "interval": "invalid" # } # } # # Note about 'reset' field # 'reset': True field - it happens once after initial subscription, which just returns old items by the moment of subscription(like "fetchMyTrades" does) # errorCode = self.safe_string(message, 'errorCode') if errorCode is not None: self.handle_error_message(client, message) return type = self.safe_string(message, 'type') if type == 'authorization': self.handle_authenticate(client, message) return handlers: dict = { 'marketdata': self.handle_market_data, 'balance': self.handle_balance, 'trade_history': self.handle_my_trades, 'open_order': self.handle_orders, 'order_history': self.handle_orders, } channel = self.safe_string(message, 'channel') handler = self.safe_value(handlers, channel) if handler is not None: handler(client, message) return error = NotSupported(self.id + ' handleMessage: unknown message: ' + self.json(message)) client.reject(error) async def authenticate(self, params={}): url = self.urls['api']['ws'] client = self.client(url) messageHash = 'authenticated' expires = self.safe_integer(self.options, 'expires', 0) future = self.safe_value(client.subscriptions, messageHash) if (future is None) or (self.milliseconds() > expires): response = await self.sign_in() # # { # "access_token": "0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=", # "token_type": "bearer", # "expires_in": 900 # } # accessToken = self.safe_string(response, 'access_token') request: dict = { 'type': 'authorization', 'token': accessToken, } future = await self.watch(url, messageHash, self.extend(request, params), messageHash) client.subscriptions[messageHash] = future return future