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

1272 lines
51 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, 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 <https://docs.ccxt.com/#/?id=order-book-structure>` 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/en/latest/manual.html#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 <https://docs.ccxt.com/#/?id=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