1497 lines
62 KiB
Python
1497 lines
62 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, ArrayCacheByTimestamp
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
|
|
from ccxt.async_support.base.ws.client import Client
|
|
from typing import List
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class phemex(ccxt.async_support.phemex):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(phemex, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchTicker': True,
|
|
'watchTickers': True,
|
|
'watchTrades': True,
|
|
'watchMyTrades': True,
|
|
'watchOrders': True,
|
|
'watchOrderBook': True,
|
|
'watchOHLCV': True,
|
|
'watchPositions': None, # TODO
|
|
# mutli-endpoints are not supported: https://github.com/ccxt/ccxt/pull/21490
|
|
'watchOrderBookForSymbols': False,
|
|
'watchTradesForSymbols': False,
|
|
'watchOHLCVForSymbols': False,
|
|
'watchBalance': True,
|
|
},
|
|
'urls': {
|
|
'test': {
|
|
'ws': 'wss://testnet-api.phemex.com/ws',
|
|
},
|
|
'api': {
|
|
'ws': 'wss://ws.phemex.com',
|
|
},
|
|
},
|
|
'options': {
|
|
'tradesLimit': 1000,
|
|
'OHLCVLimit': 1000,
|
|
},
|
|
'streaming': {
|
|
'keepAlive': 9000,
|
|
},
|
|
})
|
|
|
|
def from_en(self, en, scale):
|
|
if en is None:
|
|
return None
|
|
precise = Precise(en)
|
|
precise.decimals = self.sum(precise.decimals, scale)
|
|
precise.reduce()
|
|
return str(precise)
|
|
|
|
def from_ep(self, ep, market=None):
|
|
if (ep is None) or (market is None):
|
|
return ep
|
|
return self.from_en(ep, self.safe_integer(market, 'priceScale'))
|
|
|
|
def from_ev(self, ev, market=None):
|
|
if (ev is None) or (market is None):
|
|
return ev
|
|
return self.from_en(ev, self.safe_integer(market, 'valueScale'))
|
|
|
|
def from_er(self, er, market=None):
|
|
if (er is None) or (market is None):
|
|
return er
|
|
return self.from_en(er, self.safe_integer(market, 'ratioScale'))
|
|
|
|
def request_id(self):
|
|
requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
|
|
self.options['requestId'] = requestId
|
|
return requestId
|
|
|
|
def parse_swap_ticker(self, ticker, market=None):
|
|
#
|
|
# {
|
|
# "close": 442800,
|
|
# "fundingRate": 10000,
|
|
# "high": 445400,
|
|
# "indexPrice": 442621,
|
|
# "low": 428400,
|
|
# "markPrice": 442659,
|
|
# "open": 432200,
|
|
# "openInterest": 744183,
|
|
# "predFundingRate": 10000,
|
|
# "symbol": "LTCUSD",
|
|
# "turnover": 8133238294,
|
|
# "volume": 934292
|
|
# }
|
|
#
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
timestamp = self.safe_integer_product(ticker, 'timestamp', 0.000001)
|
|
lastString = self.from_ep(self.safe_string(ticker, 'close'), market)
|
|
last = self.parse_number(lastString)
|
|
quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'turnover'), market))
|
|
baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'volume'), market))
|
|
change = None
|
|
percentage = None
|
|
average = None
|
|
openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 'open'), market))
|
|
open = self.parse_number(openString)
|
|
if (openString is not None) and (lastString is not None):
|
|
change = self.parse_number(Precise.string_sub(lastString, openString))
|
|
average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2'))
|
|
percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100'))
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.parse_number(self.from_ep(self.safe_string(ticker, 'high'), market)),
|
|
'low': self.parse_number(self.from_ep(self.safe_string(ticker, 'low'), market)),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None, # previous day close
|
|
'change': change,
|
|
'percentage': percentage,
|
|
'average': average,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'markPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'markPrice'), market)),
|
|
'indexPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'indexPrice'), market)),
|
|
'info': ticker,
|
|
})
|
|
|
|
def parse_perpetual_ticker(self, ticker, market=None):
|
|
#
|
|
# [
|
|
# "STXUSDT",
|
|
# "0.64649",
|
|
# "0.8628",
|
|
# "0.61215",
|
|
# "0.71737",
|
|
# "4519387",
|
|
# "3210827.98166",
|
|
# "697635",
|
|
# "0.71720205",
|
|
# "0.71720205",
|
|
# "0.0001",
|
|
# "0.0001",
|
|
# ]
|
|
#
|
|
marketId = self.safe_string(ticker, 0)
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
lastString = self.from_ep(self.safe_string(ticker, 4), market)
|
|
last = self.parse_number(lastString)
|
|
quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 6), market))
|
|
baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 5), market))
|
|
change = None
|
|
percentage = None
|
|
average = None
|
|
openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 1), market))
|
|
open = self.parse_number(openString)
|
|
if (openString is not None) and (lastString is not None):
|
|
change = self.parse_number(Precise.string_sub(lastString, openString))
|
|
average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2'))
|
|
percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100'))
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'high': self.parse_number(self.from_ep(self.safe_string(ticker, 2), market)),
|
|
'low': self.parse_number(self.from_ep(self.safe_string(ticker, 3), market)),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None, # previous day close
|
|
'change': change,
|
|
'percentage': percentage,
|
|
'average': average,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'info': ticker,
|
|
})
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "spot_market24h": {
|
|
# "askEp": 958148000000,
|
|
# "bidEp": 957884000000,
|
|
# "highEp": 962000000000,
|
|
# "lastEp": 958220000000,
|
|
# "lowEp": 928049000000,
|
|
# "openEp": 935597000000,
|
|
# "symbol": "sBTCUSDT",
|
|
# "turnoverEv": 146074214388978,
|
|
# "volumeEv": 15492228900
|
|
# },
|
|
# "timestamp": 1592847265888272100
|
|
# }
|
|
#
|
|
# swap
|
|
#
|
|
# {
|
|
# "market24h": {
|
|
# "close": 442800,
|
|
# "fundingRate": 10000,
|
|
# "high": 445400,
|
|
# "indexPrice": 442621,
|
|
# "low": 428400,
|
|
# "markPrice": 442659,
|
|
# "open": 432200,
|
|
# "openInterest": 744183,
|
|
# "predFundingRate": 10000,
|
|
# "symbol": "LTCUSD",
|
|
# "turnover": 8133238294,
|
|
# "volume": 934292
|
|
# },
|
|
# "timestamp": 1592845585373374500
|
|
# }
|
|
#
|
|
# perpetual
|
|
#
|
|
# {
|
|
# "data": [
|
|
# [
|
|
# "STXUSDT",
|
|
# "0.64649",
|
|
# "0.8628",
|
|
# "0.61215",
|
|
# "0.71737",
|
|
# "4519387",
|
|
# "3210827.98166",
|
|
# "697635",
|
|
# "0.71720205",
|
|
# "0.71720205",
|
|
# "0.0001",
|
|
# "0.0001",
|
|
# ],
|
|
# ...
|
|
# ],
|
|
# "fields": [
|
|
# "symbol",
|
|
# "openRp",
|
|
# "highRp",
|
|
# "lowRp",
|
|
# "lastRp",
|
|
# "volumeRq",
|
|
# "turnoverRv",
|
|
# "openInterestRv",
|
|
# "indexRp",
|
|
# "markRp",
|
|
# "fundingRateRr",
|
|
# "predFundingRateRr",
|
|
# ],
|
|
# "method": "perp_market24h_pack_p.update",
|
|
# "timestamp": "1677094918686806209",
|
|
# "type": "snapshot",
|
|
# }
|
|
#
|
|
tickers = []
|
|
if 'market24h' in message:
|
|
ticker = self.safe_value(message, 'market24h')
|
|
tickers.append(self.parse_swap_ticker(ticker))
|
|
elif 'spot_market24h' in message:
|
|
ticker = self.safe_value(message, 'spot_market24h')
|
|
tickers.append(self.parse_ticker(ticker))
|
|
elif 'data' in message:
|
|
data = self.safe_value(message, 'data', [])
|
|
for i in range(0, len(data)):
|
|
tickers.append(self.parse_perpetual_ticker(data[i]))
|
|
for i in range(0, len(tickers)):
|
|
ticker = tickers[i]
|
|
symbol = ticker['symbol']
|
|
messageHash = 'ticker:' + symbol
|
|
timestamp = self.safe_integer_product(message, 'timestamp', 0.000001)
|
|
ticker['timestamp'] = timestamp
|
|
ticker['datetime'] = self.iso8601(timestamp)
|
|
self.tickers[symbol] = ticker
|
|
client.resolve(ticker, messageHash)
|
|
|
|
async def watch_balance(self, params={}) -> Balances:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-account-order-position-aop
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-account-order-position-aop
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-wallet-order-messages
|
|
|
|
watch balance and get the amount of funds available for trading or funds locked in orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.settle]: set to USDT to use hedged perpetual api
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('watchBalance', None, params)
|
|
usePerpetualApi = self.safe_string(params, 'settle') == 'USDT'
|
|
messageHash = ':balance'
|
|
messageHash = 'perpetual' + messageHash if usePerpetualApi else type + messageHash
|
|
return await self.subscribe_private(type, messageHash, params)
|
|
|
|
def handle_balance(self, type, client, message):
|
|
# spot
|
|
# [
|
|
# {
|
|
# "balanceEv": 0,
|
|
# "currency": "BTC",
|
|
# "lastUpdateTimeNs": "1650442638722099092",
|
|
# "lockedTradingBalanceEv": 0,
|
|
# "lockedWithdrawEv": 0,
|
|
# "userID": 2647224
|
|
# },
|
|
# {
|
|
# "balanceEv": 1154232337,
|
|
# "currency": "USDT",
|
|
# "lastUpdateTimeNs": "1650442617610017597",
|
|
# "lockedTradingBalanceEv": 0,
|
|
# "lockedWithdrawEv": 0,
|
|
# "userID": 2647224
|
|
# }
|
|
# ]
|
|
# swap
|
|
# [
|
|
# {
|
|
# "accountBalanceEv": 0,
|
|
# "accountID": 26472240001,
|
|
# "bonusBalanceEv": 0,
|
|
# "currency": "BTC",
|
|
# "totalUsedBalanceEv": 0,
|
|
# "userID": 2647224
|
|
# }
|
|
# ]
|
|
# perpetual
|
|
# [
|
|
# {
|
|
# "accountBalanceRv": "1508.452588802237",
|
|
# "accountID": 9328670003,
|
|
# "bonusBalanceRv": "0",
|
|
# "currency": "USDT",
|
|
# "totalUsedBalanceRv": "343.132599666883",
|
|
# "userID": 932867
|
|
# }
|
|
# ]
|
|
#
|
|
self.balance['info'] = message
|
|
for i in range(0, len(message)):
|
|
balance = message[i]
|
|
currencyId = self.safe_string(balance, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
currency = self.safe_value(self.currencies, code, {})
|
|
scale = self.safe_integer(currency, 'valueScale', 8)
|
|
account = self.account()
|
|
used = self.safe_string(balance, 'totalUsedBalanceRv')
|
|
if used is None:
|
|
usedEv = self.safe_string(balance, 'totalUsedBalanceEv')
|
|
if usedEv is None:
|
|
lockedTradingBalanceEv = self.safe_string(balance, 'lockedTradingBalanceEv')
|
|
lockedWithdrawEv = self.safe_string_2(balance, 'lockedWithdrawEv', 'lockedWithdrawRv')
|
|
usedEv = Precise.string_add(lockedTradingBalanceEv, lockedWithdrawEv)
|
|
used = self.from_en(usedEv, scale)
|
|
total = self.safe_string(balance, 'accountBalanceRv')
|
|
if total is None:
|
|
totalEv = self.safe_string_2(balance, 'accountBalanceEv', 'balanceEv')
|
|
total = self.from_en(totalEv, scale)
|
|
account['used'] = used
|
|
account['total'] = total
|
|
self.balance[code] = account
|
|
self.balance = self.safe_balance(self.balance)
|
|
messageHash = type + ':balance'
|
|
client.resolve(self.balance, messageHash)
|
|
|
|
def handle_trades(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "sequence": 1795484727,
|
|
# "symbol": "sBTCUSDT",
|
|
# "trades": [
|
|
# [1592891002064516600, "Buy", 964020000000, 1431000],
|
|
# [1592890978987934500, "Sell", 963704000000, 1401800],
|
|
# [1592890972918701800, "Buy", 963938000000, 2018600],
|
|
# ],
|
|
# "type": "snapshot"
|
|
# }
|
|
# perpetual
|
|
# {
|
|
# "sequence": 1230197759,
|
|
# "symbol": "BTCUSDT",
|
|
# "trades_p": [
|
|
# [
|
|
# 1677094244729433000,
|
|
# "Buy",
|
|
# "23800.4",
|
|
# "2.455",
|
|
# ],
|
|
# ],
|
|
# "type": "snapshot",
|
|
# }
|
|
#
|
|
name = 'trade'
|
|
marketId = self.safe_string(message, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
messageHash = name + ':' + 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
|
|
trades = self.safe_value_2(message, 'trades', 'trades_p', [])
|
|
parsed = self.parse_trades(trades, market)
|
|
for i in range(0, len(parsed)):
|
|
stored.append(parsed[i])
|
|
client.resolve(stored, messageHash)
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "kline": [
|
|
# [1592905200, 60, 960688000000, 960709000000, 960709000000, 960400000000, 960400000000, 848100, 8146756046],
|
|
# [1592905140, 60, 960718000000, 960716000000, 960717000000, 960560000000, 960688000000, 4284900, 41163743512],
|
|
# [1592905080, 60, 960513000000, 960684000000, 960718000000, 960684000000, 960718000000, 4880500, 46887494349],
|
|
# ],
|
|
# "sequence": 1804401474,
|
|
# "symbol": "sBTCUSDT",
|
|
# "type": "snapshot"
|
|
# }
|
|
# perpetual
|
|
# {
|
|
# "kline_p": [
|
|
# [
|
|
# 1677094560,
|
|
# 60,
|
|
# "23746.2",
|
|
# "23746.1",
|
|
# "23757.6",
|
|
# "23736.9",
|
|
# "23754.8",
|
|
# "34.273",
|
|
# "813910.208",
|
|
# ],
|
|
# ],
|
|
# "sequence": 1230786017,
|
|
# "symbol": "BTCUSDT",
|
|
# "type": "incremental",
|
|
# }
|
|
#
|
|
marketId = self.safe_string(message, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
candles = self.safe_value_2(message, 'kline', 'kline_p', [])
|
|
first = self.safe_value(candles, 0, [])
|
|
interval = self.safe_string(first, 1)
|
|
timeframe = self.find_timeframe(interval)
|
|
if timeframe is not None:
|
|
messageHash = 'kline:' + timeframe + ':' + symbol
|
|
ohlcvs = self.parse_ohlcvs(candles, market)
|
|
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
|
|
for i in range(0, len(ohlcvs)):
|
|
candle = ohlcvs[i]
|
|
stored.append(candle)
|
|
client.resolve(stored, messageHash)
|
|
|
|
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-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()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
isSwap = market['swap']
|
|
settleIsUSDT = market['settle'] == 'USDT'
|
|
name = 'spot_market24h'
|
|
if isSwap:
|
|
name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h'
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
subscriptionHash = name + '.subscribe'
|
|
messageHash = 'ticker:' + symbol
|
|
subscribe: dict = {
|
|
'method': subscriptionHash,
|
|
'id': requestId,
|
|
'params': [],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
return await self.watch(url, messageHash, request, subscriptionHash)
|
|
|
|
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-ticker
|
|
|
|
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
|
|
:param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, False)
|
|
first = symbols[0]
|
|
market = self.market(first)
|
|
isSwap = market['swap']
|
|
settleIsUSDT = market['settle'] == 'USDT'
|
|
name = 'spot_market24h'
|
|
if isSwap:
|
|
name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h'
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
subscriptionHash = name + '.subscribe'
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
messageHashes.append('ticker:' + symbols[i])
|
|
subscribe: dict = {
|
|
'method': subscriptionHash,
|
|
'id': requestId,
|
|
'params': [],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
ticker = await self.watch_multiple(url, messageHashes, request, messageHashes)
|
|
if self.newUpdates:
|
|
result: dict = {}
|
|
result[ticker['symbol']] = ticker
|
|
return result
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-trade
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-trade
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-trade
|
|
|
|
get the list of most recent trades for a particular symbol
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
isSwap = market['swap']
|
|
settleIsUSDT = market['settle'] == 'USDT'
|
|
name = 'trade_p' if (isSwap and settleIsUSDT) else 'trade'
|
|
messageHash = 'trade:' + symbol
|
|
method = name + '.subscribe'
|
|
subscribe: dict = {
|
|
'method': method,
|
|
'id': requestId,
|
|
'params': [
|
|
market['id'],
|
|
],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
trades = await self.watch(url, messageHash, request, messageHash)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-orderbook
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-orderbook-for-new-model
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-30-levels-orderbook
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-full-orderbook
|
|
|
|
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
isSwap = market['swap']
|
|
settleIsUSDT = market['settle'] == 'USDT'
|
|
name = 'orderbook_p' if (isSwap and settleIsUSDT) else 'orderbook'
|
|
messageHash = 'orderbook:' + symbol
|
|
method = name + '.subscribe'
|
|
subscribe: dict = {
|
|
'method': method,
|
|
'id': requestId,
|
|
'params': [
|
|
market['id'],
|
|
],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
orderbook = await self.watch(url, messageHash, request, messageHash)
|
|
return orderbook.limit()
|
|
|
|
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-kline
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-kline
|
|
https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-kline
|
|
|
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
: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']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
isSwap = market['swap']
|
|
settleIsUSDT = market['settle'] == 'USDT'
|
|
name = 'kline_p' if (isSwap and settleIsUSDT) else 'kline'
|
|
messageHash = 'kline:' + timeframe + ':' + symbol
|
|
method = name + '.subscribe'
|
|
subscribe: dict = {
|
|
'method': method,
|
|
'id': requestId,
|
|
'params': [
|
|
market['id'],
|
|
self.safe_integer(self.timeframes, timeframe),
|
|
],
|
|
}
|
|
request = self.deep_extend(subscribe, params)
|
|
ohlcv = await self.watch(url, messageHash, request, messageHash)
|
|
if self.newUpdates:
|
|
limit = ohlcv.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
|
|
|
def custom_handle_delta(self, bookside, delta, market=None):
|
|
bidAsk = self.custom_parse_bid_ask(delta, 0, 1, market)
|
|
bookside.storeArray(bidAsk)
|
|
|
|
def custom_handle_deltas(self, bookside, deltas, market=None):
|
|
for i in range(0, len(deltas)):
|
|
self.custom_handle_delta(bookside, deltas[i], market)
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "book": {
|
|
# "asks": [
|
|
# [960316000000, 6993800],
|
|
# [960318000000, 13183000],
|
|
# [960319000000, 9170200],
|
|
# ],
|
|
# "bids": [
|
|
# [959941000000, 8385300],
|
|
# [959939000000, 10296600],
|
|
# [959930000000, 3672400],
|
|
# ]
|
|
# },
|
|
# "depth": 30,
|
|
# "sequence": 1805784701,
|
|
# "symbol": "sBTCUSDT",
|
|
# "timestamp": 1592908460404461600,
|
|
# "type": "snapshot"
|
|
# }
|
|
# perpetual
|
|
# {
|
|
# "depth": 30,
|
|
# "orderbook_p": {
|
|
# "asks": [
|
|
# [
|
|
# "23788.5",
|
|
# "0.13",
|
|
# ],
|
|
# ],
|
|
# "bids": [
|
|
# [
|
|
# "23787.8",
|
|
# "1.836",
|
|
# ],
|
|
# ],
|
|
# },
|
|
# "sequence": 1230347368,
|
|
# "symbol": "BTCUSDT",
|
|
# "timestamp": "1677093457306978852",
|
|
# "type": "snapshot",
|
|
# }
|
|
#
|
|
marketId = self.safe_string(message, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
type = self.safe_string(message, 'type')
|
|
depth = self.safe_integer(message, 'depth')
|
|
name = 'orderbook'
|
|
messageHash = name + ':' + symbol
|
|
nonce = self.safe_integer(message, 'sequence')
|
|
timestamp = self.safe_integer_product(message, 'timestamp', 0.000001)
|
|
if type == 'snapshot':
|
|
book = self.safe_value_2(message, 'book', 'orderbook_p', {})
|
|
snapshot = self.custom_parse_order_book(book, symbol, timestamp, 'bids', 'asks', 0, 1, market)
|
|
snapshot['nonce'] = nonce
|
|
orderbook = self.order_book(snapshot, depth)
|
|
self.orderbooks[symbol] = orderbook
|
|
client.resolve(orderbook, messageHash)
|
|
else:
|
|
if symbol in self.orderbooks:
|
|
orderbook = self.orderbooks[symbol]
|
|
changes = self.safe_dict_2(message, 'book', 'orderbook_p', {})
|
|
asks = self.safe_list(changes, 'asks', [])
|
|
bids = self.safe_list(changes, 'bids', [])
|
|
self.custom_handle_deltas(orderbook['asks'], asks, market)
|
|
self.custom_handle_deltas(orderbook['bids'], bids, market)
|
|
orderbook['nonce'] = nonce
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
self.orderbooks[symbol] = orderbook
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
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
|
|
: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 = None
|
|
type = None
|
|
messageHash = 'trades:'
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = messageHash + market['symbol']
|
|
if market['settle'] == 'USDT':
|
|
params = self.extend(params)
|
|
params['settle'] = 'USDT'
|
|
type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
|
|
if symbol is None:
|
|
settle = self.safe_string(params, 'settle')
|
|
messageHash = (messageHash + 'perpetual') if (settle == 'USDT') else (messageHash + type)
|
|
trades = await self.subscribe_private(type, 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_trades(self, client: Client, message):
|
|
#
|
|
# swap
|
|
# [
|
|
# {
|
|
# "avgPriceEp":4138763000000,
|
|
# "baseCurrency":"BTC",
|
|
# "baseQtyEv":0,
|
|
# "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
|
|
# "execBaseQtyEv":30100,
|
|
# "execFeeEv":31,
|
|
# "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
|
|
# "execPriceEp":4138763000000,
|
|
# "execQuoteQtyEv":1245767663,
|
|
# "feeCurrency":"BTC",
|
|
# "lastLiquidityInd":"RemovedLiquidity",
|
|
# "ordType":"Market",
|
|
# "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
|
|
# "priceEp":4549022000000,
|
|
# "qtyType":"ByQuote",
|
|
# "quoteCurrency":"USDT",
|
|
# "quoteQtyEv":1248000000,
|
|
# "side":"Buy",
|
|
# "symbol":"sBTCUSDT",
|
|
# "tradeType":"Trade",
|
|
# "transactTimeNs":"1650442617609928764",
|
|
# "userID":2647224
|
|
# }
|
|
# ]
|
|
# perpetual
|
|
# [
|
|
# {
|
|
# "accountID": 9328670003,
|
|
# "action": "New",
|
|
# "actionBy": "ByUser",
|
|
# "actionTimeNs": 1666858780876924611,
|
|
# "addedSeq": 77751555,
|
|
# "apRp": "0",
|
|
# "bonusChangedAmountRv": "0",
|
|
# "bpRp": "0",
|
|
# "clOrdID": "c0327a7d-9064-62a9-28f6-2db9aaaa04e0",
|
|
# "closedPnlRv": "0",
|
|
# "closedSize": "0",
|
|
# "code": 0,
|
|
# "cumFeeRv": "0",
|
|
# "cumQty": "0",
|
|
# "cumValueRv": "0",
|
|
# "curAccBalanceRv": "1508.489893982237",
|
|
# "curAssignedPosBalanceRv": "24.62786650928",
|
|
# "curBonusBalanceRv": "0",
|
|
# "curLeverageRr": "-10",
|
|
# "curPosSide": "Buy",
|
|
# "curPosSize": "0.043",
|
|
# "curPosTerm": 1,
|
|
# "curPosValueRv": "894.0689",
|
|
# "curRiskLimitRv": "1000000",
|
|
# "currency": "USDT",
|
|
# "cxlRejReason": 0,
|
|
# "displayQty": "0.003",
|
|
# "execFeeRv": "0",
|
|
# "execID": "00000000-0000-0000-0000-000000000000",
|
|
# "execPriceRp": "20723.7",
|
|
# "execQty": "0",
|
|
# "execSeq": 77751555,
|
|
# "execStatus": "New",
|
|
# "execValueRv": "0",
|
|
# "feeRateRr": "0",
|
|
# "leavesQty": "0.003",
|
|
# "leavesValueRv": "63.4503",
|
|
# "message": "No error",
|
|
# "ordStatus": "New",
|
|
# "ordType": "Market",
|
|
# "orderID": "fa64c6f2-47a4-4929-aab4-b7fa9bbc4323",
|
|
# "orderQty": "0.003",
|
|
# "pegOffsetValueRp": "0",
|
|
# "posSide": "Long",
|
|
# "priceRp": "21150.1",
|
|
# "relatedPosTerm": 1,
|
|
# "relatedReqNum": 11,
|
|
# "side": "Buy",
|
|
# "slTrigger": "ByMarkPrice",
|
|
# "stopLossRp": "0",
|
|
# "stopPxRp": "0",
|
|
# "symbol": "BTCUSDT",
|
|
# "takeProfitRp": "0",
|
|
# "timeInForce": "ImmediateOrCancel",
|
|
# "tpTrigger": "ByLastPrice",
|
|
# "tradeType": "Amend",
|
|
# "transactTimeNs": 1666858780881545305,
|
|
# "userID": 932867
|
|
# },
|
|
# ...
|
|
# ]
|
|
#
|
|
channel = 'trades'
|
|
tradesLength = len(message)
|
|
if tradesLength == 0:
|
|
return
|
|
cachedTrades = self.myTrades
|
|
if cachedTrades is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
cachedTrades = ArrayCacheBySymbolById(limit)
|
|
marketIds: dict = {}
|
|
type = None
|
|
for i in range(0, len(message)):
|
|
rawTrade = message[i]
|
|
marketId = self.safe_string(rawTrade, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
parsed = self.parse_trade(rawTrade)
|
|
cachedTrades.append(parsed)
|
|
symbol = parsed['symbol']
|
|
if type is None:
|
|
type = 'perpetual' if (market['settle'] == 'USDT') else market['type']
|
|
marketIds[symbol] = True
|
|
keys = list(marketIds.keys())
|
|
for i in range(0, len(keys)):
|
|
market = keys[i]
|
|
hash = channel + ':' + market
|
|
client.resolve(cachedTrades, hash)
|
|
# generic subscription
|
|
messageHash = channel + ':' + type
|
|
client.resolve(cachedTrades, messageHash)
|
|
|
|
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
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
messageHash = 'orders:'
|
|
market = None
|
|
type = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = messageHash + market['symbol']
|
|
if market['settle'] == 'USDT':
|
|
params = self.extend(params)
|
|
params['settle'] = 'USDT'
|
|
type, params = self.handle_market_type_and_params('watchOrders', market, params)
|
|
isUSDTSettled = self.safe_string(params, 'settle') == 'USDT'
|
|
if symbol is None:
|
|
messageHash = (messageHash + 'perpetual') if (isUSDTSettled) else (messageHash + type)
|
|
orders = await self.subscribe_private(type, messageHash, 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):
|
|
# spot update
|
|
# {
|
|
# "closed":[
|
|
# {
|
|
# "action":"New",
|
|
# "avgPriceEp":4138763000000,
|
|
# "baseCurrency":"BTC",
|
|
# "baseQtyEv":0,
|
|
# "bizError":0,
|
|
# "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
|
|
# "createTimeNs":"1650442617606017583",
|
|
# "cumBaseQtyEv":30100,
|
|
# "cumFeeEv":31,
|
|
# "cumQuoteQtyEv":1245767663,
|
|
# "cxlRejReason":0,
|
|
# "feeCurrency":"BTC",
|
|
# "leavesBaseQtyEv":0,
|
|
# "leavesQuoteQtyEv":0,
|
|
# "ordStatus":"Filled",
|
|
# "ordType":"Market",
|
|
# "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
|
|
# "pegOffsetValueEp":0,
|
|
# "priceEp":4549022000000,
|
|
# "qtyType":"ByQuote",
|
|
# "quoteCurrency":"USDT",
|
|
# "quoteQtyEv":1248000000,
|
|
# "side":"Buy",
|
|
# "stopPxEp":0,
|
|
# "symbol":"sBTCUSDT",
|
|
# "timeInForce":"ImmediateOrCancel",
|
|
# "tradeType":"Trade",
|
|
# "transactTimeNs":"1650442617609928764",
|
|
# "triggerTimeNs":0,
|
|
# "userID":2647224
|
|
# }
|
|
# ],
|
|
# "fills":[
|
|
# {
|
|
# "avgPriceEp":4138763000000,
|
|
# "baseCurrency":"BTC",
|
|
# "baseQtyEv":0,
|
|
# "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
|
|
# "execBaseQtyEv":30100,
|
|
# "execFeeEv":31,
|
|
# "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
|
|
# "execPriceEp":4138763000000,
|
|
# "execQuoteQtyEv":1245767663,
|
|
# "feeCurrency":"BTC",
|
|
# "lastLiquidityInd":"RemovedLiquidity",
|
|
# "ordType":"Market",
|
|
# "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
|
|
# "priceEp":4549022000000,
|
|
# "qtyType":"ByQuote",
|
|
# "quoteCurrency":"USDT",
|
|
# "quoteQtyEv":1248000000,
|
|
# "side":"Buy",
|
|
# "symbol":"sBTCUSDT",
|
|
# "tradeType":"Trade",
|
|
# "transactTimeNs":"1650442617609928764",
|
|
# "userID":2647224
|
|
# }
|
|
# ],
|
|
# "open":[
|
|
# {
|
|
# "action":"New",
|
|
# "avgPriceEp":0,
|
|
# "baseCurrency":"LTC",
|
|
# "baseQtyEv":0,
|
|
# "bizError":0,
|
|
# "clOrdID":"2c0e5eb5-efb7-60d3-2e5f-df175df412ef",
|
|
# "createTimeNs":"1650446670073853755",
|
|
# "cumBaseQtyEv":0,
|
|
# "cumFeeEv":0,
|
|
# "cumQuoteQtyEv":0,
|
|
# "cxlRejReason":0,
|
|
# "feeCurrency":"LTC",
|
|
# "leavesBaseQtyEv":0,
|
|
# "leavesQuoteQtyEv":1000000000,
|
|
# "ordStatus":"New",
|
|
# "ordType":"Limit",
|
|
# "orderID":"d2aad92f-50f5-441a-957b-8184b146e3fb",
|
|
# "pegOffsetValueEp":0,
|
|
# "priceEp":5000000000,
|
|
# "qtyType":"ByQuote",
|
|
# "quoteCurrency":"USDT",
|
|
# "quoteQtyEv":1000000000,
|
|
# "side":"Buy",
|
|
# }
|
|
# ]
|
|
# },
|
|
# perpetual
|
|
# [
|
|
# {
|
|
# "accountID": 40183400003,
|
|
# "action": "New",
|
|
# "actionBy": "ByUser",
|
|
# "actionTimeNs": "1674110665380190869",
|
|
# "addedSeq": 678760103,
|
|
# "apRp": "0",
|
|
# "bonusChangedAmountRv": "0",
|
|
# "bpRp": "0",
|
|
# "clOrdID": '',
|
|
# "cl_req_code": 0,
|
|
# "closedPnlRv": "0",
|
|
# "closedSize": "0",
|
|
# "code": 0,
|
|
# "cumFeeRv": "0",
|
|
# "cumQty": "0.001",
|
|
# "cumValueRv": "20.849",
|
|
# "curAccBalanceRv": "19.9874906",
|
|
# "curAssignedPosBalanceRv": "0",
|
|
# "curBonusBalanceRv": "0",
|
|
# "curLeverageRr": "-10",
|
|
# "curPosSide": "Buy",
|
|
# "curPosSize": "0.001",
|
|
# "curPosTerm": 1,
|
|
# "curPosValueRv": "20.849",
|
|
# "curRiskLimitRv": "1000000",
|
|
# "currency": "USDT",
|
|
# "cxlRejReason": 0,
|
|
# "displayQty": "0.001",
|
|
# "execFeeRv": "0.0125094",
|
|
# "execID": "b88d2950-04a2-52d8-8927-346059900242",
|
|
# "execPriceRp": "20849",
|
|
# "execQty": "0.001",
|
|
# "execSeq": 678760103,
|
|
# "execStatus": "TakerFill",
|
|
# "execValueRv": "20.849",
|
|
# "feeRateRr": "0.0006",
|
|
# "lastLiquidityInd": "RemovedLiquidity",
|
|
# "leavesQty": "0",
|
|
# "leavesValueRv": "0",
|
|
# "message": "No error",
|
|
# "ordStatus": "Filled",
|
|
# "ordType": "Market",
|
|
# "orderID": "79620ed2-54c6-4645-a35c-7057e687c576",
|
|
# "orderQty": "0.001",
|
|
# "pegOffsetProportionRr": "0",
|
|
# "pegOffsetValueRp": "0",
|
|
# "posSide": "Long",
|
|
# "priceRp": "21476.3",
|
|
# "relatedPosTerm": 1,
|
|
# "relatedReqNum": 4,
|
|
# "side": "Buy",
|
|
# "slTrigger": "ByMarkPrice",
|
|
# "stopLossRp": "0",
|
|
# "stopPxRp": "0",
|
|
# "symbol": "BTCUSDT",
|
|
# "takeProfitRp": "0",
|
|
# "timeInForce": "ImmediateOrCancel",
|
|
# "tpTrigger": "ByLastPrice",
|
|
# "tradeType": "Trade",
|
|
# "transactTimeNs": "1674110665387882268",
|
|
# "userID": 4018340
|
|
# },
|
|
# ...
|
|
# ]
|
|
#
|
|
trades = []
|
|
parsedOrders = []
|
|
if ('closed' in message) or ('fills' in message) or ('open' in message):
|
|
closed = self.safe_value(message, 'closed', [])
|
|
open = self.safe_value(message, 'open', [])
|
|
orders = self.array_concat(open, closed)
|
|
ordersLength = len(orders)
|
|
if ordersLength == 0:
|
|
return
|
|
trades = self.safe_value(message, 'fills', [])
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
parsedOrder = self.parse_order(rawOrder)
|
|
parsedOrders.append(parsedOrder)
|
|
else:
|
|
messageLength = len(message)
|
|
if messageLength == 0:
|
|
return
|
|
for i in range(0, len(message)):
|
|
update = message[i]
|
|
action = self.safe_string(update, 'action')
|
|
if (action is not None) and (action != 'Cancel'):
|
|
# order + trade info together
|
|
trades.append(update)
|
|
parsedOrder = self.parse_ws_swap_order(update)
|
|
parsedOrders.append(parsedOrder)
|
|
self.handle_my_trades(client, trades)
|
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
marketIds: dict = {}
|
|
if self.orders is None:
|
|
self.orders = ArrayCacheBySymbolById(limit)
|
|
type = None
|
|
stored = self.orders
|
|
for i in range(0, len(parsedOrders)):
|
|
parsed = parsedOrders[i]
|
|
stored.append(parsed)
|
|
symbol = parsed['symbol']
|
|
market = self.market(symbol)
|
|
if type is None:
|
|
isUsdt = market['settle'] == 'USDT'
|
|
type = 'perpetual' if isUsdt else market['type']
|
|
marketIds[symbol] = True
|
|
keys = list(marketIds.keys())
|
|
for i in range(0, len(keys)):
|
|
currentMessageHash = 'orders' + ':' + keys[i]
|
|
client.resolve(self.orders, currentMessageHash)
|
|
# resolve generic subscription(spot or swap)
|
|
messageHash = 'orders:' + type
|
|
client.resolve(self.orders, messageHash)
|
|
|
|
def parse_ws_swap_order(self, order, market=None):
|
|
#
|
|
# swap
|
|
# {
|
|
# "accountID":26472240002,
|
|
# "action":"Cancel",
|
|
# "actionBy":"ByUser",
|
|
# "actionTimeNs":"1650450096104760797",
|
|
# "addedSeq":26975849309,
|
|
# "bonusChangedAmountEv":0,
|
|
# "clOrdID":"d9675963-5e4e-6fc8-898a-ec8b934c1c61",
|
|
# "closedPnlEv":0,
|
|
# "closedSize":0,
|
|
# "code":0,
|
|
# "cumQty":0,
|
|
# "cumValueEv":0,
|
|
# "curAccBalanceEv":400079,
|
|
# "curAssignedPosBalanceEv":0,
|
|
# "curBonusBalanceEv":0,
|
|
# "curLeverageEr":0,
|
|
# "curPosSide":"None",
|
|
# "curPosSize":0,
|
|
# "curPosTerm":1,
|
|
# "curPosValueEv":0,
|
|
# "curRiskLimitEv":5000000000,
|
|
# "currency":"USD",
|
|
# "cxlRejReason":0,
|
|
# "displayQty":0,
|
|
# "execFeeEv":0,
|
|
# "execID":"00000000-0000-0000-0000-000000000000",
|
|
# "execPriceEp":0,
|
|
# "execQty":1,
|
|
# "execSeq":26975862338,
|
|
# "execStatus":"Canceled",
|
|
# "execValueEv":0,
|
|
# "feeRateEr":0,
|
|
# "leavesQty":0,
|
|
# "leavesValueEv":0,
|
|
# "message":"No error",
|
|
# "ordStatus":"Canceled",
|
|
# "ordType":"Limit",
|
|
# "orderID":"8141deb9-8f94-48f6-9421-a4e3a791537b",
|
|
# "orderQty":1,
|
|
# "pegOffsetValueEp":0,
|
|
# "priceEp":9521,
|
|
# "relatedPosTerm":1,
|
|
# "relatedReqNum":4,
|
|
# "side":"Buy",
|
|
# "slTrigger":"ByMarkPrice",
|
|
# "stopLossEp":0,
|
|
# "stopPxEp":0,
|
|
# "symbol":"ADAUSD",
|
|
# "takeProfitEp":0,
|
|
# "timeInForce":"GoodTillCancel",
|
|
# "tpTrigger":"ByLastPrice",
|
|
# "transactTimeNs":"1650450096108143014",
|
|
# "userID":2647224
|
|
# }
|
|
# perpetual
|
|
# {
|
|
# "accountID": 40183400003,
|
|
# "action": "New",
|
|
# "actionBy": "ByUser",
|
|
# "actionTimeNs": "1674110665380190869",
|
|
# "addedSeq": 678760103,
|
|
# "apRp": "0",
|
|
# "bonusChangedAmountRv": "0",
|
|
# "bpRp": "0",
|
|
# "clOrdID": '',
|
|
# "cl_req_code": 0,
|
|
# "closedPnlRv": "0",
|
|
# "closedSize": "0",
|
|
# "code": 0,
|
|
# "cumFeeRv": "0",
|
|
# "cumQty": "0.001",
|
|
# "cumValueRv": "20.849",
|
|
# "curAccBalanceRv": "19.9874906",
|
|
# "curAssignedPosBalanceRv": "0",
|
|
# "curBonusBalanceRv": "0",
|
|
# "curLeverageRr": "-10",
|
|
# "curPosSide": "Buy",
|
|
# "curPosSize": "0.001",
|
|
# "curPosTerm": 1,
|
|
# "curPosValueRv": "20.849",
|
|
# "curRiskLimitRv": "1000000",
|
|
# "currency": "USDT",
|
|
# "cxlRejReason": 0,
|
|
# "displayQty": "0.001",
|
|
# "execFeeRv": "0.0125094",
|
|
# "execID": "b88d2950-04a2-52d8-8927-346059900242",
|
|
# "execPriceRp": "20849",
|
|
# "execQty": "0.001",
|
|
# "execSeq": 678760103,
|
|
# "execStatus": "TakerFill",
|
|
# "execValueRv": "20.849",
|
|
# "feeRateRr": "0.0006",
|
|
# "lastLiquidityInd": "RemovedLiquidity",
|
|
# "leavesQty": "0",
|
|
# "leavesValueRv": "0",
|
|
# "message": "No error",
|
|
# "ordStatus": "Filled",
|
|
# "ordType": "Market",
|
|
# "orderID": "79620ed2-54c6-4645-a35c-7057e687c576",
|
|
# "orderQty": "0.001",
|
|
# "pegOffsetProportionRr": "0",
|
|
# "pegOffsetValueRp": "0",
|
|
# "posSide": "Long",
|
|
# "priceRp": "21476.3",
|
|
# "relatedPosTerm": 1,
|
|
# "relatedReqNum": 4,
|
|
# "side": "Buy",
|
|
# "slTrigger": "ByMarkPrice",
|
|
# "stopLossRp": "0",
|
|
# "stopPxRp": "0",
|
|
# "symbol": "BTCUSDT",
|
|
# "takeProfitRp": "0",
|
|
# "timeInForce": "ImmediateOrCancel",
|
|
# "tpTrigger": "ByLastPrice",
|
|
# "tradeType": "Trade",
|
|
# "transactTimeNs": "1674110665387882268",
|
|
# "userID": 4018340
|
|
# }
|
|
#
|
|
id = self.safe_string(order, 'orderID')
|
|
clientOrderId = self.safe_string(order, 'clOrdID')
|
|
if (clientOrderId is not None) and (len(clientOrderId) < 1):
|
|
clientOrderId = None
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
|
|
side = self.safe_string_lower(order, 'side')
|
|
type = self.parseOrderType(self.safe_string(order, 'ordType'))
|
|
price = self.safe_string(order, 'priceRp', self.from_ep(self.safe_string(order, 'priceEp'), market))
|
|
amount = self.safe_string(order, 'orderQty')
|
|
filled = self.safe_string(order, 'cumQty')
|
|
remaining = self.safe_string(order, 'leavesQty')
|
|
timestamp = self.safe_integer_product(order, 'actionTimeNs', 0.000001)
|
|
cost = self.safe_string(order, 'cumValueRv', self.from_ev(self.safe_string(order, 'cumValueEv'), market))
|
|
lastTradeTimestamp = self.safe_integer_product(order, 'transactTimeNs', 0.000001)
|
|
if lastTradeTimestamp == 0:
|
|
lastTradeTimestamp = None
|
|
timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
|
|
stopPrice = self.safe_string(order, 'stopPx')
|
|
postOnly = (timeInForce == 'PO')
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'datetime': self.iso8601(timestamp),
|
|
'timestamp': timestamp,
|
|
'lastTradeTimestamp': lastTradeTimestamp,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'postOnly': postOnly,
|
|
'side': side,
|
|
'price': price,
|
|
'stopPrice': stopPrice,
|
|
'triggerPrice': stopPrice,
|
|
'amount': amount,
|
|
'filled': filled,
|
|
'remaining': remaining,
|
|
'cost': cost,
|
|
'average': None,
|
|
'status': status,
|
|
'fee': None,
|
|
'trades': None,
|
|
}, market)
|
|
|
|
def handle_message(self, client: Client, message):
|
|
# private spot update
|
|
# {
|
|
# "orders": {closed: [], fills: [], open: []},
|
|
# "sequence": 40435835,
|
|
# "timestamp": "1650443245600839241",
|
|
# "type": "snapshot",
|
|
# "wallets": [
|
|
# {
|
|
# "balanceEv": 0,
|
|
# "currency": "BTC",
|
|
# "lastUpdateTimeNs": "1650442638722099092",
|
|
# "lockedTradingBalanceEv": 0,
|
|
# "lockedWithdrawEv": 0,
|
|
# "userID": 2647224
|
|
# },
|
|
# {
|
|
# "balanceEv": 1154232337,
|
|
# "currency": "USDT",
|
|
# "lastUpdateTimeNs": "1650442617610017597",
|
|
# "lockedTradingBalanceEv": 0,
|
|
# "lockedWithdrawEv": 0,
|
|
# "userID": 2647224
|
|
# }
|
|
# ]
|
|
# }
|
|
# private swap update
|
|
# {
|
|
# "sequence": 83839628,
|
|
# "timestamp": "1650382581827447829",
|
|
# "type": "snapshot",
|
|
# "accounts": [
|
|
# {
|
|
# "accountBalanceEv": 0,
|
|
# "accountID": 26472240001,
|
|
# "bonusBalanceEv": 0,
|
|
# "currency": "BTC",
|
|
# "totalUsedBalanceEv": 0,
|
|
# "userID": 2647224
|
|
# }
|
|
# ],
|
|
# "orders": [],
|
|
# "positions": [
|
|
# {
|
|
# "accountID": 26472240001,
|
|
# "assignedPosBalanceEv": 0,
|
|
# "avgEntryPriceEp": 0,
|
|
# "bankruptCommEv": 0,
|
|
# "bankruptPriceEp": 0,
|
|
# "buyLeavesQty": 0,
|
|
# "buyLeavesValueEv": 0,
|
|
# "buyValueToCostEr": 1150750,
|
|
# "createdAtNs": 0,
|
|
# "crossSharedBalanceEv": 0,
|
|
# "cumClosedPnlEv": 0,
|
|
# "cumFundingFeeEv": 0,
|
|
# "cumTransactFeeEv": 0,
|
|
# "curTermRealisedPnlEv": 0,
|
|
# "currency": "BTC",
|
|
# "dataVer": 2,
|
|
# "deleveragePercentileEr": 0,
|
|
# "displayLeverageEr": 10000000000,
|
|
# "estimatedOrdLossEv": 0,
|
|
# "execSeq": 0,
|
|
# "freeCostEv": 0,
|
|
# "freeQty": 0,
|
|
# "initMarginReqEr": 1000000,
|
|
# "lastFundingTime": "1640601827712091793",
|
|
# "lastTermEndTime": 0,
|
|
# "leverageEr": 0,
|
|
# "liquidationPriceEp": 0,
|
|
# "maintMarginReqEr": 500000,
|
|
# "makerFeeRateEr": 0,
|
|
# "markPriceEp": 507806777,
|
|
# "orderCostEv": 0,
|
|
# "posCostEv": 0,
|
|
# "positionMarginEv": 0,
|
|
# "positionStatus": "Normal",
|
|
# "riskLimitEv": 10000000000,
|
|
# "sellLeavesQty": 0,
|
|
# "sellLeavesValueEv": 0,
|
|
# "sellValueToCostEr": 1149250,
|
|
# "side": "None",
|
|
# "size": 0,
|
|
# "symbol": "BTCUSD",
|
|
# "takerFeeRateEr": 0,
|
|
# "term": 1,
|
|
# "transactTimeNs": 0,
|
|
# "unrealisedPnlEv": 0,
|
|
# "updatedAtNs": 0,
|
|
# "usedBalanceEv": 0,
|
|
# "userID": 2647224,
|
|
# "valueEv": 0
|
|
# }
|
|
# ]
|
|
# }
|
|
id = self.safe_string(message, 'id')
|
|
if id in client.subscriptions:
|
|
method = client.subscriptions[id]
|
|
del client.subscriptions[id]
|
|
if method is not True:
|
|
method(client, message)
|
|
return
|
|
methodName = self.safe_string(message, 'method', '')
|
|
if ('market24h' in message) or ('spot_market24h' in message) or (methodName.find('perp_market24h_pack_p') >= 0):
|
|
self.handle_ticker(client, message)
|
|
return
|
|
elif ('trades' in message) or ('trades_p' in message):
|
|
self.handle_trades(client, message)
|
|
return
|
|
elif ('kline' in message) or ('kline_p' in message):
|
|
self.handle_ohlcv(client, message)
|
|
return
|
|
elif ('book' in message) or ('orderbook_p' in message):
|
|
self.handle_order_book(client, message)
|
|
return
|
|
if ('orders' in message) or ('orders_p' in message):
|
|
orders = self.safe_value_2(message, 'orders', 'orders_p', {})
|
|
self.handle_orders(client, orders)
|
|
if ('accounts' in message) or ('accounts_p' in message) or ('wallets' in message):
|
|
type = 'swap' if ('accounts' in message) else 'spot'
|
|
if 'accounts_p' in message:
|
|
type = 'perpetual'
|
|
accounts = self.safe_value_n(message, ['accounts', 'accounts_p', 'wallets'], [])
|
|
self.handle_balance(type, client, accounts)
|
|
|
|
def handle_authenticate(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "error": null,
|
|
# "id": 1234,
|
|
# "result": {
|
|
# "status": "success"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_value(message, 'result')
|
|
status = self.safe_string(result, 'status')
|
|
messageHash = 'authenticated'
|
|
if status == 'success':
|
|
client.resolve(message, messageHash)
|
|
else:
|
|
error = AuthenticationError(self.id + ' ' + self.json(message))
|
|
client.reject(error, messageHash)
|
|
if messageHash in client.subscriptions:
|
|
del client.subscriptions[messageHash]
|
|
|
|
async def subscribe_private(self, type, messageHash, params={}):
|
|
await self.load_markets()
|
|
await self.authenticate()
|
|
url = self.urls['api']['ws']
|
|
requestId = self.seconds()
|
|
settleIsUSDT = (self.safe_value(params, 'settle', '') == 'USDT')
|
|
params = self.omit(params, 'settle')
|
|
channel = 'aop.subscribe'
|
|
if type == 'spot':
|
|
channel = 'wo.subscribe'
|
|
if settleIsUSDT:
|
|
channel = 'aop_p.subscribe'
|
|
request = {
|
|
'id': requestId,
|
|
'method': channel,
|
|
'params': [],
|
|
}
|
|
request = self.extend(request, params)
|
|
return await self.watch(url, messageHash, request, channel)
|
|
|
|
async def authenticate(self, params={}):
|
|
self.check_required_credentials()
|
|
url = self.urls['api']['ws']
|
|
client = self.client(url)
|
|
requestId = self.request_id()
|
|
messageHash = 'authenticated'
|
|
future = self.safe_value(client.subscriptions, messageHash)
|
|
if future is None:
|
|
expiryDelta = self.safe_integer(self.options, 'expires', 120)
|
|
expiration = self.seconds() + expiryDelta
|
|
payload = self.apiKey + str(expiration)
|
|
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256)
|
|
method = 'user.auth'
|
|
request: dict = {
|
|
'method': method,
|
|
'params': ['API', self.apiKey, signature, expiration],
|
|
'id': requestId,
|
|
}
|
|
subscriptionHash = str(requestId)
|
|
message = self.extend(request, params)
|
|
if not (messageHash in client.subscriptions):
|
|
client.subscriptions[subscriptionHash] = self.handle_authenticate
|
|
future = await self.watch(url, messageHash, message, messageHash)
|
|
client.subscriptions[messageHash] = future
|
|
return future
|