# -*- 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, Balances, Bool, Int, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import AuthenticationError from ccxt.base.errors import NotSupported from ccxt.base.precise import Precise class modetrade(ccxt.async_support.modetrade): def describe(self) -> Any: return self.deep_extend(super(modetrade, self).describe(), { 'has': { 'ws': True, 'watchBalance': True, 'watchMyTrades': True, 'watchOHLCV': True, 'watchOrderBook': True, 'watchOrders': True, 'watchTicker': True, 'watchTickers': True, 'watchBidsAsks': True, 'watchTrades': True, 'watchTradesForSymbols': False, 'watchPositions': True, }, 'urls': { 'api': { 'ws': { 'public': 'wss://ws-evm.orderly.org/ws/stream', 'private': 'wss://ws-private-evm.orderly.org/v2/ws/private/stream', }, }, 'test': { 'ws': { 'public': 'wss://testnet-ws-evm.orderly.org/ws/stream', 'private': 'wss://testnet-ws-private-evm.orderly.org/v2/ws/private/stream', }, }, }, 'requiredCredentials': { 'apiKey': True, 'secret': True, 'accountId': True, }, 'options': { 'tradesLimit': 1000, 'ordersLimit': 1000, 'requestId': {}, 'watchPositions': { 'fetchPositionsSnapshot': True, # or False 'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates }, }, 'streaming': { 'ping': self.ping, 'keepAlive': 10000, }, 'exceptions': { 'ws': { 'exact': { 'Auth is needed.': AuthenticationError, }, }, }, }) def request_id(self, url): options = self.safe_dict(self.options, 'requestId', {}) previousValue = self.safe_integer(options, url, 0) newValue = self.sum(previousValue, 1) self.options['requestId'][url] = newValue return newValue async def watch_public(self, messageHash, message): # the default id id = 'OqdphuyCtYWxwzhxyLLjOWNdFP7sQt8RPWzmb5xY' if self.accountId is not None and self.accountId != '': id = self.accountId url = self.urls['api']['ws']['public'] + '/' + id requestId = self.request_id(url) subscribe: dict = { 'id': requestId, } request = self.extend(subscribe, message) return await self.watch(url, messageHash, request, messageHash, subscribe) async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/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 ` indexed by market symbols """ await self.load_markets() name = 'orderbook' market = self.market(symbol) topic = market['id'] + '@' + name request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) orderbook = await self.watch_public(topic, message) return orderbook.limit() def handle_order_book(self, client: Client, message): # # { # "topic": "PERP_BTC_USDC@orderbook", # "ts": 1650121915308, # "data": { # "symbol": "PERP_BTC_USDC", # "bids": [ # [ # 0.30891, # 2469.98 # ] # ], # "asks": [ # [ # 0.31075, # 2379.63 # ] # ] # } # } # data = self.safe_dict(message, 'data', {}) marketId = self.safe_string(data, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] topic = self.safe_string(message, 'topic') if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book() orderbook = self.orderbooks[symbol] timestamp = self.safe_integer(message, 'ts') snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks') orderbook.reset(snapshot) client.resolve(orderbook, topic) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-ticker watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market :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 ` """ await self.load_markets() name = 'ticker' market = self.market(symbol) symbol = market['symbol'] topic = market['id'] + '@' + name request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) return await self.watch_public(topic, message) def parse_ws_ticker(self, ticker, market=None): # # { # "symbol": "PERP_BTC_USDC", # "open": 19441.5, # "close": 20147.07, # "high": 20761.87, # "low": 19320.54, # "volume": 2481.103, # "amount": 50037935.0286, # "count": 3689 # } # return self.safe_ticker({ 'symbol': self.safe_symbol(None, market), 'timestamp': None, 'datetime': None, 'high': self.safe_string(ticker, 'high'), 'low': self.safe_string(ticker, 'low'), 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': None, 'open': self.safe_string(ticker, 'open'), 'close': self.safe_string(ticker, 'close'), 'last': None, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': self.safe_string(ticker, 'volume'), 'quoteVolume': self.safe_string(ticker, 'amount'), 'info': ticker, }, market) def handle_ticker(self, client: Client, message): # # { # "topic": "PERP_BTC_USDC@ticker", # "ts": 1657120017000, # "data": { # "symbol": "PERP_BTC_USDC", # "open": 19441.5, # "close": 20147.07, # "high": 20761.87, # "low": 19320.54, # "volume": 2481.103, # "amount": 50037935.0286, # "count": 3689 # } # } # data = self.safe_dict(message, 'data', {}) topic = self.safe_string(message, 'topic') marketId = self.safe_string(data, 'symbol') market = self.safe_market(marketId) timestamp = self.safe_integer(message, 'ts') data['date'] = timestamp ticker = self.parse_ws_ticker(data, market) ticker['symbol'] = market['symbol'] self.tickers[market['symbol']] = ticker client.resolve(ticker, topic) return message async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-tickers watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list :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 ` """ await self.load_markets() symbols = self.market_symbols(symbols) name = 'tickers' topic = name request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) tickers = await self.watch_public(topic, message) return self.filter_by_array(tickers, 'symbol', symbols) def handle_tickers(self, client: Client, message): # # { # "topic":"tickers", # "ts":1618820615000, # "data":[ # { # "symbol":"PERP_NEAR_USDC", # "open":16.297, # "close":17.183, # "high":24.707, # "low":11.997, # "volume":0, # "amount":0, # "count":0 # }, # ... # ] # } # topic = self.safe_string(message, 'topic') data = self.safe_list(message, 'data', []) timestamp = self.safe_integer(message, 'ts') result = [] for i in range(0, len(data)): marketId = self.safe_string(data[i], 'symbol') market = self.safe_market(marketId) ticker = self.parse_ws_ticker(self.extend(data[i], {'date': timestamp}), market) self.tickers[market['symbol']] = ticker result.append(ticker) client.resolve(result, topic) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/bbos watches best bid & ask for symbols :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 ` """ await self.load_markets() symbols = self.market_symbols(symbols) name = 'bbos' topic = name request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) tickers = await self.watch_public(topic, message) return self.filter_by_array(tickers, 'symbol', symbols) def handle_bid_ask(self, client: Client, message): # # { # "topic": "bbos", # "ts": 1726212495000, # "data": [ # { # "symbol": "PERP_BTC_USDC", # "ask": 0.16570, # "askSize": 4224, # "bid": 0.16553, # "bidSize": 6645 # } # ] # } # topic = self.safe_string(message, 'topic') data = self.safe_list(message, 'data', []) timestamp = self.safe_integer(message, 'ts') result = [] for i in range(0, len(data)): ticker = self.parse_ws_bid_ask(self.extend(data[i], {'ts': timestamp})) self.tickers[ticker['symbol']] = ticker result.append(ticker) client.resolve(result, topic) def parse_ws_bid_ask(self, ticker, market=None): marketId = self.safe_string(ticker, 'symbol') market = self.safe_market(marketId, market) symbol = self.safe_string(market, 'symbol') timestamp = self.safe_integer(ticker, 'ts') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'ask': self.safe_string(ticker, 'ask'), 'askVolume': self.safe_string(ticker, 'askSize'), 'bid': self.safe_string(ticker, 'bid'), 'bidVolume': self.safe_string(ticker, 'bidSize'), 'info': ticker, }, market) 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://orderly.network/docs/build-on-evm/evm-api/websocket-api/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 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() if (timeframe != '1m') and (timeframe != '5m') and (timeframe != '15m') and (timeframe != '30m') and (timeframe != '1h') and (timeframe != '1d') and (timeframe != '1w') and (timeframe != '1M'): raise NotSupported(self.id + ' watchOHLCV timeframe argument must be 1m, 5m, 15m, 30m, 1h, 1d, 1w, 1M') market = self.market(symbol) interval = self.safe_string(self.timeframes, timeframe, timeframe) name = 'kline' topic = market['id'] + '@' + name + '_' + interval request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) ohlcv = await self.watch_public(topic, message) if self.newUpdates: limit = ohlcv.getLimit(market['symbol'], limit) return self.filter_by_since_limit(ohlcv, since, limit, 0, True) def handle_ohlcv(self, client: Client, message): # # { # "topic":"PERP_BTC_USDC@kline_1m", # "ts":1618822432146, # "data":{ # "symbol":"PERP_BTC_USDC", # "type":"1m", # "open":56948.97, # "close":56891.76, # "high":56948.97, # "low":56889.06, # "volume":44.00947568, # "amount":2504584.9, # "startTime":1618822380000, # "endTime":1618822440000 # } # } # data = self.safe_dict(message, 'data', {}) topic = self.safe_string(message, 'topic') marketId = self.safe_string(data, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] interval = self.safe_string(data, 'type') timeframe = self.find_timeframe(interval) parsed = [ self.safe_integer(data, 'startTime'), self.safe_number(data, 'open'), self.safe_number(data, 'high'), self.safe_number(data, 'low'), self.safe_number(data, 'close'), self.safe_number(data, 'volume'), ] self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], timeframe) if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][timeframe] = stored ohlcvCache = self.ohlcvs[symbol][timeframe] ohlcvCache.append(parsed) client.resolve(ohlcvCache, topic) 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://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/trade :param str symbol: unified market symbol of the market trades were made in :param int [since]: the earliest time in ms to fetch trades 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 ` """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] topic = market['id'] + '@trade' request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) trades = await self.watch_public(topic, message) if self.newUpdates: limit = trades.getLimit(market['symbol'], limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_trade(self, client: Client, message): # # { # "topic":"PERP_ADA_USDC@trade", # "ts":1618820361552, # "data":{ # "symbol":"PERP_ADA_USDC", # "price":1.27988, # "size":300, # "side":"BUY", # } # } # topic = self.safe_string(message, 'topic') timestamp = self.safe_integer(message, 'ts') data = self.safe_dict(message, 'data', {}) marketId = self.safe_string(data, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] trade = self.parse_ws_trade(self.extend(data, {'timestamp': timestamp}), market) 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] trades.append(trade) self.trades[symbol] = trades client.resolve(trades, topic) def parse_ws_trade(self, trade, market=None): # # { # "symbol":"PERP_ADA_USDC", # "timestamp":1618820361552, # "price":1.27988, # "size":300, # "side":"BUY", # } # private stream # { # symbol: 'PERP_XRP_USDC', # clientOrderId: '', # orderId: 1167632251, # type: 'MARKET', # side: 'BUY', # quantity: 20, # price: 0, # tradeId: '1715179456664012', # executedPrice: 0.5276, # executedQuantity: 20, # fee: 0.006332, # feeAsset: 'USDC', # totalExecutedQuantity: 20, # avgPrice: 0.5276, # averageExecutedPrice: 0.5276, # status: 'FILLED', # reason: '', # totalFee: 0.006332, # visible: 0, # visibleQuantity: 0, # timestamp: 1715179456660, # orderTag: 'CCXT', # createdTime: 1715179456656, # maker: False # } # marketId = self.safe_string(trade, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] price = self.safe_string_2(trade, 'executedPrice', 'price') amount = self.safe_string_2(trade, 'executedQuantity', 'size') cost = Precise.string_mul(price, amount) side = self.safe_string_lower(trade, 'side') timestamp = self.safe_integer(trade, 'timestamp') takerOrMaker = None maker = self.safe_bool(trade, 'maker') if maker is not None: takerOrMaker = 'maker' if maker else 'taker' fee = None feeValue = self.safe_string(trade, 'fee') if feeValue is not None: fee = { 'cost': feeValue, 'currency': self.safe_currency_code(self.safe_string(trade, 'feeAsset')), } return self.safe_trade({ 'id': self.safe_string(trade, 'tradeId'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': symbol, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'order': self.safe_string(trade, 'orderId'), 'takerOrMaker': takerOrMaker, 'type': self.safe_string_lower(trade, 'type'), 'fee': fee, 'info': trade, }, market) def handle_auth(self, client: Client, message): # # { # "event": "auth", # "success": True, # "ts": 1657463158812 # } # messageHash = 'authenticated' success = self.safe_value(message, 'success') if success: # client.resolve(message, messageHash) future = self.safe_value(client.futures, 'authenticated') future.resolve(True) else: error = AuthenticationError(self.json(message)) client.reject(error, messageHash) # allows further authentication attempts if messageHash in client.subscriptions: del client.subscriptions['authenticated'] async def authenticate(self, params={}): self.check_required_credentials() url = self.urls['api']['ws']['private'] + '/' + self.accountId client = self.client(url) messageHash = 'authenticated' event = 'auth' future = client.reusableFuture(messageHash) authenticated = self.safe_value(client.subscriptions, messageHash) if authenticated is None: ts = str(self.nonce()) auth = ts secret = self.secret if secret.find('ed25519:') >= 0: parts = secret.split('ed25519:') secret = parts[1] signature = self.eddsa(self.encode(auth), self.base58_to_binary(secret), 'ed25519') request: dict = { 'event': event, 'params': { 'orderly_key': self.apiKey, 'sign': signature, 'timestamp': ts, }, } message = self.extend(request, params) self.watch(url, messageHash, message, messageHash) return await future async def watch_private(self, messageHash, message, params={}): await self.authenticate(params) url = self.urls['api']['ws']['private'] + '/' + self.accountId requestId = self.request_id(url) subscribe: dict = { 'id': requestId, } request = self.extend(subscribe, message) return await self.watch(url, messageHash, request, messageHash, subscribe) async def watch_private_multiple(self, messageHashes, message, params={}): await self.authenticate(params) url = self.urls['api']['ws']['private'] + '/' + self.accountId requestId = self.request_id(url) subscribe: dict = { 'id': requestId, } request = self.extend(subscribe, message) return await self.watch_multiple(url, messageHashes, request, messageHashes, subscribe) 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://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report :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 bool [params.trigger]: True if trigger order :returns dict[]: a list of `order structures ` """ await self.load_markets() trigger = self.safe_bool_2(params, 'stop', 'trigger', False) topic = 'algoexecutionreport' if (trigger) else 'executionreport' params = self.omit(params, ['stop', 'trigger']) messageHash = topic if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash += ':' + symbol request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) orders = await self.watch_private(messageHash, message) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) 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://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report :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 bool [params.trigger]: True if trigger order :returns dict[]: a list of `order structures ` """ await self.load_markets() trigger = self.safe_bool_2(params, 'stop', 'trigger', False) topic = 'algoexecutionreport' if (trigger) else 'executionreport' params = self.omit(params, 'stop') messageHash = 'myTrades' if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash += ':' + symbol request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) orders = await self.watch_private(messageHash, message) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def parse_ws_order(self, order, market=None): # # { # "symbol": "PERP_BTC_USDT", # "clientOrderId": 0, # "orderId": 52952826, # "type": "LIMIT", # "side": "SELL", # "quantity": 0.01, # "price": 22000, # "tradeId": 0, # "executedPrice": 0, # "executedQuantity": 0, # "fee": 0, # "feeAsset": "USDT", # "totalExecutedQuantity": 0, # "status": "NEW", # "reason": '', # "orderTag": "default", # "totalFee": 0, # "visible": 0.01, # "timestamp": 1657515556799, # "reduceOnly": False, # "maker": False # } # algo order # { # "symbol":"PERP_MATIC_USDC", # "rootAlgoOrderId":123, # "parentAlgoOrderId":123, # "algoOrderId":123, # "orderTag":"some tags", # "algoType": "STOP", # "clientOrderId":"client_id", # "type":"LIMIT", # "side":"BUY", # "quantity":7029.0, # "price":0.7699, # "tradeId":0, # "triggerTradePrice":0, # "triggerTime":1234567, # "triggered": False, # "activated": False, # "executedPrice":0.0, # "executedQuantity":0.0, # "fee":0.0, # "feeAsset":"USDC", # "totalExecutedQuantity":0.0, # "averageExecutedQuantity":0.0, # "avgPrice":0, # "triggerPrice":0.0, # "triggerPriceType":"STOP", # "isActivated": False, # "status":"NEW", # "rootAlgoStatus": "FILLED", # "algoStatus": "FILLED", # "reason":"", # "totalFee":0.0, # "visible": 7029.0, # "visibleQuantity":7029.0, # "timestamp":1704679472448, # "maker":false, # "isMaker":false, # "createdTime":1704679472448 # } # orderId = self.safe_string(order, 'orderId') marketId = self.safe_string(order, 'symbol') market = self.market(marketId) symbol = market['symbol'] timestamp = self.safe_integer(order, 'timestamp') fee = { 'cost': self.safe_string(order, 'totalFee'), 'currency': self.safe_string(order, 'feeAsset'), } priceString = self.safe_string(order, 'price') price = self.safe_number(order, 'price') avgPrice = self.safe_number(order, 'avgPrice') if Precise.string_eq(priceString, '0') and (avgPrice is not None): price = avgPrice amount = self.safe_string(order, 'quantity') side = self.safe_string_lower(order, 'side') type = self.safe_string_lower(order, 'type') filled = self.safe_number(order, 'totalExecutedQuantity') totalExecQuantity = self.safe_string(order, 'totalExecutedQuantity') remaining = amount if Precise.string_ge(amount, totalExecQuantity): remaining = Precise.string_sub(remaining, totalExecQuantity) rawStatus = self.safe_string(order, 'status') status = self.parse_order_status(rawStatus) trades = None clientOrderId = self.safe_string(order, 'clientOrderId') triggerPrice = self.safe_number(order, 'triggerPrice') return self.safe_order({ 'info': order, 'symbol': symbol, 'id': orderId, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': timestamp, 'type': type, 'timeInForce': None, 'postOnly': None, 'side': side, 'price': price, 'stopPrice': triggerPrice, 'triggerPrice': triggerPrice, 'amount': amount, 'cost': None, 'average': None, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': fee, 'trades': trades, }) def handle_order_update(self, client: Client, message): # # { # "topic": "executionreport", # "ts": 1657515556799, # "data": { # "symbol": "PERP_BTC_USDT", # "clientOrderId": 0, # "orderId": 52952826, # "type": "LIMIT", # "side": "SELL", # "quantity": 0.01, # "price": 22000, # "tradeId": 0, # "executedPrice": 0, # "executedQuantity": 0, # "fee": 0, # "feeAsset": "USDT", # "totalExecutedQuantity": 0, # "status": "NEW", # "reason": '', # "orderTag": "default", # "totalFee": 0, # "visible": 0.01, # "timestamp": 1657515556799, # "maker": False # } # } # topic = self.safe_string(message, 'topic') data = self.safe_value(message, 'data') if isinstance(data, list): # algoexecutionreport for i in range(0, len(data)): order = data[i] tradeId = self.omit_zero(self.safe_string(data, 'tradeId')) if tradeId is not None: self.handle_my_trade(client, order) self.handle_order(client, order, topic) else: # executionreport tradeId = self.omit_zero(self.safe_string(data, 'tradeId')) if tradeId is not None: self.handle_my_trade(client, data) self.handle_order(client, data, topic) def handle_order(self, client: Client, message, topic): parsed = self.parse_ws_order(message) symbol = self.safe_string(parsed, 'symbol') orderId = self.safe_string(parsed, 'id') if symbol is not None: if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) cachedOrders = self.orders orders = self.safe_dict(cachedOrders.hashmap, symbol, {}) order = self.safe_dict(orders, orderId) if order is not None: fee = self.safe_value(order, 'fee') if fee is not None: parsed['fee'] = fee fees = self.safe_list(order, 'fees') if fees is not None: parsed['fees'] = fees parsed['trades'] = self.safe_list(order, 'trades') parsed['timestamp'] = self.safe_integer(order, 'timestamp') parsed['datetime'] = self.safe_string(order, 'datetime') cachedOrders.append(parsed) client.resolve(self.orders, topic) messageHashSymbol = topic + ':' + symbol client.resolve(self.orders, messageHashSymbol) def handle_my_trade(self, client: Client, message): # # { # symbol: 'PERP_XRP_USDC', # clientOrderId: '', # orderId: 1167632251, # type: 'MARKET', # side: 'BUY', # quantity: 20, # price: 0, # tradeId: '1715179456664012', # executedPrice: 0.5276, # executedQuantity: 20, # fee: 0.006332, # feeAsset: 'USDC', # totalExecutedQuantity: 20, # avgPrice: 0.5276, # averageExecutedPrice: 0.5276, # status: 'FILLED', # reason: '', # totalFee: 0.006332, # visible: 0, # visibleQuantity: 0, # timestamp: 1715179456660, # orderTag: 'CCXT', # createdTime: 1715179456656, # maker: False # } # messageHash = 'myTrades' marketId = self.safe_string(message, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] trade = self.parse_ws_trade(message, market) trades = self.myTrades if trades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) trades = ArrayCacheBySymbolById(limit) self.myTrades = trades trades.append(trade) client.resolve(trades, messageHash) symbolSpecificMessageHash = messageHash + ':' + symbol client.resolve(trades, symbolSpecificMessageHash) async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/position-push watch all open positions :param str[] [symbols]: list of unified market symbols @param since timestamp in ms of the earliest position to fetch @param limit the maximum number of positions to fetch :param dict params: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `position structure ` """ await self.load_markets() messageHashes = [] symbols = self.market_symbols(symbols) if not self.is_empty(symbols): for i in range(0, len(symbols)): symbol = symbols[i] messageHashes.append('positions::' + symbol) else: messageHashes.append('positions') url = self.urls['api']['ws']['private'] + '/' + self.accountId client = self.client(url) self.set_positions_cache(client, symbols) fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True) awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True) if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None: snapshot = await client.future('fetchPositionsSnapshot') return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True) request: dict = { 'event': 'subscribe', 'topic': 'position', } newPositions = await self.watch_private_multiple(messageHashes, request, params) if self.newUpdates: return newPositions return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True) def set_positions_cache(self, client: Client, type, symbols: Strings = None): fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False) if fetchPositionsSnapshot: messageHash = 'fetchPositionsSnapshot' if not (messageHash in client.futures): client.future(messageHash) self.spawn(self.load_positions_snapshot, client, messageHash) else: self.positions = ArrayCacheBySymbolBySide() async def load_positions_snapshot(self, client, messageHash): positions = await self.fetch_positions() self.positions = ArrayCacheBySymbolBySide() cache = self.positions for i in range(0, len(positions)): position = positions[i] contracts = self.safe_string(position, 'contracts', '0') if Precise.string_gt(contracts, '0'): cache.append(position) # don't remove the future from the .futures cache future = client.futures[messageHash] future.resolve(cache) client.resolve(cache, 'positions') def handle_positions(self, client, message): # # { # "topic":"position", # "ts":1705292345255, # "data":{ # "positions":[ # { # "symbol":"PERP_ETH_USDC", # "positionQty":3.1408, # "costPosition":5706.51952, # "lastSumUnitaryFunding":0.804, # "sumUnitaryFundingVersion":0, # "pendingLongQty":0.0, # "pendingShortQty":-1.0, # "settlePrice":1816.9, # "averageOpenPrice":1804.51490427, # "unsettledPnl":-2.79856, # "pnl24H":-338.90179488, # "fee24H":4.242423, # "markPrice":1816.2, # "estLiqPrice":0.0, # "version":179967, # "imrwithOrders":0.1, # "mmrwithOrders":0.05, # "mmr":0.05, # "imr":0.1, # "timestamp":1685154032762 # } # ] # } # } # data = self.safe_dict(message, 'data', {}) rawPositions = self.safe_list(data, 'positions', []) if self.positions is None: self.positions = ArrayCacheBySymbolBySide() cache = self.positions newPositions = [] for i in range(0, len(rawPositions)): rawPosition = rawPositions[i] marketId = self.safe_string(rawPosition, 'symbol') market = self.safe_market(marketId) position = self.parse_ws_position(rawPosition, market) newPositions.append(position) cache.append(position) messageHash = 'positions::' + market['symbol'] client.resolve(position, messageHash) client.resolve(newPositions, 'positions') def parse_ws_position(self, position, market=None): # # { # "symbol":"PERP_ETH_USDC", # "positionQty":3.1408, # "costPosition":5706.51952, # "lastSumUnitaryFunding":0.804, # "sumUnitaryFundingVersion":0, # "pendingLongQty":0.0, # "pendingShortQty":-1.0, # "settlePrice":1816.9, # "averageOpenPrice":1804.51490427, # "unsettledPnl":-2.79856, # "pnl24H":-338.90179488, # "fee24H":4.242423, # "markPrice":1816.2, # "estLiqPrice":0.0, # "version":179967, # "imrwithOrders":0.1, # "mmrwithOrders":0.05, # "mmr":0.05, # "imr":0.1, # "timestamp":1685154032762 # } # contract = self.safe_string(position, 'symbol') market = self.safe_market(contract, market) size = self.safe_string(position, 'positionQty') side: Str = None if Precise.string_gt(size, '0'): side = 'long' else: side = 'short' contractSize = self.safe_string(market, 'contractSize') markPrice = self.safe_string(position, 'markPrice') timestamp = self.safe_integer(position, 'timestamp') entryPrice = self.safe_string(position, 'averageOpenPrice') unrealisedPnl = self.safe_string(position, 'unsettledPnl') size = Precise.string_abs(size) notional = Precise.string_mul(size, markPrice) return self.safe_position({ 'info': position, 'id': None, 'symbol': self.safe_string(market, 'symbol'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastUpdateTimestamp': None, 'initialMargin': None, 'initialMarginPercentage': None, 'maintenanceMargin': None, 'maintenanceMarginPercentage': None, 'entryPrice': self.parse_number(entryPrice), 'notional': self.parse_number(notional), 'leverage': None, 'unrealizedPnl': self.parse_number(unrealisedPnl), 'contracts': self.parse_number(size), 'contractSize': self.parse_number(contractSize), 'marginRatio': None, 'liquidationPrice': self.safe_number(position, 'estLiqPrice'), 'markPrice': self.parse_number(markPrice), 'lastPrice': None, 'collateral': None, 'marginMode': 'cross', 'marginType': None, 'side': side, 'percentage': None, 'hedged': None, 'stopLossPrice': None, 'takeProfitPrice': None, }) async def watch_balance(self, params={}) -> Balances: """ watch balance and get the amount of funds available for trading or funds locked in orders https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/balance :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ await self.load_markets() topic = 'balance' messageHash = topic request: dict = { 'event': 'subscribe', 'topic': topic, } message = self.extend(request, params) return await self.watch_private(messageHash, message) def handle_balance(self, client, message): # # { # "topic":"balance", # "ts":1651836695254, # "data":{ # "balances":{ # "USDC":{ # "holding":5555815.47398272, # "frozen":0, # "interest":0, # "pendingShortQty":0, # "pendingExposure":0, # "pendingLongQty":0, # "pendingLongExposure":0, # "version":894, # "staked":51370692, # "unbonding":0, # "vault":0, # "averageOpenPrice":0.00000574, # "pnl24H":0, # "fee24H":0.01914, # "markPrice":0.31885 # } # } # } # } # data = self.safe_dict(message, 'data', {}) balances = self.safe_dict(data, 'balances', {}) keys = list(balances.keys()) ts = self.safe_integer(message, 'ts') self.balance['info'] = data self.balance['timestamp'] = ts self.balance['datetime'] = self.iso8601(ts) for i in range(0, len(keys)): key = keys[i] value = balances[key] code = self.safe_currency_code(key) account = self.balance[code] if (code in self.balance) else self.account() total = self.safe_string(value, 'holding') used = self.safe_string(value, 'frozen') account['total'] = total account['used'] = used account['free'] = Precise.string_sub(total, used) self.balance[code] = account self.balance = self.safe_balance(self.balance) client.resolve(self.balance, 'balance') def handle_error_message(self, client: Client, message) -> Bool: # # {"id":"1","event":"subscribe","success":false,"ts":1710780997216,"errorMsg":"Auth is needed."} # if not ('success' in message): return False success = self.safe_bool(message, 'success') if success: return False errorMessage = self.safe_string(message, 'errorMsg') try: if errorMessage is not None: feedback = self.id + ' ' + self.json(message) self.throw_exactly_matched_exception(self.exceptions['exact'], errorMessage, feedback) return False except Exception as error: if isinstance(error, AuthenticationError): messageHash = 'authenticated' client.reject(error, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] else: client.reject(error) return True def handle_message(self, client: Client, message): if self.handle_error_message(client, message): return methods: dict = { 'ping': self.handle_ping, 'pong': self.handle_pong, 'subscribe': self.handle_subscribe, 'orderbook': self.handle_order_book, 'ticker': self.handle_ticker, 'tickers': self.handle_tickers, 'kline': self.handle_ohlcv, 'trade': self.handle_trade, 'auth': self.handle_auth, 'executionreport': self.handle_order_update, 'algoexecutionreport': self.handle_order_update, 'position': self.handle_positions, 'balance': self.handle_balance, 'bbos': self.handle_bid_ask, } event = self.safe_string(message, 'event') method = self.safe_value(methods, event) if method is not None: method(client, message) return topic = self.safe_string(message, 'topic') if topic is not None: method = self.safe_value(methods, topic) if method is not None: method(client, message) return splitTopic = topic.split('@') splitLength = len(splitTopic) if splitLength == 2: name = self.safe_string(splitTopic, 1) method = self.safe_value(methods, name) if method is not None: method(client, message) return splitName = name.split('_') splitNameLength = len(splitTopic) if splitNameLength == 2: method = self.safe_value(methods, self.safe_string(splitName, 0)) if method is not None: method(client, message) def ping(self, client: Client): return {'event': 'ping'} def handle_ping(self, client: Client, message): return {'event': 'pong'} def handle_pong(self, client: Client, message): # # {event: "pong", ts: 1614667590000} # client.lastPong = self.milliseconds() return message def handle_subscribe(self, client: Client, message): # # { # "id": "666888", # "event": "subscribe", # "success": True, # "ts": 1657117712212 # } # return message