This commit is contained in:
lz_db
2025-11-16 12:31:03 +08:00
commit 0fab423a18
1451 changed files with 743213 additions and 0 deletions

212
ccxt/pro/__init__.py Normal file
View File

@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
"""CCXT: CryptoCurrency eXchange Trading Library (Async)"""
# ----------------------------------------------------------------------------
__version__ = '4.5.18'
# ----------------------------------------------------------------------------
from ccxt.async_support.base.exchange import Exchange # noqa: F401
# CCXT Pro exchanges (now this is mainly used for importing exchanges in WS tests)
# DO_NOT_REMOVE__ERROR_IMPORTS_START
from ccxt.base.errors import BaseError # noqa: F401
from ccxt.base.errors import ExchangeError # noqa: F401
from ccxt.base.errors import AuthenticationError # noqa: F401
from ccxt.base.errors import PermissionDenied # noqa: F401
from ccxt.base.errors import AccountNotEnabled # noqa: F401
from ccxt.base.errors import AccountSuspended # noqa: F401
from ccxt.base.errors import ArgumentsRequired # noqa: F401
from ccxt.base.errors import BadRequest # noqa: F401
from ccxt.base.errors import BadSymbol # noqa: F401
from ccxt.base.errors import OperationRejected # noqa: F401
from ccxt.base.errors import NoChange # noqa: F401
from ccxt.base.errors import MarginModeAlreadySet # noqa: F401
from ccxt.base.errors import MarketClosed # noqa: F401
from ccxt.base.errors import ManualInteractionNeeded # noqa: F401
from ccxt.base.errors import RestrictedLocation # noqa: F401
from ccxt.base.errors import InsufficientFunds # noqa: F401
from ccxt.base.errors import InvalidAddress # noqa: F401
from ccxt.base.errors import AddressPending # noqa: F401
from ccxt.base.errors import InvalidOrder # noqa: F401
from ccxt.base.errors import OrderNotFound # noqa: F401
from ccxt.base.errors import OrderNotCached # noqa: F401
from ccxt.base.errors import OrderImmediatelyFillable # noqa: F401
from ccxt.base.errors import OrderNotFillable # noqa: F401
from ccxt.base.errors import DuplicateOrderId # noqa: F401
from ccxt.base.errors import ContractUnavailable # noqa: F401
from ccxt.base.errors import NotSupported # noqa: F401
from ccxt.base.errors import InvalidProxySettings # noqa: F401
from ccxt.base.errors import ExchangeClosedByUser # noqa: F401
from ccxt.base.errors import OperationFailed # noqa: F401
from ccxt.base.errors import NetworkError # noqa: F401
from ccxt.base.errors import DDoSProtection # noqa: F401
from ccxt.base.errors import RateLimitExceeded # noqa: F401
from ccxt.base.errors import ExchangeNotAvailable # noqa: F401
from ccxt.base.errors import OnMaintenance # noqa: F401
from ccxt.base.errors import InvalidNonce # noqa: F401
from ccxt.base.errors import ChecksumError # noqa: F401
from ccxt.base.errors import RequestTimeout # noqa: F401
from ccxt.base.errors import BadResponse # noqa: F401
from ccxt.base.errors import NullResponse # noqa: F401
from ccxt.base.errors import CancelPending # noqa: F401
from ccxt.base.errors import UnsubscribeError # noqa: F401
from ccxt.base.errors import error_hierarchy # noqa: F401
# DO_NOT_REMOVE__ERROR_IMPORTS_END
from ccxt.pro.alpaca import alpaca # noqa: F401
from ccxt.pro.apex import apex # noqa: F401
from ccxt.pro.arkham import arkham # noqa: F401
from ccxt.pro.ascendex import ascendex # noqa: F401
from ccxt.pro.backpack import backpack # noqa: F401
from ccxt.pro.bequant import bequant # noqa: F401
from ccxt.pro.binance import binance # noqa: F401
from ccxt.pro.binancecoinm import binancecoinm # noqa: F401
from ccxt.pro.binanceus import binanceus # noqa: F401
from ccxt.pro.binanceusdm import binanceusdm # noqa: F401
from ccxt.pro.bingx import bingx # noqa: F401
from ccxt.pro.bitfinex import bitfinex # noqa: F401
from ccxt.pro.bitget import bitget # noqa: F401
from ccxt.pro.bithumb import bithumb # noqa: F401
from ccxt.pro.bitmart import bitmart # noqa: F401
from ccxt.pro.bitmex import bitmex # noqa: F401
from ccxt.pro.bitopro import bitopro # noqa: F401
from ccxt.pro.bitrue import bitrue # noqa: F401
from ccxt.pro.bitstamp import bitstamp # noqa: F401
from ccxt.pro.bittrade import bittrade # noqa: F401
from ccxt.pro.bitvavo import bitvavo # noqa: F401
from ccxt.pro.blockchaincom import blockchaincom # noqa: F401
from ccxt.pro.blofin import blofin # noqa: F401
from ccxt.pro.bybit import bybit # noqa: F401
from ccxt.pro.cex import cex # noqa: F401
from ccxt.pro.coinbase import coinbase # noqa: F401
from ccxt.pro.coinbaseadvanced import coinbaseadvanced # noqa: F401
from ccxt.pro.coinbaseexchange import coinbaseexchange # noqa: F401
from ccxt.pro.coinbaseinternational import coinbaseinternational # noqa: F401
from ccxt.pro.coincatch import coincatch # noqa: F401
from ccxt.pro.coincheck import coincheck # noqa: F401
from ccxt.pro.coinex import coinex # noqa: F401
from ccxt.pro.coinone import coinone # noqa: F401
from ccxt.pro.cryptocom import cryptocom # noqa: F401
from ccxt.pro.deepcoin import deepcoin # noqa: F401
from ccxt.pro.defx import defx # noqa: F401
from ccxt.pro.deribit import deribit # noqa: F401
from ccxt.pro.derive import derive # noqa: F401
from ccxt.pro.exmo import exmo # noqa: F401
from ccxt.pro.gate import gate # noqa: F401
from ccxt.pro.gateio import gateio # noqa: F401
from ccxt.pro.gemini import gemini # noqa: F401
from ccxt.pro.hashkey import hashkey # noqa: F401
from ccxt.pro.hitbtc import hitbtc # noqa: F401
from ccxt.pro.hollaex import hollaex # noqa: F401
from ccxt.pro.htx import htx # noqa: F401
from ccxt.pro.huobi import huobi # noqa: F401
from ccxt.pro.hyperliquid import hyperliquid # noqa: F401
from ccxt.pro.independentreserve import independentreserve # noqa: F401
from ccxt.pro.kraken import kraken # noqa: F401
from ccxt.pro.krakenfutures import krakenfutures # noqa: F401
from ccxt.pro.kucoin import kucoin # noqa: F401
from ccxt.pro.kucoinfutures import kucoinfutures # noqa: F401
from ccxt.pro.lbank import lbank # noqa: F401
from ccxt.pro.luno import luno # noqa: F401
from ccxt.pro.mexc import mexc # noqa: F401
from ccxt.pro.modetrade import modetrade # noqa: F401
from ccxt.pro.myokx import myokx # noqa: F401
from ccxt.pro.ndax import ndax # noqa: F401
from ccxt.pro.okx import okx # noqa: F401
from ccxt.pro.okxus import okxus # noqa: F401
from ccxt.pro.onetrading import onetrading # noqa: F401
from ccxt.pro.oxfun import oxfun # noqa: F401
from ccxt.pro.p2b import p2b # noqa: F401
from ccxt.pro.paradex import paradex # noqa: F401
from ccxt.pro.phemex import phemex # noqa: F401
from ccxt.pro.poloniex import poloniex # noqa: F401
from ccxt.pro.probit import probit # noqa: F401
from ccxt.pro.toobit import toobit # noqa: F401
from ccxt.pro.upbit import upbit # noqa: F401
from ccxt.pro.whitebit import whitebit # noqa: F401
from ccxt.pro.woo import woo # noqa: F401
from ccxt.pro.woofipro import woofipro # noqa: F401
from ccxt.pro.xt import xt # noqa: F401
from ccxt.pro.mt5 import mt5 # noqa: F401
exchanges = [
'alpaca',
'apex',
'arkham',
'ascendex',
'backpack',
'bequant',
'binance',
'binancecoinm',
'binanceus',
'binanceusdm',
'bingx',
'bitfinex',
'bitget',
'bithumb',
'bitmart',
'bitmex',
'bitopro',
'bitrue',
'bitstamp',
'bittrade',
'bitvavo',
'blockchaincom',
'blofin',
'bybit',
'cex',
'coinbase',
'coinbaseadvanced',
'coinbaseexchange',
'coinbaseinternational',
'coincatch',
'coincheck',
'coinex',
'coinone',
'cryptocom',
'deepcoin',
'defx',
'deribit',
'derive',
'exmo',
'gate',
'gateio',
'gemini',
'hashkey',
'hitbtc',
'hollaex',
'htx',
'huobi',
'hyperliquid',
'independentreserve',
'kraken',
'krakenfutures',
'kucoin',
'kucoinfutures',
'lbank',
'luno',
'mexc',
'modetrade',
'myokx',
'ndax',
'okx',
'okxus',
'onetrading',
'oxfun',
'p2b',
'paradex',
'phemex',
'poloniex',
'probit',
'toobit',
'upbit',
'whitebit',
'woo',
'woofipro',
'xt',
'mt5',
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

716
ccxt/pro/alpaca.py Normal file
View File

@@ -0,0 +1,716 @@
# -*- 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
from ccxt.base.types import Any, Bool, Int, Order, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
class alpaca(ccxt.async_support.alpaca):
def describe(self) -> Any:
return self.deep_extend(super(alpaca, self).describe(), {
'has': {
'ws': True,
'createOrderWithTakeProfitAndStopLossWs': False,
'createReduceOnlyOrderWs': False,
'createStopLossOrderWs': False,
'createTakeProfitOrderWs': False,
'fetchPositionForSymbolWs': False,
'fetchPositionsForSymbolWs': False,
'fetchPositionsWs': False,
'fetchPositionWs': False,
'unWatchPositions': False,
'watchBalance': False,
'watchLiquidations': False,
'watchLiquidationsForSymbols': False,
'watchMarkPrice': False,
'watchMarkPrices': False,
'watchMyLiquidations': False,
'watchMyLiquidationsForSymbols': False,
'watchMyTrades': True,
'watchOHLCV': True,
'watchOrderBook': True,
'watchOrders': True,
'watchPosition': False,
'watchPositions': False,
'watchTicker': True,
'watchTickers': False, # for now
'watchTrades': True,
},
'urls': {
'api': {
'ws': {
'crypto': 'wss://stream.data.alpaca.markets/v1beta2/crypto',
'trading': 'wss://api.alpaca.markets/stream',
},
},
'test': {
'ws': {
'crypto': 'wss://stream.data.alpaca.markets/v1beta2/crypto',
'trading': 'wss://paper-api.alpaca.markets/stream',
},
},
},
'options': {
},
'streaming': {},
'exceptions': {
'ws': {
'exact': {
},
},
},
})
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://docs.alpaca.markets/docs/real-time-crypto-pricing-data#quotes
: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>`
"""
url = self.urls['api']['ws']['crypto']
await self.authenticate(url)
await self.load_markets()
market = self.market(symbol)
messageHash = 'ticker:' + market['symbol']
request: dict = {
'action': 'subscribe',
'quotes': [market['id']],
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash)
def handle_ticker(self, client: Client, message):
#
# {
# "T": "q",
# "S": "BTC/USDT",
# "bp": 17394.44,
# "bs": 0.021981,
# "ap": 17397.99,
# "as": 0.02,
# "t": "2022-12-16T06:07:56.611063286Z"
# ]
#
ticker = self.parse_ticker(message)
symbol = ticker['symbol']
messageHash = 'ticker:' + symbol
self.tickers[symbol] = ticker
client.resolve(self.tickers[symbol], messageHash)
def parse_ticker(self, ticker, market=None) -> Ticker:
#
# {
# "T": "q",
# "S": "BTC/USDT",
# "bp": 17394.44,
# "bs": 0.021981,
# "ap": 17397.99,
# "as": 0.02,
# "t": "2022-12-16T06:07:56.611063286Z"
# }
#
marketId = self.safe_string(ticker, 'S')
datetime = self.safe_string(ticker, 't')
return self.safe_ticker({
'symbol': self.safe_symbol(marketId, market),
'timestamp': self.parse8601(datetime),
'datetime': datetime,
'high': None,
'low': None,
'bid': self.safe_string(ticker, 'bp'),
'bidVolume': self.safe_string(ticker, 'bs'),
'ask': self.safe_string(ticker, 'ap'),
'askVolume': self.safe_string(ticker, 'as'),
'vwap': None,
'open': None,
'close': None,
'last': None,
'previousClose': None,
'change': None,
'percentage': None,
'average': None,
'baseVolume': None,
'quoteVolume': None,
'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://docs.alpaca.markets/docs/real-time-crypto-pricing-data#bars
: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
"""
url = self.urls['api']['ws']['crypto']
await self.authenticate(url)
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
request: dict = {
'action': 'subscribe',
'bars': [market['id']],
}
messageHash = 'ohlcv:' + symbol
ohlcv = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
def handle_ohlcv(self, client: Client, message):
#
# {
# "T": "b",
# "S": "BTC/USDT",
# "o": 17416.39,
# "h": 17424.82,
# "l": 17416.39,
# "c": 17424.82,
# "v": 1.341054,
# "t": "2022-12-16T06:53:00Z",
# "n": 21,
# "vw": 17421.9529234915
# }
#
marketId = self.safe_string(message, 'S')
symbol = self.safe_symbol(marketId)
stored = self.safe_value(self.ohlcvs, symbol)
if stored is None:
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
stored = ArrayCacheByTimestamp(limit)
self.ohlcvs[symbol] = stored
parsed = self.parse_ohlcv(message)
stored.append(parsed)
messageHash = 'ohlcv:' + symbol
client.resolve(stored, messageHash)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.alpaca.markets/docs/real-time-crypto-pricing-data#orderbooks
: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
"""
url = self.urls['api']['ws']['crypto']
await self.authenticate(url)
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orderbook' + ':' + symbol
request: dict = {
'action': 'subscribe',
'orderbooks': [market['id']],
}
orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# snapshot
# {
# "T": "o",
# "S": "BTC/USDT",
# "t": "2022-12-16T06:35:31.585113205Z",
# "b": [{
# "p": 17394.37,
# "s": 0.015499,
# },
# ...
# ],
# "a": [{
# "p": 17398.8,
# "s": 0.042919,
# },
# ...
# ],
# "r": True,
# }
#
marketId = self.safe_string(message, 'S')
symbol = self.safe_symbol(marketId)
datetime = self.safe_string(message, 't')
timestamp = self.parse8601(datetime)
isSnapshot = self.safe_bool(message, 'r', False)
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book()
orderbook = self.orderbooks[symbol]
if isSnapshot:
snapshot = self.parse_order_book(message, symbol, timestamp, 'b', 'a', 'p', 's')
orderbook.reset(snapshot)
else:
asks = self.safe_list(message, 'a', [])
bids = self.safe_list(message, 'b', [])
self.handle_deltas(orderbook['asks'], asks)
self.handle_deltas(orderbook['bids'], bids)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = datetime
messageHash = 'orderbook' + ':' + symbol
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
def handle_delta(self, bookside, delta):
bidAsk = self.parse_bid_ask(delta, 'p', 's')
bookside.storeArray(bidAsk)
def handle_deltas(self, bookside, deltas):
for i in range(0, len(deltas)):
self.handle_delta(bookside, deltas[i])
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://docs.alpaca.markets/docs/real-time-crypto-pricing-data#trades
:param str symbol: unified market symbol of the market trades were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of trade structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
url = self.urls['api']['ws']['crypto']
await self.authenticate(url)
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'trade:' + symbol
request: dict = {
'action': 'subscribe',
'trades': [market['id']],
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# {
# "T": "t",
# "S": "BTC/USDT",
# "p": 17408.8,
# "s": 0.042919,
# "t": "2022-12-16T06:43:18.327Z",
# "i": 16585162,
# "tks": "B"
# ]
#
marketId = self.safe_string(message, 'S')
symbol = self.safe_symbol(marketId)
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
parsed = self.parse_trade(message)
stored.append(parsed)
messageHash = 'trade' + ':' + symbol
client.resolve(stored, 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
https://docs.alpaca.markets/docs/websocket-streaming#trade-updates
: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
:param boolean [params.unifiedMargin]: use unified margin account
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
url = self.urls['api']['ws']['trading']
await self.authenticate(url)
messageHash = 'myTrades'
await self.load_markets()
if symbol is not None:
symbol = self.symbol(symbol)
messageHash += ':' + symbol
request: dict = {
'action': 'listen',
'data': {
'streams': ['trade_updates'],
},
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
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>`
"""
url = self.urls['api']['ws']['trading']
await self.authenticate(url)
await self.load_markets()
messageHash = 'orders'
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orders:' + symbol
request: dict = {
'action': 'listen',
'data': {
'streams': ['trade_updates'],
},
}
orders = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_trade_update(self, client: Client, message):
self.handle_order(client, message)
self.handle_my_trade(client, message)
def handle_order(self, client: Client, message):
#
# {
# "stream": "trade_updates",
# "data": {
# "event": "new",
# "timestamp": "2022-12-16T07:28:51.67621869Z",
# "order": {
# "id": "c2470331-8993-4051-bf5d-428d5bdc9a48",
# "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c",
# "created_at": "2022-12-16T02:28:51.673531798-05:00",
# "updated_at": "2022-12-16T02:28:51.678736847-05:00",
# "submitted_at": "2022-12-16T02:28:51.673015558-05:00",
# "filled_at": null,
# "expired_at": null,
# "cancel_requested_at": null,
# "canceled_at": null,
# "failed_at": null,
# "replaced_at": null,
# "replaced_by": null,
# "replaces": null,
# "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340",
# "symbol": "BTC/USD",
# "asset_class": "crypto",
# "notional": null,
# "qty": "0.01",
# "filled_qty": "0",
# "filled_avg_price": null,
# "order_class": '',
# "order_type": "market",
# "type": "market",
# "side": "buy",
# "time_in_force": "gtc",
# "limit_price": null,
# "stop_price": null,
# "status": "new",
# "extended_hours": False,
# "legs": null,
# "trail_percent": null,
# "trail_price": null,
# "hwm": null
# },
# "execution_id": "5f781a30-b9a3-4c86-b466-2175850cf340"
# }
# }
#
data = self.safe_value(message, 'data', {})
rawOrder = self.safe_value(data, 'order', {})
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
order = self.parse_order(rawOrder)
orders.append(order)
messageHash = 'orders'
client.resolve(orders, messageHash)
messageHash = 'orders:' + order['symbol']
client.resolve(orders, messageHash)
def handle_my_trade(self, client: Client, message):
#
# {
# "stream": "trade_updates",
# "data": {
# "event": "new",
# "timestamp": "2022-12-16T07:28:51.67621869Z",
# "order": {
# "id": "c2470331-8993-4051-bf5d-428d5bdc9a48",
# "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c",
# "created_at": "2022-12-16T02:28:51.673531798-05:00",
# "updated_at": "2022-12-16T02:28:51.678736847-05:00",
# "submitted_at": "2022-12-16T02:28:51.673015558-05:00",
# "filled_at": null,
# "expired_at": null,
# "cancel_requested_at": null,
# "canceled_at": null,
# "failed_at": null,
# "replaced_at": null,
# "replaced_by": null,
# "replaces": null,
# "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340",
# "symbol": "BTC/USD",
# "asset_class": "crypto",
# "notional": null,
# "qty": "0.01",
# "filled_qty": "0",
# "filled_avg_price": null,
# "order_class": '',
# "order_type": "market",
# "type": "market",
# "side": "buy",
# "time_in_force": "gtc",
# "limit_price": null,
# "stop_price": null,
# "status": "new",
# "extended_hours": False,
# "legs": null,
# "trail_percent": null,
# "trail_price": null,
# "hwm": null
# },
# "execution_id": "5f781a30-b9a3-4c86-b466-2175850cf340"
# }
# }
#
data = self.safe_value(message, 'data', {})
event = self.safe_string(data, 'event')
if event != 'fill' and event != 'partial_fill':
return
rawOrder = self.safe_value(data, 'order', {})
myTrades = self.myTrades
if myTrades is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
myTrades = ArrayCacheBySymbolById(limit)
trade = self.parse_my_trade(rawOrder)
myTrades.append(trade)
messageHash = 'myTrades:' + trade['symbol']
client.resolve(myTrades, messageHash)
messageHash = 'myTrades'
client.resolve(myTrades, messageHash)
def parse_my_trade(self, trade, market=None):
#
# {
# "id": "c2470331-8993-4051-bf5d-428d5bdc9a48",
# "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c",
# "created_at": "2022-12-16T02:28:51.673531798-05:00",
# "updated_at": "2022-12-16T02:28:51.678736847-05:00",
# "submitted_at": "2022-12-16T02:28:51.673015558-05:00",
# "filled_at": null,
# "expired_at": null,
# "cancel_requested_at": null,
# "canceled_at": null,
# "failed_at": null,
# "replaced_at": null,
# "replaced_by": null,
# "replaces": null,
# "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340",
# "symbol": "BTC/USD",
# "asset_class": "crypto",
# "notional": null,
# "qty": "0.01",
# "filled_qty": "0",
# "filled_avg_price": null,
# "order_class": '',
# "order_type": "market",
# "type": "market",
# "side": "buy",
# "time_in_force": "gtc",
# "limit_price": null,
# "stop_price": null,
# "status": "new",
# "extended_hours": False,
# "legs": null,
# "trail_percent": null,
# "trail_price": null,
# "hwm": null
# }
#
marketId = self.safe_string(trade, 'symbol')
datetime = self.safe_string(trade, 'filled_at')
type = self.safe_string(trade, 'type')
if type.find('limit') >= 0:
# might be limit or stop-limit
type = 'limit'
return self.safe_trade({
'id': self.safe_string(trade, 'i'),
'info': trade,
'timestamp': self.parse8601(datetime),
'datetime': datetime,
'symbol': self.safe_symbol(marketId, None, '/'),
'order': self.safe_string(trade, 'id'),
'type': type,
'side': self.safe_string(trade, 'side'),
'takerOrMaker': 'taker' if (type == 'market') else 'maker',
'price': self.safe_string(trade, 'filled_avg_price'),
'amount': self.safe_string(trade, 'filled_qty'),
'cost': None,
'fee': None,
}, market)
async def authenticate(self, url, params={}):
self.check_required_credentials()
messageHash = 'authenticated'
client = self.client(url)
future = client.reusableFuture(messageHash)
authenticated = self.safe_value(client.subscriptions, messageHash)
if authenticated is None:
request = {
'action': 'auth',
'key': self.apiKey,
'secret': self.secret,
}
if url == self.urls['api']['ws']['trading']:
# self auth request is being deprecated in test environment
request = {
'action': 'authenticate',
'data': {
'key_id': self.apiKey,
'secret_key': self.secret,
},
}
self.watch(url, messageHash, request, messageHash, future)
return await future
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# "T": "error",
# "code": 400,
# "msg": "invalid syntax"
# }
#
code = self.safe_string(message, 'code')
msg = self.safe_value(message, 'msg', {})
raise ExchangeError(self.id + ' code: ' + code + ' message: ' + msg)
def handle_connected(self, client: Client, message):
#
# {
# "T": "success",
# "msg": "connected"
# }
#
return message
def handle_crypto_message(self, client: Client, message):
for i in range(0, len(message)):
data = message[i]
T = self.safe_string(data, 'T')
msg = self.safe_string(data, 'msg')
if T == 'subscription':
self.handle_subscription(client, data)
return
if T == 'success' and msg == 'connected':
self.handle_connected(client, data)
return
if T == 'success' and msg == 'authenticated':
self.handle_authenticate(client, data)
return
methods: dict = {
'error': self.handle_error_message,
'b': self.handle_ohlcv,
'q': self.handle_ticker,
't': self.handle_trades,
'o': self.handle_order_book,
}
method = self.safe_value(methods, T)
if method is not None:
method(client, data)
def handle_trading_message(self, client: Client, message):
stream = self.safe_string(message, 'stream')
methods: dict = {
'authorization': self.handle_authenticate,
'listening': self.handle_subscription,
'trade_updates': self.handle_trade_update,
}
method = self.safe_value(methods, stream)
if method is not None:
method(client, message)
def handle_message(self, client: Client, message):
if isinstance(message, list):
self.handle_crypto_message(client, message)
return
self.handle_trading_message(client, message)
def handle_authenticate(self, client: Client, message):
#
# crypto
# {
# "T": "success",
# "msg": "connected"
# ]
#
# trading
# {
# "stream": "authorization",
# "data": {
# "status": "authorized",
# "action": "authenticate"
# }
# }
# error
# {
# "stream": "authorization",
# "data": {
# "action": "authenticate",
# "message": "access key verification failed",
# "status": "unauthorized"
# }
# }
#
T = self.safe_string(message, 'T')
data = self.safe_value(message, 'data', {})
status = self.safe_string(data, 'status')
if T == 'success' or status == 'authorized':
promise = client.futures['authenticated']
promise.resolve(message)
return
raise AuthenticationError(self.id + ' failed to authenticate.')
def handle_subscription(self, client: Client, message):
#
# crypto
# {
# "T": "subscription",
# "trades": [],
# "quotes": ["BTC/USDT"],
# "orderbooks": [],
# "bars": [],
# "updatedBars": [],
# "dailyBars": []
# }
# trading
# {
# "stream": "listening",
# "data": {
# "streams": ["trade_updates"]
# }
# }
#
return message

1004
ccxt/pro/apex.py Normal file

File diff suppressed because it is too large Load Diff

686
ccxt/pro/arkham.py Normal file
View File

@@ -0,0 +1,686 @@
# -*- 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
import hashlib
from ccxt.base.types import Any, Balances, Bool, Int, Order, OrderBook, Position, Str, Strings, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
class arkham(ccxt.async_support.arkham):
def describe(self) -> Any:
return self.deep_extend(super(arkham, self).describe(), {
'has': {
'ws': True,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchOrderBook': True,
'watchOrderBookForSymbols': False,
'watchOHLCV': True,
'watchOHLCVForSymbols': False,
'watchOrders': True,
'watchMyTrades': False,
'watchTicker': True,
'watchTickers': False,
'watchBalance': True,
},
'urls': {
'api': {
'ws': 'wss://arkm.com/ws',
},
},
'options': {
'watchOrderBook': {
'depth': 100, # 5, 10, 20, 50, 100
'interval': 500, # 100, 200, 500, 1000
},
},
'streaming': {
'keepAlive': 300000, # 5 minutes
},
})
def handle_message(self, client: Client, message):
#
# confirmation
#
# {channel: 'confirmations', confirmationId: 'myCustomId-123'}
if self.handle_error_message(client, message):
return
methods: dict = {
'ticker': self.handle_ticker,
'candles': self.handle_ohlcv,
'l2_updates': self.handle_order_book,
'trades': self.handle_trades,
'balances': self.handle_balance,
'positions': self.handle_positions,
'order_statuses': self.handle_order,
'trigger_orders': self.handle_order,
# 'confirmations': self.handle_ticker,
}
channel = self.safe_string(message, 'channel')
if channel == 'confirmations':
return
# type = self.safe_string(message, 'type')
# if type != 'update' and type != 'snapshot':
# debugger
# }
method = self.safe_value(methods, channel)
if method is not None:
method(client, message)
async def subscribe(self, messageHash: str, rawChannel: str, params: dict) -> Any:
subscriptionHash = messageHash
request: dict = {
'args': {
'channel': rawChannel,
'params': params,
},
'confirmationId': self.uuid(),
'method': 'subscribe',
}
return await self.watch(self.urls['api']['ws'], messageHash, request, subscriptionHash)
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://arkm.com/docs#stream/ticker
: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)
requestArg = {
'symbol': market['id'],
}
messageHash = 'ticker::' + market['symbol']
return await self.subscribe(messageHash, 'ticker', self.extend(params, requestArg))
def handle_ticker(self, client: Client, message):
#
# {
# channel: 'ticker',
# type: 'update',
# data: {
# symbol: 'BTC_USDT',
# baseSymbol: 'BTC',
# quoteSymbol: 'USDT',
# price: '118962.74',
# price24hAgo: '118780.42',
# high24h: '120327.96',
# low24h: '118217.28',
# volume24h: '32.89729',
# quoteVolume24h: '3924438.7146048',
# markPrice: '0',
# indexPrice: '118963.080293501',
# fundingRate: '0',
# nextFundingRate: '0',
# nextFundingTime: 0,
# productType: 'spot',
# openInterest: '0',
# indexCurrency: 'USDT',
# usdVolume24h: '3924438.7146048',
# openInterestUSD: '0'
# }
# }
#
data = self.safe_dict(message, 'data', {})
marketId = self.safe_string(data, 'symbol')
market = self.safe_market(marketId, None)
symbol = market['symbol']
ticker = self.parse_ws_ticker(data, market)
self.tickers[symbol] = ticker
client.resolve(ticker, 'ticker::' + symbol)
# if self.safe_string(message, 'dataType') == 'all@ticker':
# client.resolve(ticker, self.getMessageHash('ticker'))
# }
def parse_ws_ticker(self, message, market=None):
# same dict api
return self.parse_ticker(message, market)
async def watch_ohlcv(self, symbol: str, timeframe='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://arkm.com/docs#stream/candles
: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)
rawTimeframe = self.safe_string(self.timeframes, timeframe, timeframe)
requestArg = {
'symbol': market['id'],
'duration': rawTimeframe,
}
messageHash = 'ohlcv::' + market['symbol'] + '::' + rawTimeframe
result = await self.subscribe(messageHash, 'candles', self.extend(requestArg, params))
ohlcv = result
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):
#
# {
# channel: 'candles',
# type: 'update',
# data: {
# symbol: 'BTC_USDT',
# time: '1755076380000000',
# duration: 60000000,
# open: '120073.01',
# high: '120073.01',
# low: '120073.01',
# close: '120073.01',
# volume: '0',
# quoteVolume: '0'
# }
# }
#
data = self.safe_dict(message, 'data', {})
marketId = self.safe_string(data, 'symbol')
market = self.safe_market(marketId, None)
symbol = market['symbol']
duration = self.safe_integer(data, 'duration')
timeframe = self.findTimeframeByDuration(duration)
messageHash = 'ohlcv::' + symbol + '::' + timeframe
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
if not (timeframe in self.ohlcvs[symbol]):
limit = self.handle_option('watchOHLCV', 'limit', 1000)
self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
stored = self.ohlcvs[symbol][timeframe]
parsed = self.parse_ws_ohlcv(data, market)
stored.append(parsed)
client.resolve(stored, messageHash)
return message
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
# same api
return self.parse_ohlcv(ohlcv, market)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://arkm.com/docs#stream/l2_updates
: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)
requestArg = {
'symbol': market['id'],
'snapshot': True,
}
messageHash = 'orderBook::' + market['symbol']
orderbook = await self.subscribe(messageHash, 'l2_updates', self.extend(requestArg, params))
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# snapshot:
#
# {
# channel: 'l2_updates',
# type: 'snapshot',
# data: {
# symbol: 'BTC_USDT',
# group: '0.01',
# asks: [ [Object], [Object], ...],
# bids: [ [Object], [Object], ...],
# lastTime: 1755115180608299
# }
# }
#
# update:
#
# {
# channel: "l2_updates",
# type: "update",
# data: {
# symbol: "BTC_USDT",
# group: "0.01",
# side: "sell",
# size: "0.05295",
# price: "122722.76",
# revisionId: 2455511217,
# time: 1755115736475207,
# }
# }
#
data = self.safe_dict(message, 'data')
type = self.safe_string(message, 'type')
marketId = self.safe_string(data, 'symbol')
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = 'orderBook::' + symbol
if not (symbol in self.orderbooks):
ob = self.order_book({})
ob['symbol'] = symbol
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
if type == 'snapshot':
timestamp = self.safe_integer_product(data, 'lastTime', 0.001)
parsedOrderBook = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'price', 'size')
orderbook.reset(parsedOrderBook)
elif type == 'update':
timestamp = self.safe_integer_product(data, 'time', 0.001)
side = self.safe_string(data, 'side')
bookside = orderbook['bids'] if (side == 'buy') else orderbook['asks']
self.handle_delta(bookside, data)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
self.orderbooks[symbol] = orderbook
client.resolve(self.orderbooks[symbol], messageHash)
def handle_delta(self, bookside, delta):
bidAsk = self.parse_bid_ask(delta, 'price', 'size')
bookside.storeArray(bidAsk)
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://arkm.com/docs#stream/trades
:param str symbol: unified market symbol of the market trades were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of trade structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
market = self.market(symbol)
requestArg = {
'symbol': market['id'],
}
messageHash = 'trade::' + market['symbol']
trades = await self.subscribe(messageHash, 'trades', self.extend(requestArg, params))
if self.newUpdates:
limit = trades.getLimit(market['symbol'], limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# {
# channel: 'trades',
# type: 'update',
# data: {
# symbol: 'BTC_USDT',
# revisionId: 2643896903,
# size: '0.00261',
# price: '118273.2',
# takerSide: 'buy',
# time: 1755200320146389
# }
# }
#
data = self.safe_dict(message, 'data')
marketId = self.safe_string(data, 'symbol')
symbol = self.safe_symbol(marketId)
if not (symbol in self.trades):
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
self.trades[symbol] = ArrayCache(limit)
parsed = self.parse_ws_trade(data)
stored = self.trades[symbol]
stored.append(parsed)
client.resolve(stored, 'trade::' + symbol)
def parse_ws_trade(self, trade, market=None):
# same api
return self.parse_trade(trade, market)
async def authenticate(self, params={}):
self.check_required_credentials()
expires = (self.milliseconds() + self.safe_integer(self.options, 'requestExpiration', 5000)) * 1000 # need macroseconds
wsOptions: dict = self.safe_dict(self.options, 'ws', {})
authenticated = self.safe_string(wsOptions, 'token')
if authenticated is None:
method = 'GET'
bodyStr = ''
path = 'ws'
payload = self.apiKey + str(expires) + method.upper() + '/' + path + bodyStr
decodedSecret = self.base64_to_binary(self.secret)
signature = self.hmac(self.encode(payload), decodedSecret, hashlib.sha256, 'base64')
defaultOptions: dict = {
'ws': {
'options': {
'headers': {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Arkham-Api-Key': self.apiKey,
'Arkham-Expires': str(expires),
'Arkham-Signature': signature,
},
},
},
}
self.extend_exchange_options(defaultOptions)
self.client(self.urls['api']['ws'])
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://arkm.com/docs#stream/balances
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.type]: spot or contract if not provided self.options['defaultType'] is used
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.authenticate()
await self.load_markets()
requestArg = {
'snapshot': True,
}
messageHash = 'balances'
result = await self.subscribe(messageHash, 'balances', self.extend(requestArg, params))
return result
def handle_balance(self, client: Client, message):
#
# snapshot:
#
# {
# channel: 'balances',
# type: 'snapshot',
# data: [
# {
# subaccountId: 0,
# symbol: 'USDT',
# balance: '7.035335375',
# free: '7.035335375',
# priceUSDT: '1',
# balanceUSDT: '7.035335375',
# freeUSDT: '7.035335375',
# lastUpdateReason: 'withdrawalFee',
# lastUpdateTime: '1753905990432678',
# lastUpdateId: 250483404,
# lastUpdateAmount: '-2'
# },
# {
# subaccountId: 0,
# symbol: 'SOL',
# balance: '0.03',
# free: '0.03',
# priceUSDT: '197.37823276',
# balanceUSDT: '5.921346982',
# freeUSDT: '5.921346982',
# lastUpdateReason: 'orderFill',
# lastUpdateTime: '1753777760560164',
# lastUpdateId: 248588190,
# lastUpdateAmount: '0.03'
# }
# ]
# }
#
# update:
#
# {
# channel: 'balances',
# type: 'update',
# data: {
# subaccountId: 0,
# symbol: 'USDT',
# balance: '7.028357615',
# free: '7.028357615',
# priceUSDT: '1',
# balanceUSDT: '7.028357615',
# freeUSDT: '7.028357615',
# lastUpdateReason: 'tradingFee',
# lastUpdateTime: '1755240882544056',
# lastUpdateId: 2697860787,
# lastUpdateAmount: '-0.00697776'
# }
# }
#
type = self.safe_string(message, 'type')
parsed = {}
if type == 'snapshot':
# response same api
data = self.safe_list(message, 'data')
parsed = self.parse_ws_balance(data)
parsed['info'] = message
self.balance = parsed
else:
data = self.safe_dict(message, 'data')
balancesArray = [data]
parsed = self.parse_ws_balance(balancesArray)
currencyId = self.safe_string(data, 'symbol')
code = self.safe_currency_code(currencyId)
self.balance[code] = parsed[code]
messageHash = 'balances'
client.resolve(self.safe_balance(self.balance), messageHash)
def parse_ws_balance(self, balance):
# same api
return self.parse_balance(balance)
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
"""
https://arkm.com/docs#stream/positions
watch all open positions
:param str[] [symbols]: list of unified market symbols
:param int [since]: the earliest time in ms to fetch positions for
:param int [limit]: the maximum number of positions to retrieve
:param dict params: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
"""
await self.authenticate()
await self.load_markets()
messageHash = 'positions'
if not self.is_empty(symbols):
symbols = self.market_symbols(symbols)
messageHash += '::' + ','.join(symbols)
self.positions = ArrayCacheBySymbolBySide()
requestArg = {
'snapshot': False, # no need for initial snapshot, it's done in REST api
}
newPositions = await self.subscribe(messageHash, 'positions', self.extend(requestArg, params))
if self.newUpdates:
return newPositions
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
def handle_positions(self, client, message):
#
# snapshot:
#
# {
# channel: 'positions',
# type: 'snapshot',
# data: [
# {
# subaccountId: 0,
# symbol: 'SOL_USDT_PERP',
# base: '0.059',
# quote: '-11.50618',
# openBuySize: '0',
# openSellSize: '0',
# openBuyNotional: '0',
# openSellNotional: '0',
# lastUpdateReason: 'orderFill',
# lastUpdateTime: '1755251065621402',
# lastUpdateId: 2709589783,
# lastUpdateBaseDelta: '0.059',
# lastUpdateQuoteDelta: '-11.50618',
# breakEvenPrice: '195.02',
# markPrice: '195',
# value: '11.505',
# pnl: '-0.00118',
# initialMargin: '1.1505',
# maintenanceMargin: '0.6903',
# averageEntryPrice: '195.02'
# }
# ]
# }
#
newPositions = []
if self.positions is None:
self.positions = {}
type = self.safe_string(message, 'type')
if type == 'snapshot':
data = self.safe_list(message, 'data', [])
for i in range(0, len(data)):
position = self.parse_ws_position(data[i])
if self.safe_integer(position, 'entryPrice') != 0:
newPositions.append(position)
symbol = self.safe_string(position, 'symbol')
self.positions[symbol] = position
else:
data = self.safe_dict(message, 'data')
position = self.parse_ws_position(data)
symbol = self.safe_string(position, 'symbol')
self.positions[symbol] = position
newPositions.append(position)
messageHashes = self.find_message_hashes(client, 'positions::')
for i in range(0, len(messageHashes)):
messageHash = messageHashes[i]
parts = messageHash.split('::')
symbolsString = parts[1]
symbols = symbolsString.split(',')
positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
if not self.is_empty(positions):
client.resolve(positions, messageHash)
length = len(newPositions)
if length > 0:
client.resolve(newPositions, 'positions')
def parse_ws_positions(self, positions: List[Any], symbols: List[str] = None, params={}) -> List[Position]:
symbols = self.market_symbols(symbols)
positions = self.to_array(positions)
result = []
for i in range(0, len(positions)):
position = self.extend(self.parse_ws_position(positions[i], None), params)
result.append(position)
return self.filter_by_array_positions(result, 'symbol', symbols, False)
def parse_ws_position(self, position, market=None):
# same api
return self.parse_position(position, market)
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://arkm.com/docs#stream/order_statuses
: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.authenticate()
await self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
requestArg = {
'snapshot': False,
}
isTriggerOrder = False
isTriggerOrder, params = self.handle_option_and_params(params, 'watchOrders', 'trigger', False)
rawChannel = 'trigger_orders' if isTriggerOrder else 'order_statuses'
messageHash = 'orders'
if symbol is not None:
messageHash += '::' + market['symbol']
messageHash += '::' + rawChannel
orders = await self.subscribe(messageHash, rawChannel, self.extend(requestArg, params))
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_order(self, client: Client, message):
#
# {
# channel: "order_statuses",
# type: "update",
# data: {
# orderId: 4200775347657,
# userId: 2959880,
# subaccountId: 0,
# symbol: "ARKM_USDT_PERP",
# time: "1755253639782186",
# side: "buy",
# type: "limitGtc",
# size: "10",
# price: "0.5",
# postOnly: False,
# reduceOnly: False,
# executedSize: "0",
# status: "cancelled",
# avgPrice: "0",
# executedNotional: "0",
# creditFeePaid: "0",
# marginBonusFeePaid: "0",
# quoteFeePaid: "0",
# arkmFeePaid: "0",
# revisionId: 2752963990,
# lastTime: "1755272026403545",
# clientOrderId: "",
# lastSize: "0",
# lastPrice: "0",
# lastCreditFee: "0",
# lastMarginBonusFee: "0",
# lastQuoteFee: "0",
# lastArkmFee: "0",
# }
# }
#
channel = self.safe_string(message, 'channel')
data = self.safe_dict(message, 'data')
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
order = self.parse_ws_order(data)
orders.append(order)
client.resolve(orders, 'orders')
client.resolve(orders, 'orders::' + order['symbol'] + '::' + channel)
client.resolve(orders, 'orders::' + channel)
def parse_ws_order(self, order, market=None) -> Order:
# same api
return self.parse_order(order, market)
def handle_error_message(self, client: Client, response) -> Bool:
#
# error example:
#
# {
# "id": "30005",
# "name": "InvalidNotional",
# "message": "order validation failed: invalid notional: notional 0.25 is less than min notional 1"
# }
#
message = self.safe_string(response, 'message')
if message is not None:
body = self.json(response)
errorCode = self.safe_string(response, 'id')
feedback = self.id + ' ' + body
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
raise ExchangeError(self.id + ' ' + body)
return False

964
ccxt/pro/ascendex.py Normal file
View File

@@ -0,0 +1,964 @@
# -*- 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, Bool, Int, Order, OrderBook, Str, 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 NetworkError
class ascendex(ccxt.async_support.ascendex):
def describe(self) -> Any:
return self.deep_extend(super(ascendex, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchOHLCV': True,
'watchOrderBook': True,
'watchOrders': True,
'watchTicker': False,
'watchTrades': True,
'watchTradesForSymbols': True,
},
'urls': {
'api': {
'ws': {
'public': 'wss://ascendex.com:443/api/pro/v2/stream',
'private': 'wss://ascendex.com:443/{accountGroup}/api/pro/v2/stream',
},
},
'test': {
'ws': {
'public': 'wss://api-test.ascendex-sandbox.com:443/api/pro/v2/stream',
'private': 'wss://api-test.ascendex-sandbox.com:443/{accountGroup}/api/pro/v2/stream',
},
},
},
'options': {
'tradesLimit': 1000,
'ordersLimit': 1000,
'OHLCVLimit': 1000,
'categoriesAccount': {
'cash': 'spot',
'futures': 'swap',
'margin': 'margin',
},
},
})
async def watch_public(self, messageHash, params={}):
url = self.urls['api']['ws']['public']
id = self.nonce()
request: dict = {
'id': str(id),
'op': 'sub',
}
message = self.extend(request, params)
return await self.watch(url, messageHash, message, messageHash)
async def watch_public_multiple(self, messageHashes, params={}):
url = self.urls['api']['ws']['public']
id = self.nonce()
request: dict = {
'id': str(id),
'op': 'sub',
}
message = self.extend(request, params)
return await self.watch_multiple(url, messageHashes, message, messageHashes)
async def watch_private(self, channel, messageHash, params={}):
await self.load_accounts()
accountGroup = self.safe_string(self.options, 'account-group')
url = self.urls['api']['ws']['private']
url = self.implode_params(url, {'accountGroup': accountGroup})
id = self.nonce()
request: dict = {
'id': str(id),
'op': 'sub',
'ch': channel,
}
message = self.extend(request, params)
await self.authenticate(url, params)
return await self.watch(url, messageHash, message, channel)
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://ascendex.github.io/ascendex-pro-api/#channel-bar-data
: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']
if (limit is None) or (limit > 1440):
limit = 100
interval = self.safe_string(self.timeframes, timeframe, timeframe)
channel = 'bar' + ':' + interval + ':' + market['id']
params = {
'ch': channel,
}
ohlcv = await self.watch_public(channel, params)
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
def handle_ohlcv(self, client: Client, message):
#
# {
# "m": "bar",
# "s": "ASD/USDT",
# "data": {
# "i": "1",
# "ts": 1575398940000,
# "o": "0.04993",
# "c": "0.04970",
# "h": "0.04993",
# "l": "0.04970",
# "v": "8052"
# }
# }
#
marketId = self.safe_string(message, 's')
symbol = self.safe_symbol(marketId)
channel = self.safe_string(message, 'm')
data = self.safe_value(message, 'data', {})
interval = self.safe_string(data, 'i')
messageHash = channel + ':' + interval + ':' + marketId
timeframe = self.find_timeframe(interval)
market = self.market(symbol)
parsed = self.parse_ohlcv(message, 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
stored.append(parsed)
client.resolve(stored, messageHash)
return message
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://ascendex.github.io/ascendex-pro-api/#channel-market-trades
: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>`
"""
return await self.watch_trades_for_symbols([symbol], since, limit, params)
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a list of symbols
https://ascendex.github.io/ascendex-pro-api/#channel-market-trades
:param str[] symbols: unified symbol of the market to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade'
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols, None, False, True, True)
marketIds = []
messageHashes = []
if symbols is not None:
for i in range(0, len(symbols)):
market = self.market(symbols[i])
marketIds.append(market['id'])
messageHashes.append('trades:' + market['id'])
channel = 'trades:' + ','.join(marketIds)
params = self.extend(params, {
'ch': channel,
})
trades = await self.watch_public_multiple(messageHashes, params)
if self.newUpdates:
first = self.safe_value(trades, 0)
tradeSymbol = self.safe_string(first, 'symbol')
limit = trades.getLimit(tradeSymbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# {
# "m": "trades",
# "symbol": "BTC/USDT",
# "data": [
# {
# "p": "40744.28",
# "q": "0.00150",
# "ts": 1647514330758,
# "bm": True,
# "seqnum": 72057633465800320
# }
# ]
# }
#
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
channel = self.safe_string(message, 'm')
messageHash = channel + ':' + marketId
market = self.market(symbol)
rawData = self.safe_value(message, 'data')
if rawData is None:
rawData = []
trades = self.parse_trades(rawData, market)
tradesArray = self.safe_value(self.trades, symbol)
if tradesArray is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
tradesArray = ArrayCache(limit)
for i in range(0, len(trades)):
tradesArray.append(trades[i])
self.trades[symbol] = tradesArray
client.resolve(tradesArray, messageHash)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://ascendex.github.io/ascendex-pro-api/#channel-level-2-order-book-updates
: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)
channel = 'depth' + ':' + market['id']
params = self.extend(params, {
'ch': channel,
})
orderbook = await self.watch_public(channel, params)
return orderbook.limit()
async def watch_order_book_snapshot(self, symbol: str, limit: Int = None, params={}):
await self.load_markets()
market = self.market(symbol)
action = 'depth-snapshot'
channel = action + ':' + market['id']
params = self.extend(params, {
'action': action,
'args': {
'symbol': market['id'],
},
'op': 'req',
})
orderbook = await self.watch_public(channel, params)
return orderbook.limit()
async def fetch_order_book_snapshot_custom(self, symbol: str, limit: Int = None, params={}):
restOrderBook = await self.fetch_rest_order_book_safe(symbol, limit, params)
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book()
orderbook = self.orderbooks[symbol]
orderbook.reset(restOrderBook)
return orderbook
def handle_order_book_snapshot(self, client: Client, message):
#
# {
# "m": "depth",
# "symbol": "BTC/USDT",
# "data": {
# "ts": 1647520500149,
# "seqnum": 28590487626,
# "asks": [
# [Array], [Array], [Array],
# [Array], [Array], [Array],
# ],
# "bids": [
# [Array], [Array], [Array],
# [Array], [Array], [Array],
# ]
# }
# }
#
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
channel = self.safe_string(message, 'm')
messageHash = channel + ':' + symbol
orderbook = self.orderbooks[symbol]
data = self.safe_value(message, 'data')
snapshot = self.parse_order_book(data, symbol)
snapshot['nonce'] = self.safe_integer(data, 'seqnum')
orderbook.reset(snapshot)
# unroll the accumulated deltas
messages = orderbook.cache
for i in range(0, len(messages)):
messageItem = messages[i]
self.handle_order_book_message(client, messageItem, orderbook)
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
def handle_order_book(self, client: Client, message):
#
# {
# "m": "depth",
# "symbol": "BTC/USDT",
# "data": {
# "ts": 1647515136144,
# "seqnum": 28590470736,
# "asks": [[Array], [Array]],
# "bids": [[Array], [Array], [Array], [Array], [Array], [Array]]
# }
# }
#
channel = self.safe_string(message, 'm')
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
messageHash = channel + ':' + marketId
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book({})
orderbook = self.orderbooks[symbol]
if orderbook['nonce'] is None:
orderbook.cache.append(message)
else:
self.handle_order_book_message(client, message, orderbook)
client.resolve(orderbook, messageHash)
def handle_delta(self, bookside, delta):
#
# ["40990.47","0.01619"],
#
price = self.safe_float(delta, 0)
amount = self.safe_float(delta, 1)
bookside.store(price, amount)
def handle_deltas(self, bookside, deltas):
for i in range(0, len(deltas)):
self.handle_delta(bookside, deltas[i])
def handle_order_book_message(self, client: Client, message, orderbook):
#
# {
# "m":"depth",
# "symbol":"BTC/USDT",
# "data":{
# "ts":1647527417715,
# "seqnum":28590257013,
# "asks":[
# ["40990.47","0.01619"],
# ["41021.21","0"],
# ["41031.59","0.06096"]
# ],
# "bids":[
# ["40990.46","0.76114"],
# ["40985.18","0"]
# ]
# }
# }
#
data = self.safe_value(message, 'data', {})
seqNum = self.safe_integer(data, 'seqnum')
if seqNum > orderbook['nonce']:
asks = self.safe_value(data, 'asks', [])
bids = self.safe_value(data, 'bids', [])
self.handle_deltas(orderbook['asks'], asks)
self.handle_deltas(orderbook['bids'], bids)
orderbook['nonce'] = seqNum
timestamp = self.safe_integer(data, 'ts')
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
return orderbook
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://ascendex.github.io/ascendex-pro-api/#channel-order-and-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()
type, query = self.handle_market_type_and_params('watchBalance', None, params)
channel = None
messageHash = None
if (type == 'spot') or (type == 'margin'):
accountCategories = self.safe_value(self.options, 'accountCategories', {})
accountCategory = self.safe_string(accountCategories, type, 'cash') # cash, margin,
accountCategory = accountCategory.upper()
channel = 'order:' + accountCategory # order and balance share the same channel
messageHash = 'balance:' + type
else:
channel = 'futures-account-update'
messageHash = 'balance:swap'
return await self.watch_private(channel, messageHash, query)
def handle_balance(self, client: Client, message):
#
# cash account
#
# {
# "m": "balance",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqEo",
# "ac": "CASH",
# "data": {
# "a" : "USDT",
# "sn": 8159798,
# "tb": "600",
# "ab": "600"
# }
# }
#
# margin account
#
# {
# "m": "balance",
# "accountId": "marOxpKJV83dxTRx0Eyxpa0gxc4Txt0P",
# "ac": "MARGIN",
# "data": {
# "a" : "USDT",
# "sn" : 8159802,
# "tb" : "400", # total Balance
# "ab" : "400", # available balance
# "brw": "0", # borrowws
# "int": "0" # interest
# }
# }
#
# futures
# {
# "m" : "futures-account-update", # message
# "e" : "ExecutionReport", # event type
# "t" : 1612508562129, # time
# "acc" : "futures-account-id", # account ID
# "at" : "FUTURES", # account type
# "sn" : 23128, # sequence number,
# "id" : "r177710001cbU3813942147C5kbFGOan",
# "col": [
# {
# "a": "USDT", # asset code
# "b": "1000000", # balance
# "f": "1" # discount factor
# }
# ],
# (...)
#
channel = self.safe_string(message, 'm')
result = None
type = None
if (channel == 'order') or (channel == 'futures-order'):
data = self.safe_value(message, 'data')
marketId = self.safe_string(data, 's')
market = self.safe_market(marketId)
baseAccount = self.account()
baseAccount['free'] = self.safe_string(data, 'bab')
baseAccount['total'] = self.safe_string(data, 'btb')
quoteAccount = self.account()
quoteAccount['free'] = self.safe_string(data, 'qab')
quoteAccount['total'] = self.safe_string(data, 'qtb')
if market['contract']:
type = 'swap'
result = self.safe_value(self.balance, type, {})
else:
type = market['type']
result = self.safe_value(self.balance, type, {})
result[market['base']] = baseAccount
result[market['quote']] = quoteAccount
else:
accountType = self.safe_string_lower_2(message, 'ac', 'at')
categoriesAccounts = self.safe_value(self.options, 'categoriesAccount')
type = self.safe_string(categoriesAccounts, accountType, 'spot')
result = self.safe_value(self.balance, type, {})
data = self.safe_value(message, 'data')
balances = None
if data is None:
balances = self.safe_value(message, 'col')
else:
balances = [data]
for i in range(0, len(balances)):
balance = balances[i]
code = self.safe_currency_code(self.safe_string(balance, 'a'))
account = self.account()
account['free'] = self.safe_string(balance, 'ab')
account['total'] = self.safe_string_2(balance, 'tb', 'b')
result[code] = account
messageHash = 'balance' + ':' + type
client.resolve(self.safe_balance(result), messageHash)
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
https://ascendex.github.io/ascendex-pro-api/#channel-order-and-balance
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()
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
type, query = self.handle_market_type_and_params('watchOrders', market, params)
messageHash = None
channel = None
if type != 'spot' and type != 'margin':
channel = 'futures-order'
messageHash = 'order:FUTURES'
else:
accountCategories = self.safe_value(self.options, 'accountCategories', {})
accountCategory = self.safe_string(accountCategories, type, 'cash') # cash, margin
accountCategory = accountCategory.upper()
messageHash = 'order' + ':' + accountCategory
channel = messageHash
if symbol is not None:
messageHash = messageHash + ':' + symbol
orders = await self.watch_private(channel, messageHash, query)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_order(self, client: Client, message):
#
# spot order
# {
# "m": "order",
# "accountId": "cshF5SlR9ukAXoDOuXbND4dVpBMw9gzH",
# "ac": "CASH",
# "data": {
# "sn": 19399016185,
# "orderId": "r17f9d7983faU7223046196CMlrj3bfC",
# "s": "LTC/USDT",
# "ot": "Limit",
# "t": 1647614461160,
# "p": "50",
# "q": "0.1",
# "sd": "Buy",
# "st": "New",
# "ap": "0",
# "cfq": "0",
# "sp": '',
# "err": '',
# "btb": "0",
# "bab": "0",
# "qtb": "8",
# "qab": "2.995",
# "cf": "0",
# "fa": "USDT",
# "ei": "NULL_VAL"
# }
# }
#
# futures order
# {
# "m": "futures-order",
# "sn": 19399927636,
# "e": "ExecutionReport",
# "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH", # account id
# "ac": "FUTURES",
# "t": 1647622515434, # last execution time
# (...)
# }
#
accountType = self.safe_string(message, 'ac')
messageHash = 'order:' + accountType
data = self.safe_value(message, 'data', message)
order = self.parse_ws_order(data)
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
orders.append(order)
symbolMessageHash = messageHash + ':' + order['symbol']
client.resolve(orders, symbolMessageHash)
client.resolve(orders, messageHash)
def parse_ws_order(self, order, market=None):
#
# spot order
# {
# "sn": 19399016185, #sequence number
# "orderId": "r17f9d7983faU7223046196CMlrj3bfC",
# "s": "LTC/USDT",
# "ot": "Limit", # order type
# "t": 1647614461160, # last execution timestamp
# "p": "50", # price
# "q": "0.1", # quantity
# "sd": "Buy", # side
# "st": "New", # status
# "ap": "0", # average fill price
# "cfq": "0", # cumulated fill quantity
# "sp": '', # stop price
# "err": '',
# "btb": "0", # base asset total balance
# "bab": "0", # base asset available balance
# "qtb": "8", # quote asset total balance
# "qab": "2.995", # quote asset available balance
# "cf": "0", # cumulated commission
# "fa": "USDT", # fee asset
# "ei": "NULL_VAL"
# }
#
# futures order
# {
# "m": "futures-order",
# "sn": 19399927636,
# "e": "ExecutionReport",
# "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH", # account id
# "ac": "FUTURES",
# "t": 1647622515434, # last execution time
# "ct": 1647622515413, # order creation time
# "orderId": "r17f9df469b1U7223046196Okf5Kbmd",
# "sd": "Buy", # side
# "ot": "Limit", # order type
# "ei": "NULL_VAL",
# "q": "1", # quantity
# "p": "50", #price
# "sp": "0", # stopPrice
# "spb": '', # stopTrigger
# "s": "LTC-PERP", # symbol
# "st": "New", # state
# "err": '',
# "lp": "0", # last filled price
# "lq": "0", # last filled quantity(base asset)
# "ap": "0", # average filled price
# "cfq": "0", # cummulative filled quantity(base asset)
# "f": "0", # commission fee of the current execution
# "cf": "0", # cumulative commission fee
# "fa": "USDT", # fee asset
# "psl": "0",
# "pslt": "market",
# "ptp": "0",
# "ptpt": "market"
# }
#
status = self.parse_order_status(self.safe_string(order, 'st'))
marketId = self.safe_string(order, 's')
timestamp = self.safe_integer(order, 't')
symbol = self.safe_symbol(marketId, market, '/')
lastTradeTimestamp = self.safe_integer(order, 't')
price = self.safe_string(order, 'p')
amount = self.safe_string(order, 'q')
average = self.safe_string(order, 'ap')
filled = self.safe_string(order, 'cfq')
id = self.safe_string(order, 'orderId')
type = self.safe_string_lower(order, 'ot')
side = self.safe_string_lower(order, 'sd')
feeCost = self.safe_number(order, 'cf')
fee = None
if feeCost is not None:
feeCurrencyId = self.safe_string(order, 'fa')
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
fee = {
'cost': feeCost,
'currency': feeCurrencyCode,
}
stopPrice = self.parse_number(self.omit_zero(self.safe_string(order, 'sp')))
return self.safe_order({
'info': order,
'id': id,
'clientOrderId': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': None,
'postOnly': None,
'side': side,
'price': price,
'stopPrice': stopPrice,
'triggerPrice': stopPrice,
'amount': amount,
'cost': None,
'average': average,
'filled': filled,
'remaining': None,
'status': status,
'fee': fee,
'trades': None,
}, market)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# "m": "disconnected",
# "code": 100005,
# "reason": "INVALID_WS_REQUEST_DATA",
# "info": "Session is disconnected due to missing pong message from the client"
# }
#
errorCode = self.safe_integer(message, 'code')
try:
if errorCode is not None:
feedback = self.id + ' ' + self.json(message)
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
messageString = self.safe_value(message, 'message')
if messageString is not None:
self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback)
return False
except Exception as e:
if isinstance(e, AuthenticationError):
messageHash = 'authenticated'
client.reject(e, messageHash)
if messageHash in client.subscriptions:
del client.subscriptions[messageHash]
else:
client.reject(e)
return True
def handle_authenticate(self, client: Client, message):
#
# {m: "auth", id: "1647605234", code: 0}
#
messageHash = 'authenticated'
client.resolve(message, messageHash)
def handle_message(self, client: Client, message):
if self.handle_error_message(client, message):
return
#
# {m: "ping", hp: 3}
#
# {m: "sub", ch: "bar:BTC/USDT", code: 0}
#
# {m: 'sub', id: "1647515701", ch: "depth:BTC/USDT", code: 0}
#
# {m: "connected", type: "unauth"}
#
# {m: "auth", id: "1647605234", code: 0}
#
# order or balance sub
# {
# "m": "sub",
# "id": "1647605952",
# "ch": "order:cshF5SlR9ukAXoDOuXbND4dVpBMw9gzH", or futures-order
# "code": 0
# }
#
# ohlcv
# {
# "m": "bar",
# "s": "BTC/USDT",
# "data": {
# "i": "1",
# "ts": 1647510060000,
# "o": "40813.93",
# "c": "40804.57",
# "h": "40814.21",
# "l": "40804.56",
# "v": "0.01537"
# }
# }
#
# trades
#
# {
# "m": "trades",
# "symbol": "BTC/USDT",
# "data": [
# {
# "p": "40762.26",
# "q": "0.01500",
# "ts": 1647514306759,
# "bm": True,
# "seqnum": 72057633465795180
# }
# ]
# }
#
# orderbook deltas
#
# {
# "m":"depth",
# "symbol":"BTC/USDT",
# "data":{
# "ts":1647527417715,
# "seqnum":28590257013,
# "asks":[
# ["40990.47","0.01619"],
# ["41021.21","0"],
# ["41031.59","0.06096"]
# ],
# "bids":[
# ["40990.46","0.76114"],
# ["40985.18","0"]
# ]
# }
# }
#
# orderbook snapshot
# {
# "m": "depth-snapshot",
# "symbol": "BTC/USDT",
# "data": {
# "ts": 1647525938513,
# "seqnum": 28590504772,
# "asks": [
# [Array], [Array], [Array], [Array], [Array], [Array], [Array],
# [Array], [Array], [Array], [Array], [Array], [Array], [Array],
# [Array], [Array], [Array], [Array], [Array], [Array], [Array],
# (...)
# ]
# }
#
# spot order update
# {
# "m": "order",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "ac": "CASH",
# "data": {
# "s": "BTC/USDT",
# "sn": 8159711,
# "sd": "Buy",
# "ap": "0",
# "bab": "2006.5974027",
# "btb": "2006.5974027",
# "cf": "0",
# "cfq": "0",
# (...)
# }
# }
# future order update
# {
# "m": "futures-order",
# "sn": 19404258063,
# "e": "ExecutionReport",
# "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH",
# "ac": "FUTURES",
# "t": 1647681792543,
# "ct": 1647622515413,
# "orderId": "r17f9df469b1U7223046196Okf5KbmdL",
# (...)
# "ptpt": "None"
# }
#
# balance update cash
# {
# "m": "balance",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "ac": "CASH",
# "data": {
# "a" : "USDT",
# "sn": 8159798,
# "tb": "600",
# "ab": "600"
# }
# }
#
# balance update margin
# {
# "m": "balance",
# "accountId": "marOxpKJV83dxTRx0Eyxpa0gxc4Txt0P",
# "ac": "MARGIN",
# "data": {
# "a" : "USDT",
# "sn" : 8159802,
# "tb" : "400",
# "ab" : "400",
# "brw": "0",
# "int": "0"
# }
# }
#
subject = self.safe_string(message, 'm')
methods: dict = {
'ping': self.handle_ping,
'auth': self.handle_authenticate,
'sub': self.handle_subscription_status,
'depth': self.handle_order_book,
'depth-snapshot': self.handle_order_book_snapshot,
'trades': self.handle_trades,
'bar': self.handle_ohlcv,
'balance': self.handle_balance,
'futures-account-update': self.handle_balance,
}
method = self.safe_value(methods, subject)
if method is not None:
method(client, message)
if (subject == 'order') or (subject == 'futures-order'):
# self.handle_order(client, message)
# balance updates may be in the order structure
# they may also be standalone balance updates related to account transfers
self.handle_order(client, message)
if subject == 'order':
self.handle_balance(client, message)
def handle_subscription_status(self, client: Client, message):
#
# {m: "sub", ch: "bar:BTC/USDT", code: 0}
#
# {m: 'sub', id: "1647515701", ch: "depth:BTC/USDT", code: 0}
#
channel = self.safe_string(message, 'ch', '')
if channel.find('depth') > -1 and not (channel.find('depth-snapshot') > -1):
self.handle_order_book_subscription(client, message)
return message
def handle_order_book_subscription(self, client: Client, message):
channel = self.safe_string(message, 'ch')
parts = channel.split(':')
marketId = parts[1]
market = self.safe_market(marketId)
symbol = market['symbol']
if symbol in self.orderbooks:
del self.orderbooks[symbol]
self.orderbooks[symbol] = self.order_book({})
if self.options['defaultType'] == 'swap' or market['contract']:
self.spawn(self.fetch_order_book_snapshot_custom, symbol)
else:
self.spawn(self.watch_order_book_snapshot, symbol)
async def pong(self, client, message):
#
# {m: "ping", hp: 3}
#
try:
await client.send({'op': 'pong', 'hp': self.safe_integer(message, 'hp')})
except Exception as e:
error = NetworkError(self.id + ' handlePing failed with error ' + self.json(e))
client.reset(error)
def handle_ping(self, client: Client, message):
self.spawn(self.pong, client, message)
async def authenticate(self, url, params={}):
self.check_required_credentials()
messageHash = 'authenticated'
client = self.client(url)
future = self.safe_value(client.subscriptions, messageHash)
if future is None:
timestamp = str(self.milliseconds())
urlParts = url.split('/')
partsLength = len(urlParts)
path = self.safe_string(urlParts, partsLength - 1)
version = self.safe_string(urlParts, partsLength - 2)
auth = timestamp + '+' + version + '/' + path
secret = self.base64_to_binary(self.secret)
signature = self.hmac(self.encode(auth), secret, hashlib.sha256, 'base64')
request: dict = {
'op': 'auth',
'id': str(self.nonce()),
't': timestamp,
'key': self.apiKey,
'sig': signature,
}
future = await self.watch(url, messageHash, self.extend(request, params), messageHash)
client.subscriptions[messageHash] = future
return future

1240
ccxt/pro/backpack.py Normal file

File diff suppressed because it is too large Load Diff

43
ccxt/pro/bequant.py Normal file
View File

@@ -0,0 +1,43 @@
# -*- 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
from ccxt.pro.hitbtc import hitbtc
from ccxt.base.types import Any
import ccxt.async_support.hitbtc as hitbtcRest
import ccxt.async_support.bequant as bequantRest
class bequant(hitbtc):
def describe(self) -> Any:
# eslint-disable-next-line new-cap
describeExtended = self.get_describe_for_extended_ws_exchange(bequantRest(), hitbtcRest(), super(bequant, self).describe())
return self.deep_extend(describeExtended, {
'id': 'bequant',
'name': 'Bequant',
'countries': ['MT'], # Malta
'pro': True,
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg',
'api': {
'public': 'https://api.bequant.io/api/3',
'private': 'https://api.bequant.io/api/3',
'ws': {
'public': 'wss://api.bequant.io/api/3/ws/public',
'private': 'wss://api.bequant.io/api/3/ws/trading',
},
},
'www': 'https://bequant.io',
'doc': [
'https://api.bequant.io/',
],
'fees': [
'https://bequant.io/fees-and-limits',
],
'referral': 'https://bequant.io',
},
})

4201
ccxt/pro/binance.py Normal file

File diff suppressed because it is too large Load Diff

32
ccxt/pro/binancecoinm.py Normal file
View File

@@ -0,0 +1,32 @@
# -*- 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
from ccxt.pro.binance import binance
from ccxt.base.types import Any
import ccxt.async_support.binancecoinm as binancecoinmRest
class binancecoinm(binance):
def describe(self) -> Any:
# eslint-disable-next-line new-cap
restInstance = binancecoinmRest()
restDescribe = restInstance.describe()
extended = self.deep_extend(super(binancecoinm, self).describe(), restDescribe)
return self.deep_extend(extended, {
'id': 'binancecoinm',
'name': 'Binance COIN-M',
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/117738721-668c8d80-b205-11eb-8c49-3fad84c4a07f.jpg',
'doc': 'https://developers.binance.com/en',
},
'options': {
'fetchMarkets': {
'types': ['inverse'],
},
'defaultSubType': 'inverse',
},
})

71
ccxt/pro/binanceus.py Normal file
View File

@@ -0,0 +1,71 @@
# -*- 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
from ccxt.pro.binance import binance
from ccxt.base.types import Any
import ccxt.async_support.binanceus as binanceusRest
class binanceus(binance):
def describe(self) -> Any:
# eslint-disable-next-line new-cap
restInstance = binanceusRest()
restDescribe = restInstance.describe()
parentWsDescribe = super(binanceus, self).describe_data()
extended = self.deep_extend(restDescribe, parentWsDescribe)
return self.deep_extend(extended, {
'id': 'binanceus',
'name': 'Binance US',
'countries': ['US'], # US
'certified': False,
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg',
'api': {
'ws': {
'spot': 'wss://stream.binance.us:9443/ws',
},
'web': 'https://www.binance.us',
'sapi': 'https://api.binance.us/sapi/v1',
'wapi': 'https://api.binance.us/wapi/v3',
'public': 'https://api.binance.us/api/v3',
'private': 'https://api.binance.us/api/v3',
'v3': 'https://api.binance.us/api/v3',
'v1': 'https://api.binance.us/api/v1',
},
'www': 'https://www.binance.us',
'referral': 'https://www.binance.us/?ref=35005074',
'doc': 'https://github.com/binance-us/binance-official-api-docs',
'fees': 'https://www.binance.us/en/fee/schedule',
},
'has': {
'createOrderWithTakeProfitAndStopLossWs': False,
'createReduceOnlyOrderWs': False,
'createStopLossOrderWs': False,
'createTakeProfitOrderWs': False,
'fetchPositionForSymbolWs': False,
'fetchPositionsForSymbolWs': False,
'fetchPositionsWs': False,
'fetchPositionWs': False,
'unWatchPositions': False,
'watchLiquidations': False,
'watchLiquidationsForSymbols': False,
'watchMarkPrice': False,
'watchMarkPrices': False,
'watchMyLiquidations': False,
'watchMyLiquidationsForSymbols': False,
'watchPosition': False,
'watchPositions': False,
},
'options': {
'fetchCurrencies': False,
'quoteOrderQty': False,
'defaultType': 'spot',
'fetchMarkets': {
'types': ['spot'],
},
},
})

36
ccxt/pro/binanceusdm.py Normal file
View File

@@ -0,0 +1,36 @@
# -*- 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
from ccxt.pro.binance import binance
from ccxt.base.types import Any
from ccxt.base.errors import InvalidOrder
class binanceusdm(binance):
def describe(self) -> Any:
return self.deep_extend(super(binanceusdm, self).describe(), {
'id': 'binanceusdm',
'name': 'Binance USDⓈ-M',
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/117738721-668c8d80-b205-11eb-8c49-3fad84c4a07f.jpg',
'doc': 'https://developers.binance.com/en',
},
'options': {
'fetchMarkets': {
'types': ['linear'],
},
'defaultSubType': 'linear',
},
# https://binance-docs.github.io/apidocs/futures/en/#error-codes
# https://developers.binance.com/docs/derivatives/usds-margined-futures/error-code
'exceptions': {
'exact': {
'-5021': InvalidOrder, # {"code":-5021,"msg":"Due to the order could not be filled immediately, the FOK order has been rejected."}
'-5022': InvalidOrder, # {"code":-5022,"msg":"Due to the order could not be executed, the Post Only order will be rejected."}
'-5028': InvalidOrder, # {"code":-5028,"msg":"Timestamp for self request is outside of the ME recvWindow."}
},
},
})

1459
ccxt/pro/bingx.py Normal file

File diff suppressed because it is too large Load Diff

1218
ccxt/pro/bitfinex.py Normal file

File diff suppressed because it is too large Load Diff

2712
ccxt/pro/bitget.py Normal file

File diff suppressed because it is too large Load Diff

622
ccxt/pro/bithumb.py Normal file
View File

@@ -0,0 +1,622 @@
# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
from ccxt.base.types import Any, Balances, Bool, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
class bithumb(ccxt.async_support.bithumb):
def describe(self) -> Any:
return self.deep_extend(super(bithumb, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchOrders': True,
'watchTicker': True,
'watchTickers': True,
'watchTrades': True,
'watchOrderBook': True,
'watchOHLCV': False,
},
'urls': {
'api': {
'ws': {
'public': 'wss://pubwss.bithumb.com/pub/ws', # v1.2.0
'publicV2': 'wss://ws-api.bithumb.com/websocket/v1', # v2.1.5
'privateV2': 'wss://ws-api.bithumb.com/websocket/v1/private', # v2.1.5
},
},
},
'options': {},
'streaming': {},
'exceptions': {},
})
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param 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://github.com/ccxt/ccxt/wiki/Manual#ticker-structure>`
"""
url = self.urls['api']['ws']['public']
await self.load_markets()
market = self.market(symbol)
messageHash = 'ticker:' + market['symbol']
request: dict = {
'type': 'ticker',
'symbols': [market['base'] + '_' + market['quote']],
'tickTypes': [self.safe_string(params, 'tickTypes', '24H')],
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash)
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
: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()
url = self.urls['api']['ws']['public']
marketIds = []
messageHashes = []
symbols = self.market_symbols(symbols, None, False, True, True)
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
marketIds.append(market['base'] + '_' + market['quote'])
messageHashes.append('ticker:' + market['symbol'])
request: dict = {
'type': 'ticker',
'symbols': marketIds,
'tickTypes': [self.safe_string(params, 'tickTypes', '24H')],
}
message = self.extend(request, params)
newTicker = await self.watch_multiple(url, messageHashes, message, messageHashes)
if self.newUpdates:
result: dict = {}
result[newTicker['symbol']] = newTicker
return result
return self.filter_by_array(self.tickers, 'symbol', symbols)
def handle_ticker(self, client: Client, message):
#
# {
# "type" : "ticker",
# "content" : {
# "symbol" : "BTC_KRW", # 통화코드
# "tickType" : "24H", # 변동 기준시간- 30M, 1H, 12H, 24H, MID
# "date" : "20200129", # 일자
# "time" : "121844", # 시간
# "openPrice" : "2302", # 시가
# "closePrice" : "2317", # 종가
# "lowPrice" : "2272", # 저가
# "highPrice" : "2344", # 고가
# "value" : "2831915078.07065789", # 누적거래금액
# "volume" : "1222314.51355788", # 누적거래량
# "sellVolume" : "760129.34079004", # 매도누적거래량
# "buyVolume" : "462185.17276784", # 매수누적거래량
# "prevClosePrice" : "2326", # 전일종가
# "chgRate" : "0.65", # 변동률
# "chgAmt" : "15", # 변동금액
# "volumePower" : "60.80" # 체결강도
# }
# }
#
content = self.safe_dict(message, 'content', {})
marketId = self.safe_string(content, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
ticker = self.parse_ws_ticker(content)
messageHash = 'ticker:' + symbol
self.tickers[symbol] = ticker
client.resolve(self.tickers[symbol], messageHash)
def parse_ws_ticker(self, ticker, market=None):
#
# {
# "symbol" : "BTC_KRW", # 통화코드
# "tickType" : "24H", # 변동 기준시간- 30M, 1H, 12H, 24H, MID
# "date" : "20200129", # 일자
# "time" : "121844", # 시간
# "openPrice" : "2302", # 시가
# "closePrice" : "2317", # 종가
# "lowPrice" : "2272", # 저가
# "highPrice" : "2344", # 고가
# "value" : "2831915078.07065789", # 누적거래금액
# "volume" : "1222314.51355788", # 누적거래량
# "sellVolume" : "760129.34079004", # 매도누적거래량
# "buyVolume" : "462185.17276784", # 매수누적거래량
# "prevClosePrice" : "2326", # 전일종가
# "chgRate" : "0.65", # 변동률
# "chgAmt" : "15", # 변동금액
# "volumePower" : "60.80" # 체결강도
# }
#
date = self.safe_string(ticker, 'date', '')
time = self.safe_string(ticker, 'time', '')
datetime = date[0:4] + '-' + date[4:6] + '-' + date[6:8] + 'T' + time[0:2] + ':' + time[2:4] + ':' + time[4:6]
marketId = self.safe_string(ticker, 'symbol')
return self.safe_ticker({
'symbol': self.safe_symbol(marketId, market, '_'),
'timestamp': self.parse8601(datetime),
'datetime': datetime,
'high': self.safe_string(ticker, 'highPrice'),
'low': self.safe_string(ticker, 'lowPrice'),
'bid': None,
'bidVolume': self.safe_string(ticker, 'buyVolume'),
'ask': None,
'askVolume': self.safe_string(ticker, 'sellVolume'),
'vwap': None,
'open': self.safe_string(ticker, 'openPrice'),
'close': self.safe_string(ticker, 'closePrice'),
'last': None,
'previousClose': self.safe_string(ticker, 'prevClosePrice'),
'change': self.safe_string(ticker, 'chgAmt'),
'percentage': self.safe_string(ticker, 'chgRate'),
'average': None,
'baseVolume': self.safe_string(ticker, 'volume'),
'quoteVolume': self.safe_string(ticker, 'value'),
'info': ticker,
}, market)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
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://github.com/ccxt/ccxt/wiki/Manual#order-book-structure>` indexed by market symbols
"""
await self.load_markets()
url = self.urls['api']['ws']['public']
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orderbook' + ':' + symbol
request: dict = {
'type': 'orderbookdepth',
'symbols': [market['base'] + '_' + market['quote']],
}
orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# {
# "type" : "orderbookdepth",
# "content" : {
# "list" : [
# {
# "symbol" : "BTC_KRW",
# "orderType" : "ask", # 주문타입 bid / ask
# "price" : "10593000", # 호가
# "quantity" : "1.11223318", # 잔량
# "total" : "3" # 건수
# },
# {"symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10596000", "quantity" : "0.5495", "total" : "8"},
# {"symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10598000", "quantity" : "18.2085", "total" : "10"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10532000", "quantity" : "0", "total" : "0"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10572000", "quantity" : "2.3324", "total" : "4"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10571000", "quantity" : "1.469", "total" : "3"},
# {"symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10569000", "quantity" : "0.5152", "total" : "2"}
# ],
# "datetime":1580268255864325 # 일시
# }
# }
#
content = self.safe_dict(message, 'content', {})
list = self.safe_list(content, 'list', [])
first = self.safe_dict(list, 0, {})
marketId = self.safe_string(first, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
timestampStr = self.safe_string(content, 'datetime')
timestamp = self.parse_to_int(timestampStr[0:13])
if not (symbol in self.orderbooks):
ob = self.order_book()
ob['symbol'] = symbol
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
self.handle_deltas(orderbook, list)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
messageHash = 'orderbook' + ':' + symbol
client.resolve(orderbook, messageHash)
def handle_delta(self, orderbook, delta):
#
# {
# symbol: "ETH_BTC",
# orderType: "bid",
# price: "0.07349517",
# quantity: "0",
# total: "0",
# }
#
sideId = self.safe_string(delta, 'orderType')
side = 'bids' if (sideId == 'bid') else 'asks'
bidAsk = self.parse_bid_ask(delta, 'price', 'quantity')
orderbookSide = orderbook[side]
orderbookSide.storeArray(bidAsk)
def handle_deltas(self, orderbook, deltas):
for i in range(0, len(deltas)):
self.handle_delta(orderbook, deltas[i])
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
: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://github.com/ccxt/ccxt/wiki/Manual#public-trades>`
"""
await self.load_markets()
url = self.urls['api']['ws']['public']
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'trade:' + symbol
request: dict = {
'type': 'transaction',
'symbols': [market['base'] + '_' + market['quote']],
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client, message):
#
# {
# "type" : "transaction",
# "content" : {
# "list" : [
# {
# "symbol" : "BTC_KRW",
# "buySellGb" : "1",
# "contPrice" : "10579000",
# "contQty" : "0.01",
# "contAmt" : "105790.00",
# "contDtm" : "2020-01-29 12:24:18.830039",
# "updn" : "dn"
# }
# ]
# }
# }
#
content = self.safe_dict(message, 'content', {})
rawTrades = self.safe_list(content, 'list', [])
for i in range(0, len(rawTrades)):
rawTrade = rawTrades[i]
marketId = self.safe_string(rawTrade, 'symbol')
symbol = self.safe_symbol(marketId, None, '_')
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]
parsed = self.parse_ws_trade(rawTrade)
trades.append(parsed)
messageHash = 'trade' + ':' + symbol
client.resolve(trades, messageHash)
def parse_ws_trade(self, trade, market=None):
#
# {
# "symbol" : "BTC_KRW",
# "buySellGb" : "1",
# "contPrice" : "10579000",
# "contQty" : "0.01",
# "contAmt" : "105790.00",
# "contDtm" : "2020-01-29 12:24:18.830038",
# "updn" : "dn"
# }
#
marketId = self.safe_string(trade, 'symbol')
datetime = self.safe_string(trade, 'contDtm')
# that date is not UTC iso8601, but exchange's local time, -9hr difference
timestamp = self.parse8601(datetime) - 32400000
sideId = self.safe_string(trade, 'buySellGb')
return self.safe_trade({
'id': None,
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': self.safe_symbol(marketId, market, '_'),
'order': None,
'type': None,
'side': 'buy' if (sideId == '1') else 'sell',
'takerOrMaker': None,
'price': self.safe_string(trade, 'contPrice'),
'amount': self.safe_string(trade, 'contQty'),
'cost': self.safe_string(trade, 'contAmt'),
'fee': None,
}, market)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# "status" : "5100",
# "resmsg" : "Invalid Filter Syntax"
# }
#
if not ('status' in message):
return True
errorCode = self.safe_string(message, 'status')
try:
if errorCode != '0000':
msg = self.safe_string(message, 'resmsg')
raise ExchangeError(self.id + ' ' + msg)
return True
except Exception as e:
client.reject(e)
return True
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://apidocs.bithumb.com/v2.1.5/reference/%EB%82%B4-%EC%9E%90%EC%82%B0-myasset
: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()
await self.authenticate()
url = self.urls['api']['ws']['privateV2']
messageHash = 'myAsset'
request = [
{'ticket': 'ccxt'},
{'type': messageHash},
]
balance = await self.watch(url, messageHash, request, messageHash)
return balance
def handle_balance(self, client: Client, message):
#
# {
# "type": "myAsset",
# "assets": [
# {
# "currency": "KRW",
# "balance": "2061832.35",
# "locked": "3824127.3"
# }
# ],
# "asset_timestamp": 1727052537592,
# "timestamp": 1727052537687,
# "stream_type": "REALTIME"
# }
#
messageHash = 'myAsset'
assets = self.safe_list(message, 'assets', [])
if self.balance is None:
self.balance = {}
for i in range(0, len(assets)):
asset = assets[i]
currencyId = self.safe_string(asset, 'currency')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string(asset, 'balance')
account['used'] = self.safe_string(asset, 'locked')
self.balance[code] = account
self.balance['info'] = message
timestamp = self.safe_integer(message, 'timestamp')
self.balance['timestamp'] = timestamp
self.balance['datetime'] = self.iso8601(timestamp)
self.balance = self.safe_balance(self.balance)
client.resolve(self.balance, messageHash)
async def authenticate(self, params={}):
self.check_required_credentials()
wsOptions: dict = self.safe_dict(self.options, 'ws', {})
authenticated = self.safe_string(wsOptions, 'token')
if authenticated is None:
payload: dict = {
'access_key': self.apiKey,
'nonce': self.uuid(),
'timestamp': self.milliseconds(),
}
jwtToken = self.jwt(payload, self.encode(self.secret), 'sha256')
wsOptions['token'] = jwtToken
wsOptions['options'] = {
'headers': {
'authorization': 'Bearer ' + jwtToken,
},
}
self.options['ws'] = wsOptions
url = self.urls['api']['ws']['privateV2']
client = self.client(url)
return client
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://apidocs.bithumb.com/v2.1.5/reference/%EB%82%B4-%EC%A3%BC%EB%AC%B8-%EB%B0%8F-%EC%B2%B4%EA%B2%B0-myorder
: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 str[] [params.codes]: market codes to filter orders
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.authenticate()
url = self.urls['api']['ws']['privateV2']
messageHash = 'myOrder'
codes = self.safe_list(params, 'codes', [])
request = [
{'ticket': 'ccxt'},
{'type': messageHash, 'codes': codes},
]
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash = messageHash + ':' + symbol
orders = await self.watch(url, messageHash, request, messageHash)
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):
#
# {
# "type": "myOrder",
# "code": "KRW-BTC",
# "uuid": "C0101000000001818113",
# "ask_bid": "BID",
# "order_type": "limit",
# "state": "trade",
# "trade_uuid": "C0101000000001744207",
# "price": 1927000,
# "volume": 0.4697,
# "remaining_volume": 0.0803,
# "executed_volume": 0.4697,
# "trades_count": 1,
# "reserved_fee": 0,
# "remaining_fee": 0,
# "paid_fee": 0,
# "executed_funds": 905111.9000,
# "trade_timestamp": 1727052318148,
# "order_timestamp": 1727052318074,
# "timestamp": 1727052318369,
# "stream_type": "REALTIME"
# }
#
messageHash = 'myOrder'
parsed = self.parse_ws_order(message)
symbol = self.safe_string(parsed, 'symbol')
# orderId = self.safe_string(parsed, 'id')
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
cachedOrders = self.orders
cachedOrders.append(parsed)
client.resolve(cachedOrders, messageHash)
symbolSpecificMessageHash = messageHash + ':' + symbol
client.resolve(cachedOrders, symbolSpecificMessageHash)
def parse_ws_order(self, order, market=None):
#
# {
# "type": "myOrder",
# "code": "KRW-BTC",
# "uuid": "C0101000000001818113",
# "ask_bid": "BID",
# "order_type": "limit",
# "state": "trade",
# "trade_uuid": "C0101000000001744207",
# "price": 1927000,
# "volume": 0.4697,
# "remaining_volume": 0.0803,
# "executed_volume": 0.4697,
# "trades_count": 1,
# "reserved_fee": 0,
# "remaining_fee": 0,
# "paid_fee": 0,
# "executed_funds": 905111.9000,
# "trade_timestamp": 1727052318148,
# "order_timestamp": 1727052318074,
# "timestamp": 1727052318369,
# "stream_type": "REALTIME"
# }
#
marketId = self.safe_string(order, 'code')
symbol = self.safe_symbol(marketId, market, '-')
timestamp = self.safe_integer(order, 'order_timestamp')
sideId = self.safe_string(order, 'ask_bid')
side = ('buy') if (sideId == 'BID') else ('sell')
typeId = self.safe_string(order, 'order_type')
type = None
if typeId == 'limit':
type = 'limit'
elif typeId == 'price':
type = 'market'
elif typeId == 'market':
type = 'market'
stateId = self.safe_string(order, 'state')
status = None
if stateId == 'wait':
status = 'open'
elif stateId == 'trade':
status = 'open'
elif stateId == 'done':
status = 'closed'
elif stateId == 'cancel':
status = 'canceled'
price = self.safe_string(order, 'price')
amount = self.safe_string(order, 'volume')
remaining = self.safe_string(order, 'remaining_volume')
filled = self.safe_string(order, 'executed_volume')
cost = self.safe_string(order, 'executed_funds')
feeCost = self.safe_string(order, 'paid_fee')
fee = None
if feeCost is not None:
marketForFee = self.safe_market(marketId, market)
feeCurrency = self.safe_string(marketForFee, 'quote')
fee = {
'cost': feeCost,
'currency': feeCurrency,
}
return self.safe_order({
'info': order,
'id': self.safe_string(order, 'uuid'),
'clientOrderId': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': self.safe_integer(order, 'trade_timestamp'),
'symbol': symbol,
'type': type,
'timeInForce': None,
'postOnly': None,
'side': side,
'price': price,
'stopPrice': None,
'triggerPrice': None,
'amount': amount,
'cost': cost,
'average': None,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': fee,
'trades': None,
}, market)
def handle_message(self, client: Client, message):
if not self.handle_error_message(client, message):
return
topic = self.safe_string(message, 'type')
if topic is not None:
methods: dict = {
'ticker': self.handle_ticker,
'orderbookdepth': self.handle_order_book,
'transaction': self.handle_trades,
'myAsset': self.handle_balance,
'myOrder': self.handle_orders,
}
method = self.safe_value(methods, topic)
if method is not None:
method(client, message)

1578
ccxt/pro/bitmart.py Normal file

File diff suppressed because it is too large Load Diff

1694
ccxt/pro/bitmex.py Normal file

File diff suppressed because it is too large Load Diff

457
ccxt/pro/bitopro.py Normal file
View File

@@ -0,0 +1,457 @@
# -*- 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
import hashlib
from ccxt.base.types import Any, Balances, Int, Market, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
class bitopro(ccxt.async_support.bitopro):
def describe(self) -> Any:
return self.deep_extend(super(bitopro, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchMyTrades': True,
'watchOHLCV': False,
'watchOrderBook': True,
'watchOrders': False,
'watchTicker': True,
'watchTickers': False,
'watchTrades': True,
'watchTradesForSymbols': False,
},
'urls': {
'ws': {
'public': 'wss://stream.bitopro.com:443/ws/v1/pub',
'private': 'wss://stream.bitopro.com:443/ws/v1/pub/auth',
},
},
'requiredCredentials': {
'apiKey': True,
'secret': True,
'login': True,
},
'options': {
'tradesLimit': 1000,
'ordersLimit': 1000,
'ws': {
'options': {
# headers is required for the authentication
'headers': {},
},
},
},
})
async def watch_public(self, path, messageHash, marketId):
url = self.urls['ws']['public'] + '/' + path + '/' + marketId
return await self.watch(url, messageHash, None, messageHash)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/ws/public/order_book_stream.md
: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
"""
if limit is not None:
if (limit != 5) and (limit != 10) and (limit != 20) and (limit != 50) and (limit != 100) and (limit != 500) and (limit != 1000):
raise ExchangeError(self.id + ' watchOrderBook limit argument must be None, 5, 10, 20, 50, 100, 500 or 1000')
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'ORDER_BOOK' + ':' + symbol
endPart = None
if limit is None:
endPart = market['id']
else:
endPart = market['id'] + ':' + self.number_to_string(limit)
orderbook = await self.watch_public('order-books', messageHash, endPart)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# {
# "event": "ORDER_BOOK",
# "timestamp": 1650121915308,
# "datetime": "2022-04-16T15:11:55.308Z",
# "pair": "BTC_TWD",
# "limit": 5,
# "scale": 0,
# "bids": [
# {price: "1188178", amount: '0.0425', count: 1, total: "0.0425"},
# ],
# "asks": [
# {
# "price": "1190740",
# "amount": "0.40943964",
# "count": 1,
# "total": "0.40943964"
# },
# ]
# }
#
marketId = self.safe_string(message, 'pair')
market = self.safe_market(marketId, None, '_')
symbol = market['symbol']
event = self.safe_string(message, 'event')
messageHash = event + ':' + symbol
orderbook = self.safe_value(self.orderbooks, symbol)
if orderbook is None:
orderbook = self.order_book({})
timestamp = self.safe_integer(message, 'timestamp')
snapshot = self.parse_order_book(message, symbol, timestamp, 'bids', 'asks', 'price', 'amount')
orderbook.reset(snapshot)
client.resolve(orderbook, messageHash)
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/ws/public/trade_stream.md
: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']
messageHash = 'TRADE' + ':' + symbol
trades = await self.watch_public('trades', messageHash, market['id'])
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trade(self, client: Client, message):
#
# {
# "event": "TRADE",
# "timestamp": 1650116346665,
# "datetime": "2022-04-16T13:39:06.665Z",
# "pair": "BTC_TWD",
# "data": [
# {
# "event": '',
# "datetime": '',
# "pair": '',
# "timestamp": 1650116227,
# "price": "1189429",
# "amount": "0.0153127",
# "isBuyer": True
# },
# ]
# }
#
marketId = self.safe_string(message, 'pair')
market = self.safe_market(marketId, None, '_')
symbol = market['symbol']
event = self.safe_string(message, 'event')
messageHash = event + ':' + symbol
rawData = self.safe_value(message, 'data', [])
trades = self.parse_trades(rawData, market)
tradesCache = self.safe_value(self.trades, symbol)
if tradesCache is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
tradesCache = ArrayCache(limit)
for i in range(0, len(trades)):
tradesCache.append(trades[i])
self.trades[symbol] = tradesCache
client.resolve(tradesCache, 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
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/ws/private/matches_stream.md
: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>`
"""
self.check_required_credentials()
await self.load_markets()
messageHash = 'USER_TRADE'
if symbol is not None:
market = self.market(symbol)
messageHash = messageHash + ':' + market['symbol']
url = self.urls['ws']['private'] + '/' + 'user-trades'
self.authenticate(url)
trades = await self.watch(url, messageHash, None, messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_my_trade(self, client: Client, message):
#
# {
# "event": "USER_TRADE",
# "timestamp": 1694667358782,
# "datetime": "2023-09-14T12:55:58.782Z",
# "data": {
# "base": "usdt",
# "quote": "twd",
# "side": "ask",
# "price": "32.039",
# "volume": "1",
# "fee": "6407800",
# "feeCurrency": "twd",
# "transactionTimestamp": 1694667358,
# "eventTimestamp": 1694667358,
# "orderID": 390733918,
# "orderType": "LIMIT",
# "matchID": "bd07673a-94b1-419e-b5ee-d7b723261a5d",
# "isMarket": False,
# "isMaker": False
# }
# }
#
data = self.safe_value(message, 'data', {})
baseId = self.safe_string(data, 'base')
quoteId = self.safe_string(data, 'quote')
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
symbol = self.symbol(base + '/' + quote)
messageHash = self.safe_string(message, 'event')
if self.myTrades is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
self.myTrades = ArrayCacheBySymbolById(limit)
trades = self.myTrades
parsed = self.parse_ws_trade(data)
trades.append(parsed)
client.resolve(trades, messageHash)
client.resolve(trades, messageHash + ':' + symbol)
def parse_ws_trade(self, trade: dict, market: Market = None) -> Trade:
#
# {
# "base": "usdt",
# "quote": "twd",
# "side": "ask",
# "price": "32.039",
# "volume": "1",
# "fee": "6407800",
# "feeCurrency": "twd",
# "transactionTimestamp": 1694667358,
# "eventTimestamp": 1694667358,
# "orderID": 390733918,
# "orderType": "LIMIT",
# "matchID": "bd07673a-94b1-419e-b5ee-d7b723261a5d",
# "isMarket": False,
# "isMaker": False
# }
#
id = self.safe_string(trade, 'matchID')
orderId = self.safe_string(trade, 'orderID')
timestamp = self.safe_timestamp(trade, 'transactionTimestamp')
baseId = self.safe_string(trade, 'base')
quoteId = self.safe_string(trade, 'quote')
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
symbol = self.symbol(base + '/' + quote)
market = self.safe_market(symbol, market)
price = self.safe_string(trade, 'price')
type = self.safe_string_lower(trade, 'orderType')
side = self.safe_string(trade, 'side')
if side is not None:
if side == 'ask':
side = 'sell'
elif side == 'bid':
side = 'buy'
amount = self.safe_string(trade, 'volume')
fee = None
feeAmount = self.safe_string(trade, 'fee')
feeSymbol = self.safe_currency_code(self.safe_string(trade, 'feeCurrency'))
if feeAmount is not None:
fee = {
'cost': feeAmount,
'currency': feeSymbol,
'rate': None,
}
isMaker = self.safe_value(trade, 'isMaker')
takerOrMaker = None
if isMaker is not None:
if isMaker:
takerOrMaker = 'maker'
else:
takerOrMaker = 'taker'
return self.safe_trade({
'id': id,
'info': trade,
'order': orderId,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'takerOrMaker': takerOrMaker,
'type': type,
'side': side,
'price': price,
'amount': amount,
'cost': None,
'fee': fee,
}, market)
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/ws/public/ticker_stream.md
: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']
messageHash = 'TICKER' + ':' + symbol
return await self.watch_public('tickers', messageHash, market['id'])
def handle_ticker(self, client: Client, message):
#
# {
# "event": "TICKER",
# "timestamp": 1650119165710,
# "datetime": "2022-04-16T14:26:05.710Z",
# "pair": "BTC_TWD",
# "lastPrice": "1189110",
# "lastPriceUSD": "40919.1328",
# "lastPriceTWD": "1189110",
# "isBuyer": True,
# "priceChange24hr": "1.23",
# "volume24hr": "7.2090",
# "volume24hrUSD": "294985.5375",
# "volume24hrTWD": "8572279",
# "high24hr": "1193656",
# "low24hr": "1179321"
# }
#
marketId = self.safe_string(message, 'pair')
# market-ids are lowercase in REST API and uppercase in WS API
market = self.safe_market(marketId.lower(), None, '_')
symbol = market['symbol']
event = self.safe_string(message, 'event')
messageHash = event + ':' + symbol
result = self.parse_ticker(message, market)
result['symbol'] = self.safe_string(market, 'symbol') # symbol returned from REST's parseTicker is distorted for WS, so re-set it from market object
timestamp = self.safe_integer(message, 'timestamp')
result['timestamp'] = timestamp
result['datetime'] = self.iso8601(timestamp) # we shouldn't set "datetime" string provided by server, values are obviously wrong offset from UTC
self.tickers[symbol] = result
client.resolve(result, messageHash)
def authenticate(self, url):
if (self.clients is not None) and (url in self.clients):
return
self.check_required_credentials()
nonce = self.milliseconds()
rawData = self.json({
'nonce': nonce,
'identity': self.login,
})
payload = self.string_to_base64(rawData)
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha384)
defaultOptions: dict = {
'ws': {
'options': {
'headers': {},
},
},
}
# self.options = self.extend(defaultOptions, self.options)
self.extend_exchange_options(defaultOptions)
originalHeaders = self.options['ws']['options']['headers']
headers: dict = {
'X-BITOPRO-API': 'ccxt',
'X-BITOPRO-APIKEY': self.apiKey,
'X-BITOPRO-PAYLOAD': payload,
'X-BITOPRO-SIGNATURE': signature,
}
self.options['ws']['options']['headers'] = headers
# instantiate client
self.client(url)
self.options['ws']['options']['headers'] = originalHeaders
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/ws/private/user_balance_stream.md
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
self.check_required_credentials()
await self.load_markets()
messageHash = 'ACCOUNT_BALANCE'
url = self.urls['ws']['private'] + '/' + 'account-balance'
self.authenticate(url)
return await self.watch(url, messageHash, None, messageHash)
def handle_balance(self, client: Client, message):
#
# {
# "event": "ACCOUNT_BALANCE",
# "timestamp": 1650450505715,
# "datetime": "2022-04-20T10:28:25.715Z",
# "data": {
# "ADA": {
# "currency": "ADA",
# "amount": "0",
# "available": "0",
# "stake": "0",
# "tradable": True
# },
# }
# }
#
event = self.safe_string(message, 'event')
data = self.safe_value(message, 'data')
timestamp = self.safe_integer(message, 'timestamp')
datetime = self.safe_string(message, 'datetime')
currencies = list(data.keys())
result: dict = {
'info': data,
'timestamp': timestamp,
'datetime': datetime,
}
for i in range(0, len(currencies)):
currency = self.safe_string(currencies, i)
balance = self.safe_value(data, currency)
currencyId = self.safe_string(balance, 'currency')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string(balance, 'available')
account['total'] = self.safe_string(balance, 'amount')
result[code] = account
self.balance = self.safe_balance(result)
client.resolve(self.balance, event)
def handle_message(self, client: Client, message):
methods: dict = {
'TRADE': self.handle_trade,
'TICKER': self.handle_ticker,
'ORDER_BOOK': self.handle_order_book,
'ACCOUNT_BALANCE': self.handle_balance,
'USER_TRADE': self.handle_my_trade,
}
event = self.safe_string(message, 'event')
method = self.safe_value(methods, event)
if method is not None:
method(client, message)

447
ccxt/pro/bitrue.py Normal file
View File

@@ -0,0 +1,447 @@
# -*- 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 ArrayCacheBySymbolById
from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str
from ccxt.async_support.base.ws.client import Client
from typing import List
class bitrue(ccxt.async_support.bitrue):
def describe(self) -> Any:
return self.deep_extend(super(bitrue, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchTicker': False,
'watchTickers': False,
'watchTrades': False,
'watchMyTrades': False,
'watchOrders': True,
'watchOrderBook': True,
'watchOHLCV': False,
},
'urls': {
'api': {
'open': 'https://open.bitrue.com',
'ws': {
'public': 'wss://ws.bitrue.com/market/ws',
'private': 'wss://wsapi.bitrue.com',
},
},
},
'api': {
'open': {
'v1': {
'private': {
'post': {
'poseidon/api/v1/listenKey': 1,
},
'put': {
'poseidon/api/v1/listenKey/{listenKey}': 1,
},
'delete': {
'poseidon/api/v1/listenKey/{listenKey}': 1,
},
},
},
},
},
'options': {
'listenKeyRefreshRate': 1800000, # 30 mins
'ws': {
'gunzip': True,
},
},
})
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://github.com/Bitrue-exchange/Spot-official-api-docs#balance-update
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
url = await self.authenticate()
messageHash = 'balance'
message: dict = {
'event': 'sub',
'params': {
'channel': 'user_balance_update',
},
}
request = self.deep_extend(message, params)
return await self.watch(url, messageHash, request, messageHash)
def handle_balance(self, client: Client, message):
#
# {
# "e": "BALANCE",
# "x": "OutboundAccountPositionTradeEvent",
# "E": 1657799510175,
# "I": "302274978401288200",
# "i": 1657799510175,
# "B": [{
# "a": "btc",
# "F": "0.0006000000000000",
# "T": 1657799510000,
# "f": "0.0006000000000000",
# "t": 0
# },
# {
# "a": "usdt",
# "T": 0,
# "L": "0.0000000000000000",
# "l": "-11.8705317318000000",
# "t": 1657799510000
# }
# ],
# "u": 1814396
# }
#
# {
# "e": "BALANCE",
# "x": "OutboundAccountPositionOrderEvent",
# "E": 1670051332478,
# "I": "353662845694083072",
# "i": 1670051332478,
# "B": [
# {
# "a": "eth",
# "F": "0.0400000000000000",
# "T": 1670051332000,
# "f": "-0.0100000000000000",
# "L": "0.0100000000000000",
# "l": "0.0100000000000000",
# "t": 1670051332000
# }
# ],
# "u": 2285311
# }
#
balances = self.safe_value(message, 'B', [])
self.parse_ws_balances(balances)
messageHash = 'balance'
client.resolve(self.balance, messageHash)
def parse_ws_balances(self, balances):
#
# [{
# "a": "btc",
# "F": "0.0006000000000000",
# "T": 1657799510000,
# "f": "0.0006000000000000",
# "t": 0
# },
# {
# "a": "usdt",
# "T": 0,
# "L": "0.0000000000000000",
# "l": "-11.8705317318000000",
# "t": 1657799510000
# }]
#
self.balance['info'] = balances
for i in range(0, len(balances)):
balance = balances[i]
currencyId = self.safe_string(balance, 'a')
code = self.safe_currency_code(currencyId)
account = self.account()
free = self.safe_string(balance, 'F')
used = self.safe_string(balance, 'L')
balanceUpdateTime = self.safe_integer(balance, 'T', 0)
lockBalanceUpdateTime = self.safe_integer(balance, 't', 0)
updateFree = balanceUpdateTime != 0
updateUsed = lockBalanceUpdateTime != 0
if updateFree or updateUsed:
if updateFree:
account['free'] = free
if updateUsed:
account['used'] = used
self.balance[code] = account
self.balance = self.safe_balance(self.balance)
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
watches information on user orders
https://github.com/Bitrue-exchange/Spot-official-api-docs#order-update
:param str symbol:
:param int [since]: timestamp in ms of the earliest order
:param int [limit]: the maximum amount of orders to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order structure <https://docs.ccxt.com/#/?id=order-structure>` indexed by market symbols
"""
await self.load_markets()
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
url = await self.authenticate()
messageHash = 'orders'
message: dict = {
'event': 'sub',
'params': {
'channel': 'user_order_update',
},
}
request = self.deep_extend(message, params)
orders = await self.watch(url, messageHash, request, messageHash)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_order(self, client: Client, message):
#
# {
# "e": "ORDER",
# "i": 16122802798,
# "E": 1657882521876,
# "I": "302623154710888464",
# "u": 1814396,
# "s": "btcusdt",
# "S": 2,
# "o": 1,
# "q": "0.0005",
# "p": "60000",
# "X": 0,
# "x": 1,
# "z": "0",
# "n": "0",
# "N": "usdt",
# "O": 1657882521876,
# "L": "0",
# "l": "0",
# "Y": "0"
# }
#
parsed = self.parse_ws_order(message)
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
orders.append(parsed)
messageHash = 'orders'
client.resolve(self.orders, messageHash)
def parse_ws_order(self, order, market=None):
#
# {
# "e": "ORDER",
# "i": 16122802798,
# "E": 1657882521876,
# "I": "302623154710888464",
# "u": 1814396,
# "s": "btcusdt",
# "S": 2,
# "o": 1,
# "q": "0.0005",
# "p": "60000",
# "X": 0,
# "x": 1,
# "z": "0",
# "n": "0",
# "N": "usdt",
# "O": 1657882521876,
# "L": "0",
# "l": "0",
# "Y": "0"
# }
#
timestamp = self.safe_integer(order, 'E')
marketId = self.safe_string_upper(order, 's')
typeId = self.safe_string(order, 'o')
sideId = self.safe_integer(order, 'S')
# 1: buy
# 2: sell
side = 'buy' if (sideId == 1) else 'sell'
statusId = self.safe_string(order, 'X')
feeCurrencyId = self.safe_string(order, 'N')
return self.safe_order({
'info': order,
'id': self.safe_string(order, 'i'),
'clientOrderId': self.safe_string(order, 'c'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': self.safe_integer(order, 'T'),
'symbol': self.safe_symbol(marketId, market),
'type': self.parse_ws_order_type(typeId),
'timeInForce': None,
'postOnly': None,
'side': side,
'price': self.safe_string(order, 'p'),
'triggerPrice': None,
'amount': self.safe_string(order, 'q'),
'cost': self.safe_string(order, 'Y'),
'average': None,
'filled': self.safe_string(order, 'z'),
'remaining': None,
'status': self.parse_ws_order_status(statusId),
'fee': {
'currency': self.safe_currency_code(feeCurrencyId),
'cost': self.safe_number(order, 'n'),
},
}, market)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orderbook:' + symbol
marketIdLowercase = market['id'].lower()
channel = 'market_' + marketIdLowercase + '_simple_depth_step0'
url = self.urls['api']['ws']['public']
message: dict = {
'event': 'sub',
'params': {
'cb_id': marketIdLowercase,
'channel': channel,
},
}
request = self.deep_extend(message, params)
return await self.watch(url, messageHash, request, messageHash)
def handle_order_book(self, client: Client, message):
#
# {
# "channel": "market_ethbtc_simple_depth_step0",
# "ts": 1670056708670,
# "tick": {
# "buys": [
# [
# "0.075170",
# "67.153"
# ],
# [
# "0.075169",
# "17.195"
# ],
# [
# "0.075166",
# "29.788"
# ],
# ]
# "asks": [
# [
# "0.075171",
# "0.256"
# ],
# [
# "0.075172",
# "0.160"
# ],
# ]
# }
# }
#
channel = self.safe_string(message, 'channel')
parts = channel.split('_')
marketId = self.safe_string_upper(parts, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
timestamp = self.safe_integer(message, 'ts')
tick = self.safe_value(message, 'tick', {})
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book()
orderbook = self.orderbooks[symbol]
snapshot = self.parse_order_book(tick, symbol, timestamp, 'buys', 'asks')
orderbook.reset(snapshot)
messageHash = 'orderbook:' + symbol
client.resolve(orderbook, messageHash)
def parse_ws_order_type(self, typeId):
types: dict = {
'1': 'limit',
'2': 'market',
'3': 'limit',
}
return self.safe_string(types, typeId, typeId)
def parse_ws_order_status(self, status):
statuses: dict = {
'0': 'open', # The order has not been accepted by the engine.
'1': 'open', # The order has been accepted by the engine.
'2': 'closed', # The order has been completed.
'3': 'open', # A part of the order has been filled.
'4': 'canceled', # The order has been canceled.
'7': 'open', # Stop order placed.
}
return self.safe_string(statuses, status, status)
def handle_ping(self, client: Client, message):
self.spawn(self.pong, client, message)
async def pong(self, client, message):
#
# {
# "ping": 1670057540627
# }
#
time = self.safe_integer(message, 'ping')
pong: dict = {
'pong': time,
}
await client.send(pong)
def handle_message(self, client: Client, message):
if 'channel' in message:
self.handle_order_book(client, message)
elif 'ping' in message:
self.handle_ping(client, message)
else:
event = self.safe_string(message, 'e')
handlers: dict = {
'BALANCE': self.handle_balance,
'ORDER': self.handle_order,
}
handler = self.safe_value(handlers, event)
if handler is not None:
handler(client, message)
async def authenticate(self, params={}):
listenKey = self.safe_value(self.options, 'listenKey')
if listenKey is None:
response = await self.openV1PrivatePostPoseidonApiV1ListenKey(params)
#
# {
# "msg": "succ",
# "code": 200,
# "data": {
# "listenKey": "7d1ec51340f499d85bb33b00a96ef680bda28869d5c3374a444c5ca4847d1bf0"
# }
# }
#
data = self.safe_value(response, 'data', {})
key = self.safe_string(data, 'listenKey')
self.options['listenKey'] = key
self.options['listenKeyUrl'] = self.urls['api']['ws']['private'] + '/stream?listenKey=' + key
refreshTimeout = self.safe_integer(self.options, 'listenKeyRefreshRate', 1800000)
self.delay(refreshTimeout, self.keep_alive_listen_key)
return self.options['listenKeyUrl']
async def keep_alive_listen_key(self, params={}):
listenKey = self.safe_string(self.options, 'listenKey')
request: dict = {
'listenKey': listenKey,
}
try:
await self.openV1PrivatePutPoseidonApiV1ListenKeyListenKey(self.extend(request, params))
#
# ಠ_ಠ
# {
# "msg": "succ",
# "code": "200"
# }
#
except Exception as error:
self.options['listenKey'] = None
self.options['listenKeyUrl'] = None
return
refreshTimeout = self.safe_integer(self.options, 'listenKeyRefreshRate', 1800000)
self.delay(refreshTimeout, self.keep_alive_listen_key)

555
ccxt/pro/bitstamp.py Normal file
View File

@@ -0,0 +1,555 @@
# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
from ccxt.base.types import Any, Bool, Int, Order, OrderBook, Str, 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 ArgumentsRequired
from ccxt.base.precise import Precise
class bitstamp(ccxt.async_support.bitstamp):
def describe(self) -> Any:
return self.deep_extend(super(bitstamp, self).describe(), {
'has': {
'ws': True,
'watchOrderBook': True,
'watchOrders': True,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchOHLCV': False,
'watchTicker': False,
'watchTickers': False,
},
'urls': {
'api': {
'ws': 'wss://ws.bitstamp.net',
},
},
'options': {
'expiresIn': '',
'userId': '',
'wsSessionToken': '',
'watchOrderBook': {
'snapshotDelay': 6,
'snapshotMaxRetries': 3,
},
'tradesLimit': 1000,
'OHLCVLimit': 1000,
},
'exceptions': {
'exact': {
'4009': AuthenticationError,
},
},
})
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
: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']
messageHash = 'orderbook:' + symbol
channel = 'diff_order_book_' + market['id']
url = self.urls['api']['ws']
request: dict = {
'event': 'bts:subscribe',
'data': {
'channel': channel,
},
}
message = self.extend(request, params)
orderbook = await self.watch(url, messageHash, message, messageHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# initial snapshot is fetched with ccxt's fetchOrderBook
# the feed does not include a snapshot, just the deltas
#
# {
# "data": {
# "timestamp": "1583656800",
# "microtimestamp": "1583656800237527",
# "bids": [
# ["8732.02", "0.00002478", "1207590500704256"],
# ["8729.62", "0.01600000", "1207590502350849"],
# ["8727.22", "0.01800000", "1207590504296448"],
# ],
# "asks": [
# ["8735.67", "2.00000000", "1207590693249024"],
# ["8735.67", "0.01700000", "1207590693634048"],
# ["8735.68", "1.53294500", "1207590692048896"],
# ],
# },
# "event": "data",
# "channel": "diff_order_book_btcusd"
# }
#
channel = self.safe_string(message, 'channel')
parts = channel.split('_')
marketId = self.safe_string(parts, 3)
symbol = self.safe_symbol(marketId)
storedOrderBook = self.safe_value(self.orderbooks, symbol)
nonce = self.safe_value(storedOrderBook, 'nonce')
delta = self.safe_value(message, 'data')
deltaNonce = self.safe_integer(delta, 'microtimestamp')
messageHash = 'orderbook:' + symbol
if nonce is None:
cacheLength = len(storedOrderBook.cache)
# the rest API is very delayed
# usually it takes at least 4-5 deltas to resolve
snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 6)
if cacheLength == snapshotDelay:
self.spawn(self.load_order_book, client, messageHash, symbol, None, {})
storedOrderBook.cache.append(delta)
return
elif nonce >= deltaNonce:
return
self.handle_delta(storedOrderBook, delta)
client.resolve(storedOrderBook, messageHash)
def handle_delta(self, orderbook, delta):
timestamp = self.safe_timestamp(delta, 'timestamp')
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
orderbook['nonce'] = self.safe_integer(delta, 'microtimestamp')
bids = self.safe_value(delta, 'bids', [])
asks = self.safe_value(delta, 'asks', [])
storedBids = orderbook['bids']
storedAsks = orderbook['asks']
self.handle_bid_asks(storedBids, bids)
self.handle_bid_asks(storedAsks, asks)
def handle_bid_asks(self, bookSide, bidAsks):
for i in range(0, len(bidAsks)):
bidAsk = self.parse_bid_ask(bidAsks[i])
bookSide.storeArray(bidAsk)
def get_cache_index(self, orderbook, deltas):
# we will consider it a fail
firstElement = deltas[0]
firstElementNonce = self.safe_integer(firstElement, 'microtimestamp')
nonce = self.safe_integer(orderbook, 'nonce')
if nonce < firstElementNonce:
return -1
for i in range(0, len(deltas)):
delta = deltas[i]
deltaNonce = self.safe_integer(delta, 'microtimestamp')
if deltaNonce == nonce:
return i + 1
return len(deltas)
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
: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']
messageHash = 'trades:' + symbol
url = self.urls['api']['ws']
channel = 'live_trades_' + market['id']
request: dict = {
'event': 'bts:subscribe',
'data': {
'channel': channel,
},
}
message = self.extend(request, params)
trades = await self.watch(url, messageHash, message, messageHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def parse_ws_trade(self, trade, market=None):
#
# {
# "buy_order_id": 1211625836466176,
# "amount_str": "1.08000000",
# "timestamp": "1584642064",
# "microtimestamp": "1584642064685000",
# "id": 108637852,
# "amount": 1.08,
# "sell_order_id": 1211625840754689,
# "price_str": "6294.77",
# "type": 1,
# "price": 6294.77
# }
#
microtimestamp = self.safe_integer(trade, 'microtimestamp')
id = self.safe_string(trade, 'id')
timestamp = self.parse_to_int(microtimestamp / 1000)
price = self.safe_string(trade, 'price')
amount = self.safe_string(trade, 'amount')
symbol = market['symbol']
sideRaw = self.safe_integer(trade, 'type')
side = 'buy' if (sideRaw == 0) else 'sell'
return self.safe_trade({
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': None,
'type': None,
'takerOrMaker': None,
'side': side,
'price': price,
'amount': amount,
'cost': None,
'fee': None,
}, market)
def handle_trade(self, client: Client, message):
#
# {
# "data": {
# "buy_order_id": 1207733769326592,
# "amount_str": "0.14406384",
# "timestamp": "1583691851",
# "microtimestamp": "1583691851934000",
# "id": 106833903,
# "amount": 0.14406384,
# "sell_order_id": 1207733765476352,
# "price_str": "8302.92",
# "type": 0,
# "price": 8302.92
# },
# "event": "trade",
# "channel": "live_trades_btcusd"
# }
#
# the trade streams push raw trade information in real-time
# each trade has a unique buyer and seller
channel = self.safe_string(message, 'channel')
parts = channel.split('_')
marketId = self.safe_string(parts, 2)
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = 'trades:' + symbol
data = self.safe_value(message, 'data')
trade = self.parse_ws_trade(data, market)
tradesArray = self.safe_value(self.trades, symbol)
if tradesArray is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
tradesArray = ArrayCache(limit)
self.trades[symbol] = tradesArray
tradesArray.append(trade)
client.resolve(tradesArray, 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>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' watchOrders() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
channel = 'private-my_orders'
messageHash = channel + '_' + market['id']
subscription: dict = {
'symbol': symbol,
'limit': limit,
'type': channel,
'params': params,
}
orders = await self.subscribe_private(subscription, messageHash, params)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)
def handle_orders(self, client: Client, message):
#
# {
# "data":{
# "id":"1463471322288128",
# "id_str":"1463471322288128",
# "order_type":1,
# "datetime":"1646127778",
# "microtimestamp":"1646127777950000",
# "amount":0.05,
# "amount_str":"0.05000000",
# "price":1000,
# "price_str":"1000.00"
# },
# "channel":"private-my_orders_ltcusd-4848701",
# "event": "order_deleted" # field only present for cancelOrder
# }
#
channel = self.safe_string(message, 'channel')
order = self.safe_value(message, 'data', {})
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
if self.orders is None:
self.orders = ArrayCacheBySymbolById(limit)
stored = self.orders
subscription = self.safe_value(client.subscriptions, channel)
symbol = self.safe_string(subscription, 'symbol')
market = self.market(symbol)
order['event'] = self.safe_string(message, 'event')
parsed = self.parse_ws_order(order, market)
stored.append(parsed)
client.resolve(self.orders, channel)
def parse_ws_order(self, order, market=None):
#
# {
# "id": "1894876776091648",
# "id_str": "1894876776091648",
# "order_type": 0,
# "order_subtype": 0,
# "datetime": "1751451375",
# "microtimestamp": "1751451375070000",
# "amount": 1.1,
# "amount_str": "1.10000000",
# "amount_traded": "0",
# "amount_at_create": "1.10000000",
# "price": 10.23,
# "price_str": "10.23",
# "is_liquidation": False,
# "trade_account_id": 0
# }
#
id = self.safe_string(order, 'id_str')
orderTypeRaw = self.safe_string_lower(order, 'order_type')
side = 'sell' if (orderTypeRaw == '1') else 'buy'
orderSubTypeRaw = self.safe_string_lower(order, 'order_subtype') # https://www.bitstamp.net/websocket/v2/#:~:text=order_subtype
orderType: Str = None
timeInForce: Str = None
if orderSubTypeRaw == '0':
orderType = 'limit'
elif orderSubTypeRaw == '2':
orderType = 'market'
elif orderSubTypeRaw == '4':
orderType = 'limit'
timeInForce = 'IOC'
elif orderSubTypeRaw == '6':
orderType = 'limit'
timeInForce = 'FOK'
elif orderSubTypeRaw == '8':
orderType = 'limit'
timeInForce = 'GTD'
price = self.safe_string(order, 'price_str')
amount = self.safe_string(order, 'amount_str')
filled = self.safe_string(order, 'amount_traded')
event = self.safe_string(order, 'event')
status = None
if Precise.string_eq(filled, amount):
status = 'closed'
elif event == 'order_deleted':
status = 'canceled'
timestamp = self.safe_timestamp(order, 'datetime')
market = self.safe_market(None, market)
symbol = market['symbol']
return self.safe_order({
'info': order,
'symbol': symbol,
'id': id,
'clientOrderId': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': None,
'type': orderType,
'timeInForce': timeInForce,
'postOnly': None,
'side': side,
'price': price,
'stopPrice': None,
'triggerPrice': None,
'amount': amount,
'cost': None,
'average': None,
'filled': filled,
'remaining': None,
'status': status,
'fee': None,
'trades': None,
}, market)
def handle_order_book_subscription(self, client: Client, message):
channel = self.safe_string(message, 'channel')
parts = channel.split('_')
marketId = self.safe_string(parts, 3)
symbol = self.safe_symbol(marketId)
self.orderbooks[symbol] = self.order_book()
def handle_subscription_status(self, client: Client, message):
#
# {
# "event": "bts:subscription_succeeded",
# "channel": "detail_order_book_btcusd",
# "data": {},
# }
# {
# "event": "bts:subscription_succeeded",
# "channel": "private-my_orders_ltcusd-4848701",
# "data": {}
# }
#
channel = self.safe_string(message, 'channel')
if channel.find('order_book') > -1:
self.handle_order_book_subscription(client, message)
def handle_subject(self, client: Client, message):
#
# {
# "data": {
# "timestamp": "1583656800",
# "microtimestamp": "1583656800237527",
# "bids": [
# ["8732.02", "0.00002478", "1207590500704256"],
# ["8729.62", "0.01600000", "1207590502350849"],
# ["8727.22", "0.01800000", "1207590504296448"],
# ],
# "asks": [
# ["8735.67", "2.00000000", "1207590693249024"],
# ["8735.67", "0.01700000", "1207590693634048"],
# ["8735.68", "1.53294500", "1207590692048896"],
# ],
# },
# "event": "data",
# "channel": "detail_order_book_btcusd"
# }
#
# private order
# {
# "data":{
# "id":"1463471322288128",
# "id_str":"1463471322288128",
# "order_type":1,
# "datetime":"1646127778",
# "microtimestamp":"1646127777950000",
# "amount":0.05,
# "amount_str":"0.05000000",
# "price":1000,
# "price_str":"1000.00"
# },
# "channel":"private-my_orders_ltcusd-4848701",
# "event": "order_deleted" # field only present for cancelOrder
# }
#
channel = self.safe_string(message, 'channel')
methods: dict = {
'live_trades': self.handle_trade,
'diff_order_book': self.handle_order_book,
'private-my_orders': self.handle_orders,
}
keys = list(methods.keys())
for i in range(0, len(keys)):
key = keys[i]
if channel.find(key) > -1:
method = methods[key]
method(client, message)
def handle_error_message(self, client: Client, message) -> Bool:
# {
# "event": "bts:error",
# "channel": '',
# "data": {code: 4009, message: "Connection is unauthorized."}
# }
event = self.safe_string(message, 'event')
if event == 'bts:error':
feedback = self.id + ' ' + self.json(message)
data = self.safe_value(message, 'data', {})
code = self.safe_number(data, 'code')
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
return True
def handle_message(self, client: Client, message):
if not self.handle_error_message(client, message):
return
#
# {
# "event": "bts:subscription_succeeded",
# "channel": "detail_order_book_btcusd",
# "data": {},
# }
#
# {
# "data": {
# "timestamp": "1583656800",
# "microtimestamp": "1583656800237527",
# "bids": [
# ["8732.02", "0.00002478", "1207590500704256"],
# ["8729.62", "0.01600000", "1207590502350849"],
# ["8727.22", "0.01800000", "1207590504296448"],
# ],
# "asks": [
# ["8735.67", "2.00000000", "1207590693249024"],
# ["8735.67", "0.01700000", "1207590693634048"],
# ["8735.68", "1.53294500", "1207590692048896"],
# ],
# },
# "event": "data",
# "channel": "detail_order_book_btcusd"
# }
#
# {
# "event": "bts:subscription_succeeded",
# "channel": "private-my_orders_ltcusd-4848701",
# "data": {}
# }
#
event = self.safe_string(message, 'event')
if event == 'bts:subscription_succeeded':
self.handle_subscription_status(client, message)
else:
self.handle_subject(client, message)
async def authenticate(self, params={}):
self.check_required_credentials()
time = self.milliseconds()
expiresIn = self.safe_integer(self.options, 'expiresIn')
if (expiresIn is None) or (time > expiresIn):
response = await self.privatePostWebsocketsToken(params)
#
# {
# "valid_sec":60,
# "token":"siPaT4m6VGQCdsDCVbLBemiphHQs552e",
# "user_id":4848701
# }
#
sessionToken = self.safe_string(response, 'token')
if sessionToken is not None:
userId = self.safe_string(response, 'user_id')
validity = self.safe_integer_product(response, 'valid_sec', 1000)
self.options['expiresIn'] = self.sum(time, validity)
self.options['userId'] = userId
self.options['wsSessionToken'] = sessionToken
async def subscribe_private(self, subscription, messageHash, params={}):
url = self.urls['api']['ws']
await self.authenticate()
messageHash += '-' + self.options['userId']
request: dict = {
'event': 'bts:subscribe',
'data': {
'channel': messageHash,
'auth': self.options['wsSessionToken'],
},
}
subscription['messageHash'] = messageHash
return await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)

571
ccxt/pro/bittrade.py Normal file
View File

@@ -0,0 +1,571 @@
# -*- 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, ArrayCacheByTimestamp
from ccxt.base.types import Any, Bool, Int, OrderBook, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
class bittrade(ccxt.async_support.bittrade):
def describe(self) -> Any:
return self.deep_extend(super(bittrade, self).describe(), {
'has': {
'ws': True,
'watchOrderBook': True,
'watchTickers': False, # for now
'watchTicker': True,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchBalance': False, # for now
'watchOHLCV': True,
},
'urls': {
'api': {
'ws': {
'api': {
'public': 'wss://{hostname}/ws',
'private': 'wss://{hostname}/ws/v2',
},
},
},
},
'options': {
'tradesLimit': 1000,
'OHLCVLimit': 1000,
'api': 'api', # or api-aws for clients hosted on AWS
'ws': {
'gunzip': True,
},
},
})
def request_id(self):
requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
self.options['requestId'] = requestId
return str(requestId)
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
: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']
# only supports a limit of 150 at self time
messageHash = 'market.' + market['id'] + '.detail'
api = self.safe_string(self.options, 'api', 'api')
hostname: dict = {'hostname': self.hostname}
url = self.implode_params(self.urls['api']['ws'][api]['public'], hostname)
requestId = self.request_id()
request: dict = {
'sub': messageHash,
'id': requestId,
}
subscription: dict = {
'id': requestId,
'messageHash': messageHash,
'symbol': symbol,
'params': params,
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
def handle_ticker(self, client: Client, message):
#
# {
# "ch": "market.btcusdt.detail",
# "ts": 1583494163784,
# "tick": {
# "id": 209988464418,
# "low": 8988,
# "high": 9155.41,
# "open": 9078.91,
# "close": 9136.46,
# "vol": 237813910.5928412,
# "amount": 26184.202558551195,
# "version": 209988464418,
# "count": 265673
# }
# }
#
tick = self.safe_value(message, 'tick', {})
ch = self.safe_string(message, 'ch')
parts = ch.split('.')
marketId = self.safe_string(parts, 1)
market = self.safe_market(marketId)
ticker = self.parse_ticker(tick, market)
timestamp = self.safe_value(message, 'ts')
ticker['timestamp'] = timestamp
ticker['datetime'] = self.iso8601(timestamp)
symbol = ticker['symbol']
self.tickers[symbol] = ticker
client.resolve(ticker, ch)
return message
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
: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']
# only supports a limit of 150 at self time
messageHash = 'market.' + market['id'] + '.trade.detail'
api = self.safe_string(self.options, 'api', 'api')
hostname: dict = {'hostname': self.hostname}
url = self.implode_params(self.urls['api']['ws'][api]['public'], hostname)
requestId = self.request_id()
request: dict = {
'sub': messageHash,
'id': requestId,
}
subscription: dict = {
'id': requestId,
'messageHash': messageHash,
'symbol': symbol,
'params': params,
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# {
# "ch": "market.btcusdt.trade.detail",
# "ts": 1583495834011,
# "tick": {
# "id": 105004645372,
# "ts": 1583495833751,
# "data": [
# {
# "id": 1.050046453727319e+22,
# "ts": 1583495833751,
# "tradeId": 102090727790,
# "amount": 0.003893,
# "price": 9150.01,
# "direction": "sell"
# }
# ]
# }
# }
#
tick = self.safe_value(message, 'tick', {})
data = self.safe_value(tick, 'data', {})
ch = self.safe_string(message, 'ch')
parts = ch.split('.')
marketId = self.safe_string(parts, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
tradesCache = self.safe_value(self.trades, symbol)
if tradesCache is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
tradesCache = ArrayCache(limit)
self.trades[symbol] = tradesCache
for i in range(0, len(data)):
trade = self.parse_trade(data[i], market)
tradesCache.append(trade)
client.resolve(tradesCache, ch)
return message
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
: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']
interval = self.safe_string(self.timeframes, timeframe, timeframe)
messageHash = 'market.' + market['id'] + '.kline.' + interval
api = self.safe_string(self.options, 'api', 'api')
hostname: dict = {'hostname': self.hostname}
url = self.implode_params(self.urls['api']['ws'][api]['public'], hostname)
requestId = self.request_id()
request: dict = {
'sub': messageHash,
'id': requestId,
}
subscription: dict = {
'id': requestId,
'messageHash': messageHash,
'symbol': symbol,
'timeframe': timeframe,
'params': params,
}
ohlcv = await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
def handle_ohlcv(self, client: Client, message):
#
# {
# "ch": "market.btcusdt.kline.1min",
# "ts": 1583501786794,
# "tick": {
# "id": 1583501760,
# "open": 9094.5,
# "close": 9094.51,
# "low": 9094.5,
# "high": 9094.51,
# "amount": 0.44639786263800907,
# "vol": 4059.76919054,
# "count": 16
# }
# }
#
ch = self.safe_string(message, 'ch')
parts = ch.split('.')
marketId = self.safe_string(parts, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
interval = self.safe_string(parts, 3)
timeframe = self.find_timeframe(interval)
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
tick = self.safe_value(message, 'tick')
parsed = self.parse_ohlcv(tick, market)
stored.append(parsed)
client.resolve(stored, ch)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
: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
"""
if (limit is not None) and (limit != 150):
raise ExchangeError(self.id + ' watchOrderBook accepts limit = 150 only')
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
# only supports a limit of 150 at self time
limit = 150 if (limit is None) else limit
messageHash = 'market.' + market['id'] + '.mbp.' + str(limit)
api = self.safe_string(self.options, 'api', 'api')
hostname: dict = {'hostname': self.hostname}
url = self.implode_params(self.urls['api']['ws'][api]['public'], hostname)
requestId = self.request_id()
request: dict = {
'sub': messageHash,
'id': requestId,
}
subscription: dict = {
'id': requestId,
'messageHash': messageHash,
'symbol': symbol,
'limit': limit,
'params': params,
'method': self.handle_order_book_subscription,
}
orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
return orderbook.limit()
def handle_order_book_snapshot(self, client: Client, message, subscription):
#
# {
# "id": 1583473663565,
# "rep": "market.btcusdt.mbp.150",
# "status": "ok",
# "data": {
# "seqNum": 104999417756,
# "bids": [
# [9058.27, 0],
# [9058.43, 0],
# [9058.99, 0],
# ],
# "asks": [
# [9084.27, 0.2],
# [9085.69, 0],
# [9085.81, 0],
# ]
# }
# }
#
symbol = self.safe_string(subscription, 'symbol')
messageHash = self.safe_string(subscription, 'messageHash')
orderbook = self.orderbooks[symbol]
data = self.safe_value(message, 'data')
snapshot = self.parse_order_book(data, symbol)
snapshot['nonce'] = self.safe_integer(data, 'seqNum')
orderbook.reset(snapshot)
# unroll the accumulated deltas
messages = orderbook.cache
for i in range(0, len(messages)):
self.handle_order_book_message(client, messages[i], orderbook)
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
async def watch_order_book_snapshot(self, client, message, subscription):
messageHash = self.safe_string(subscription, 'messageHash')
try:
symbol = self.safe_string(subscription, 'symbol')
limit = self.safe_integer(subscription, 'limit')
params = self.safe_value(subscription, 'params')
api = self.safe_string(self.options, 'api', 'api')
hostname: dict = {'hostname': self.hostname}
url = self.implode_params(self.urls['api']['ws'][api]['public'], hostname)
requestId = self.request_id()
request: dict = {
'req': messageHash,
'id': requestId,
}
# self is a temporary subscription by a specific requestId
# it has a very short lifetime until the snapshot is received over ws
snapshotSubscription: dict = {
'id': requestId,
'messageHash': messageHash,
'symbol': symbol,
'limit': limit,
'params': params,
'method': self.handle_order_book_snapshot,
}
orderbook = await self.watch(url, requestId, request, requestId, snapshotSubscription)
return orderbook.limit()
except Exception as e:
del client.subscriptions[messageHash]
client.reject(e, messageHash)
return None
def handle_delta(self, bookside, delta):
price = self.safe_float(delta, 0)
amount = self.safe_float(delta, 1)
bookside.store(price, amount)
def handle_deltas(self, bookside, deltas):
for i in range(0, len(deltas)):
self.handle_delta(bookside, deltas[i])
def handle_order_book_message(self, client: Client, message, orderbook):
#
# {
# "ch": "market.btcusdt.mbp.150",
# "ts": 1583472025885,
# "tick": {
# "seqNum": 104998984994,
# "prevSeqNum": 104998984977,
# "bids": [
# [9058.27, 0],
# [9058.43, 0],
# [9058.99, 0],
# ],
# "asks": [
# [9084.27, 0.2],
# [9085.69, 0],
# [9085.81, 0],
# ]
# }
# }
#
tick = self.safe_value(message, 'tick', {})
seqNum = self.safe_integer(tick, 'seqNum')
prevSeqNum = self.safe_integer(tick, 'prevSeqNum')
if (prevSeqNum <= orderbook['nonce']) and (seqNum > orderbook['nonce']):
asks = self.safe_value(tick, 'asks', [])
bids = self.safe_value(tick, 'bids', [])
self.handle_deltas(orderbook['asks'], asks)
self.handle_deltas(orderbook['bids'], bids)
orderbook['nonce'] = seqNum
timestamp = self.safe_integer(message, 'ts')
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
return orderbook
def handle_order_book(self, client: Client, message):
#
# deltas
#
# {
# "ch": "market.btcusdt.mbp.150",
# "ts": 1583472025885,
# "tick": {
# "seqNum": 104998984994,
# "prevSeqNum": 104998984977,
# "bids": [
# [9058.27, 0],
# [9058.43, 0],
# [9058.99, 0],
# ],
# "asks": [
# [9084.27, 0.2],
# [9085.69, 0],
# [9085.81, 0],
# ]
# }
# }
#
messageHash = self.safe_string(message, 'ch')
ch = self.safe_value(message, 'ch')
parts = ch.split('.')
marketId = self.safe_string(parts, 1)
symbol = self.safe_symbol(marketId)
orderbook = self.orderbooks[symbol]
if orderbook['nonce'] is None:
orderbook.cache.append(message)
else:
self.handle_order_book_message(client, message, orderbook)
client.resolve(orderbook, messageHash)
def handle_order_book_subscription(self, client: Client, message, subscription):
symbol = self.safe_string(subscription, 'symbol')
limit = self.safe_integer(subscription, 'limit')
if symbol in self.orderbooks:
del self.orderbooks[symbol]
self.orderbooks[symbol] = self.order_book({}, limit)
# watch the snapshot in a separate async call
self.spawn(self.watch_order_book_snapshot, client, message, subscription)
def handle_subscription_status(self, client: Client, message):
#
# {
# "id": 1583414227,
# "status": "ok",
# "subbed": "market.btcusdt.mbp.150",
# "ts": 1583414229143
# }
#
id = self.safe_string(message, 'id')
subscriptionsById = self.index_by(client.subscriptions, 'id')
subscription = self.safe_value(subscriptionsById, id)
if subscription is not None:
method = self.safe_value(subscription, 'method')
if method is not None:
return method(client, message, subscription)
# clean up
if id in client.subscriptions:
del client.subscriptions[id]
return message
def handle_system_status(self, client: Client, message):
#
# todo: answer the question whether handleSystemStatus should be renamed
# and unified for any usage pattern that
# involves system status and maintenance updates
#
# {
# "id": "1578090234088", # connectId
# "type": "welcome",
# }
#
return message
def handle_subject(self, client: Client, message):
#
# {
# "ch": "market.btcusdt.mbp.150",
# "ts": 1583472025885,
# "tick": {
# "seqNum": 104998984994,
# "prevSeqNum": 104998984977,
# "bids": [
# [9058.27, 0],
# [9058.43, 0],
# [9058.99, 0],
# ],
# "asks": [
# [9084.27, 0.2],
# [9085.69, 0],
# [9085.81, 0],
# ]
# }
# }
#
ch = self.safe_value(message, 'ch')
parts = ch.split('.')
type = self.safe_string(parts, 0)
if type == 'market':
methodName = self.safe_string(parts, 2)
methods: dict = {
'mbp': self.handle_order_book,
'detail': self.handle_ticker,
'trade': self.handle_trades,
'kline': self.handle_ohlcv,
# ...
}
method = self.safe_value(methods, methodName)
if method is not None:
method(client, message)
async def pong(self, client, message):
#
# {ping: 1583491673714}
#
await client.send({'pong': self.safe_integer(message, 'ping')})
def handle_ping(self, client: Client, message):
self.spawn(self.pong, client, message)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# "ts": 1586323747018,
# "status": "error",
# 'err-code': "bad-request",
# 'err-msg': "invalid mbp.150.symbol linkusdt",
# "id": "2"
# }
#
status = self.safe_string(message, 'status')
if status == 'error':
id = self.safe_string(message, 'id')
subscriptionsById = self.index_by(client.subscriptions, 'id')
subscription = self.safe_value(subscriptionsById, id)
if subscription is not None:
errorCode = self.safe_string(message, 'err-code')
try:
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, self.json(message))
except Exception as e:
messageHash = self.safe_string(subscription, 'messageHash')
client.reject(e, messageHash)
client.reject(e, id)
if id in client.subscriptions:
del client.subscriptions[id]
return False
return message
def handle_message(self, client: Client, message):
if self.handle_error_message(client, message):
#
# {"id":1583414227,"status":"ok","subbed":"market.btcusdt.mbp.150","ts":1583414229143}
#
# ________________________
#
# sometimes bittrade responds with half of a JSON response like
#
# " {"ch":"market.ethbtc.m "
#
# self is passed to handleMessage string since it failed to be decoded
#
if self.safe_string(message, 'id') is not None:
self.handle_subscription_status(client, message)
elif self.safe_string(message, 'ch') is not None:
# route by channel aka topic aka subject
self.handle_subject(client, message)
elif self.safe_string(message, 'ping') is not None:
self.handle_ping(client, message)

1394
ccxt/pro/bitvavo.py Normal file

File diff suppressed because it is too large Load Diff

751
ccxt/pro/blockchaincom.py Normal file
View File

@@ -0,0 +1,751 @@
# -*- 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
from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import NotSupported
class blockchaincom(ccxt.async_support.blockchaincom):
def describe(self) -> Any:
return self.deep_extend(super(blockchaincom, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchTicker': True,
'watchTickers': False,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchMyTrades': False,
'watchOrders': True,
'watchOrderBook': True,
'watchOHLCV': True,
},
'urls': {
'api': {
'ws': 'wss://ws.blockchain.info/mercury-gateway/v1/ws',
},
},
'options': {
'ws': {
'options': {
'headers': {
'Origin': 'https://exchange.blockchain.com',
},
},
'noOriginHeader': False,
},
},
'streaming': {
},
'exceptions': {
},
'timeframes': {
'1m': '60',
'5m': '300',
'15m': '900',
'1h': '3600',
'6h': '21600',
'1d': '86400',
},
})
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://exchange.blockchain.com/api/#balances
: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.authenticate(params)
messageHash = 'balance'
url = self.urls['api']['ws']
subscribe: dict = {
'action': 'subscribe',
'channel': 'balances',
}
request = self.deep_extend(subscribe, params)
return await self.watch(url, messageHash, request, messageHash, request)
def handle_balance(self, client: Client, message):
#
# subscribed
# {
# "seqnum": 1,
# "event": "subscribed",
# "channel": "balances",
# "local_currency": "USD",
# "batching": False
# }
# snapshot
# {
# "seqnum": 2,
# "event": "snapshot",
# "channel": "balances",
# "balances": [
# {
# "currency": "BTC",
# "balance": 0.00366963,
# "available": 0.00266963,
# "balance_local": 38.746779155,
# "available_local": 28.188009155,
# "rate": 10558.77
# },
# ...
# ],
# "total_available_local": 65.477864168,
# "total_balance_local": 87.696634168
# }
#
event = self.safe_string(message, 'event')
if event == 'subscribed':
return
result: dict = {'info': message}
balances = self.safe_value(message, 'balances', [])
for i in range(0, len(balances)):
entry = balances[i]
currencyId = self.safe_string(entry, 'currency')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string(entry, 'available')
account['total'] = self.safe_string(entry, 'balance')
result[code] = account
messageHash = 'balance'
self.balance = self.safe_balance(result)
client.resolve(self.balance, messageHash)
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://exchange.blockchain.com/api/#prices
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents. Allows '1m', '5m', '15m', '1h', '6h' '1d'. Can only watch one timeframe per symbol.
: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']
interval = self.safe_string(self.timeframes, timeframe, timeframe)
messageHash = 'ohlcv:' + symbol
request = {
'action': 'subscribe',
'channel': 'prices',
'symbol': market['id'],
'granularity': self.parse_number(interval),
}
request = self.deep_extend(request, params)
url = self.urls['api']['ws']
ohlcv = await self.watch(url, messageHash, request, messageHash, request)
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
def handle_ohlcv(self, client: Client, message):
#
# subscribed
# {
# "seqnum": 0,
# "event": "subscribed",
# "channel": "prices",
# "symbol": "BTC-USDT",
# "granularity": 60
# }
#
# updated
# {
# "seqnum": 1,
# "event": "updated",
# "channel": "prices",
# "symbol": "BTC-USD",
# "price": [1660085580000, 23185.215, 23185.935, 23164.79, 23169.97, 0]
# }
#
event = self.safe_string(message, 'event')
if event == 'rejected':
jsonMessage = self.json(message)
raise ExchangeError(self.id + ' ' + jsonMessage)
elif event == 'updated':
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId, None, '-')
messageHash = 'ohlcv:' + symbol
request = self.safe_value(client.subscriptions, messageHash)
timeframeId = self.safe_number(request, 'granularity')
timeframe = self.find_timeframe(timeframeId)
ohlcv = self.safe_value(message, 'price', [])
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
stored.append(ohlcv)
client.resolve(stored, messageHash)
elif event != 'subscribed':
raise NotSupported(self.id + ' ' + self.json(message))
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://exchange.blockchain.com/api/#ticker
: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']
url = self.urls['api']['ws']
messageHash = 'ticker:' + symbol
request = {
'action': 'subscribe',
'channel': 'ticker',
'symbol': market['id'],
}
request = self.deep_extend(request, params)
return await self.watch(url, messageHash, request, messageHash)
def handle_ticker(self, client: Client, message):
#
# subscribed
# {
# "seqnum": 0,
# "event": "subscribed",
# "channel": "ticker",
# "symbol": "BTC-USD"
# }
# snapshot
# {
# "seqnum": 1,
# "event": "snapshot",
# "channel": "ticker",
# "symbol": "BTC-USD",
# "price_24h": 23071.4,
# "volume_24h": 236.28398636,
# "last_trade_price": 23936.4,
# "mark_price": 23935.335240262
# }
# update
# {
# "seqnum": 2,
# "event": "updated",
# "channel": "ticker",
# "symbol": "BTC-USD",
# "mark_price": 23935.242443617
# }
#
event = self.safe_string(message, 'event')
marketId = self.safe_string(message, 'symbol')
market = self.safe_market(marketId)
symbol = market['symbol']
ticker = None
if event == 'subscribed':
return
elif event == 'snapshot':
ticker = self.parse_ticker(message, market)
elif event == 'updated':
lastTicker = self.safe_value(self.tickers, symbol)
ticker = self.parse_ws_updated_ticker(message, lastTicker, market)
messageHash = 'ticker:' + symbol
self.tickers[symbol] = ticker
client.resolve(ticker, messageHash)
def parse_ws_updated_ticker(self, ticker, lastTicker=None, market=None):
#
# {
# "seqnum": 2,
# "event": "updated",
# "channel": "ticker",
# "symbol": "BTC-USD",
# "mark_price": 23935.242443617
# }
#
marketId = self.safe_string(ticker, 'symbol')
symbol = self.safe_symbol(marketId, None, '-')
last = self.safe_string(ticker, 'mark_price')
return self.safe_ticker({
'symbol': symbol,
'timestamp': None,
'datetime': None,
'high': None,
'low': None,
'bid': None,
'bidVolume': None,
'ask': None,
'askVolume': None,
'vwap': None,
'open': self.safe_string(lastTicker, 'open'),
'close': None,
'last': last,
'previousClose': self.safe_string(lastTicker, 'close'),
'change': None,
'percentage': None,
'average': None,
'baseVolume': self.safe_string(lastTicker, 'baseVolume'),
'quoteVolume': None,
'info': self.extend(self.safe_value(lastTicker, 'info', {}), ticker),
}, market)
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://exchange.blockchain.com/api/#trades
: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']
messageHash = 'trades:' + symbol
request = {
'action': 'subscribe',
'channel': 'trades',
'symbol': market['id'],
}
request = self.deep_extend(request, params)
trades = await self.watch(url, messageHash, request, messageHash, request)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# subscribed
# {
# "seqnum": 0,
# "event": "subscribed",
# "channel": "trades",
# "symbol": "BTC-USDT"
# }
# updates
# {
# "seqnum": 1,
# "event": "updated",
# "channel": "trades",
# "symbol": "BTC-USDT",
# "timestamp": "2022-08-08T17:23:48.163096Z",
# "side": "sell",
# "qty": 0.083523,
# "price": 23940.67,
# "trade_id": "563078810223444"
# }
#
event = self.safe_string(message, 'event')
if event != 'updated':
return
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
market = self.safe_market(marketId)
messageHash = 'trades:' + symbol
stored = self.safe_value(self.trades, symbol)
if stored is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
stored = ArrayCache(limit)
self.trades[symbol] = stored
parsed = self.parse_ws_trade(message, market)
stored.append(parsed)
self.trades[symbol] = stored
client.resolve(self.trades[symbol], messageHash)
def parse_ws_trade(self, trade, market=None):
#
# {
# "seqnum": 1,
# "event": "updated",
# "channel": "trades",
# "symbol": "BTC-USDT",
# "timestamp": "2022-08-08T17:23:48.163096Z",
# "side": "sell",
# "qty": 0.083523,
# "price": 23940.67,
# "trade_id": "563078810223444"
# }
#
marketId = self.safe_string(trade, 'symbol')
datetime = self.safe_string(trade, 'timestamp')
return self.safe_trade({
'id': self.safe_string(trade, 'trade_id'),
'timestamp': self.parse8601(datetime),
'datetime': datetime,
'symbol': self.safe_symbol(marketId, market, '-'),
'order': None,
'type': None,
'side': self.safe_string(trade, 'side'),
'takerOrMaker': None,
'price': self.safe_string(trade, 'price'),
'amount': self.safe_string(trade, 'qty'),
'cost': None,
'fee': None,
'info': trade,
}, market)
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://exchange.blockchain.com/api/#mass-order-status-request-ordermassstatusrequest
: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()
await self.authenticate()
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
url = self.urls['api']['ws']
message: dict = {
'action': 'subscribe',
'channel': 'trading',
}
messageHash = 'orders'
request = self.deep_extend(message, params)
orders = await self.watch(url, messageHash, request, messageHash)
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):
#
# {
# "seqnum": 1,
# "event": "rejected",
# "channel": "trading",
# "text": "Not subscribed to channel"
# }
# snapshot
# {
# "seqnum": 2,
# "event": "snapshot",
# "channel": "trading",
# "orders": [
# {
# "orderID": "562965341621940",
# "gwOrderId": 181011136260,
# "clOrdID": "016caf67f7a94508webd",
# "symbol": "BTC-USD",
# "side": "sell",
# "ordType": "limit",
# "orderQty": 0.000675,
# "leavesQty": 0.000675,
# "cumQty": 0,
# "avgPx": 0,
# "ordStatus": "open",
# "timeInForce": "GTC",
# "text": "New order",
# "execType": "0",
# "execID": "21415965325",
# "transactTime": "2022-08-08T23:31:00.550795Z",
# "msgType": 8,
# "lastPx": 0,
# "lastShares": 0,
# "tradeId": "0",
# "fee": 0,
# "price": 30000,
# "marginOrder": False,
# "closePositionOrder": False
# }
# ],
# "positions": []
# }
# update
# {
# "seqnum": 3,
# "event": "updated",
# "channel": "trading",
# "orderID": "562965341621940",
# "gwOrderId": 181011136260,
# "clOrdID": "016caf67f7a94508webd",
# "symbol": "BTC-USD",
# "side": "sell",
# "ordType": "limit",
# "orderQty": 0.000675,
# "leavesQty": 0.000675,
# "cumQty": 0,
# "avgPx": 0,
# "ordStatus": "cancelled",
# "timeInForce": "GTC",
# "text": "Canceled by User",
# "execType": "4",
# "execID": "21416034921",
# "transactTime": "2022-08-08T23:33:25.727785Z",
# "msgType": 8,
# "lastPx": 0,
# "lastShares": 0,
# "tradeId": "0",
# "fee": 0,
# "price": 30000,
# "marginOrder": False,
# "closePositionOrder": False
# }
#
event = self.safe_string(message, 'event')
messageHash = 'orders'
cachedOrders = self.orders
if cachedOrders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
if event == 'subscribed':
return
elif event == 'rejected':
raise ExchangeError(self.id + ' ' + self.json(message))
elif event == 'snapshot':
orders = self.safe_value(message, 'orders', [])
for i in range(0, len(orders)):
order = orders[i]
parsedOrder = self.parse_ws_order(order)
cachedOrders.append(parsedOrder)
elif event == 'updated':
parsedOrder = self.parse_ws_order(message)
cachedOrders.append(parsedOrder)
self.orders = cachedOrders
client.resolve(self.orders, messageHash)
def parse_ws_order(self, order, market=None):
#
# {
# "seqnum": 3,
# "event": "updated",
# "channel": "trading",
# "orderID": "562965341621940",
# "gwOrderId": 181011136260,
# "clOrdID": "016caf67f7a94508webd",
# "symbol": "BTC-USD",
# "side": "sell",
# "ordType": "limit",
# "orderQty": 0.000675,
# "leavesQty": 0.000675,
# "cumQty": 0,
# "avgPx": 0,
# "ordStatus": "cancelled",
# "timeInForce": "GTC",
# "text": "Canceled by User",
# "execType": "4",
# "execID": "21416034921",
# "transactTime": "2022-08-08T23:33:25.727785Z",
# "msgType": 8,
# "lastPx": 0,
# "lastShares": 0,
# "tradeId": "0",
# "fee": 0,
# "price": 30000,
# "marginOrder": False,
# "closePositionOrder": False
# }
#
datetime = self.safe_string(order, 'transactTime')
status = self.safe_string(order, 'ordStatus')
marketId = self.safe_string(order, 'symbol')
market = self.safe_market(marketId, market)
tradeId = self.safe_string(order, 'tradeId')
trades = []
if tradeId != '0':
trades.append({'id': tradeId})
return self.safe_order({
'id': self.safe_string(order, 'orderID'),
'clientOrderId': self.safe_string(order, 'clOrdID'),
'datetime': datetime,
'timestamp': self.parse8601(datetime),
'status': self.parse_ws_order_status(status),
'symbol': self.safe_symbol(marketId, market),
'type': self.safe_string(order, 'ordType'), # limit, market, stop, stopLimit, trailingStop, fillOrKill
'timeInForce': self.safe_string(order, 'timeInForce'),
'postOnly': self.safe_string(order, 'execInst') == 'ALO',
'side': self.safe_string(order, 'side'),
'price': self.safe_string(order, 'price'),
'stopPrice': self.safe_string(order, 'stopPx'),
'cost': None,
'amount': self.safe_string(order, 'orderQty'),
'filled': self.safe_string(order, 'cumQty'),
'remaining': self.safe_string(order, 'leavesQty'),
'trades': trades,
'fee': {
'rate': None,
'cost': self.safe_number(order, 'fee'),
'currency': self.safe_string(market, 'quote'),
},
'info': order,
'lastTradeTimestamp': None,
'average': self.safe_string(order, 'avgPx'),
}, market)
def parse_ws_order_status(self, status):
statuses: dict = {
'pending': 'open',
'open': 'open',
'rejected': 'rejected',
'cancelled': 'canceled',
'filled': 'closed',
'partial': 'open',
'expired': 'expired',
}
return self.safe_string(statuses, status, status)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://exchange.blockchain.com/api/#l2-order-book
: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 dictConstructor [params]: extra parameters specific to the exchange API endpoint
:param str [params.type]: accepts l2 or l3 for level 2 or level 3 order book
: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)
url = self.urls['api']['ws']
type = self.safe_string(params, 'type', 'l2')
params = self.omit(params, 'type')
messageHash = 'orderbook:' + symbol + ':' + type
subscribe: dict = {
'action': 'subscribe',
'channel': type,
'symbol': market['id'],
}
request = self.deep_extend(subscribe, params)
orderbook = await self.watch(url, messageHash, request, messageHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# subscribe
# {
# "seqnum": 0,
# "event": "subscribed",
# "channel": "l2",
# "symbol": "BTC-USDT",
# "batching": False
# }
# snapshot
# {
# "seqnum": 1,
# "event": "snapshot",
# "channel": "l2",
# "symbol": "BTC-USDT",
# "bids": [
# {num: 1, px: 0.01, qty: 22},
# ],
# "asks": [
# {num: 1, px: 23840.26, qty: 0.25},
# ],
# "timestamp": "2022-08-08T22:03:19.071870Z"
# }
# update
# {
# "seqnum": 2,
# "event": "updated",
# "channel": "l2",
# "symbol": "BTC-USDT",
# "bids": [],
# "asks": [{num: 1, px: 23855.06, qty: 1.04786347}],
# "timestamp": "2022-08-08T22:03:19.014680Z"
# }
#
event = self.safe_string(message, 'event')
if event == 'subscribed':
return
type = self.safe_string(message, 'channel')
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
messageHash = 'orderbook:' + symbol + ':' + type
datetime = self.safe_string(message, 'timestamp')
timestamp = self.parse8601(datetime)
if self.safe_value(self.orderbooks, symbol) is None:
self.orderbooks[symbol] = self.counted_order_book()
orderbook = self.orderbooks[symbol]
if event == 'snapshot':
snapshot = self.parse_order_book(message, symbol, timestamp, 'bids', 'asks', 'px', 'qty', 'num')
orderbook.reset(snapshot)
elif event == 'updated':
asks = self.safe_list(message, 'asks', [])
bids = self.safe_list(message, 'bids', [])
self.handle_deltas(orderbook['asks'], asks)
self.handle_deltas(orderbook['bids'], bids)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = datetime
else:
raise NotSupported(self.id + ' watchOrderBook() does not support ' + event + ' yet')
client.resolve(orderbook, messageHash)
def handle_delta(self, bookside, delta):
bookArray = self.parse_bid_ask(delta, 'px', 'qty', 'num')
bookside.storeArray(bookArray)
def handle_deltas(self, bookside, deltas):
for i in range(0, len(deltas)):
self.handle_delta(bookside, deltas[i])
def handle_message(self, client: Client, message):
channel = self.safe_string(message, 'channel')
handlers: dict = {
'ticker': self.handle_ticker,
'trades': self.handle_trades,
'prices': self.handle_ohlcv,
'l2': self.handle_order_book,
'l3': self.handle_order_book,
'auth': self.handle_authentication_message,
'balances': self.handle_balance,
'trading': self.handle_orders,
}
handler = self.safe_value(handlers, channel)
if handler is not None:
handler(client, message)
return
raise NotSupported(self.id + ' received an unsupported message: ' + self.json(message))
def handle_authentication_message(self, client: Client, message):
#
# {
# "seqnum": 0,
# "event": "subscribed",
# "channel": "auth",
# "readOnly": False
# }
#
event = self.safe_string(message, 'event')
if event != 'subscribed':
raise AuthenticationError(self.id + ' received an authentication error: ' + self.json(message))
future = self.safe_value(client.futures, 'authenticated')
if future is not None:
future.resolve(True)
async def authenticate(self, params={}):
url = self.urls['api']['ws']
client = self.client(url)
messageHash = 'authenticated'
future = client.reusableFuture(messageHash)
isAuthenticated = self.safe_value(client.subscriptions, messageHash)
if isAuthenticated is None:
self.check_required_credentials()
request: dict = {
'action': 'subscribe',
'channel': 'auth',
'token': self.secret,
}
return self.watch(url, messageHash, self.extend(request, params), messageHash)
return await future

711
ccxt/pro/blofin.py Normal file
View File

@@ -0,0 +1,711 @@
# -*- 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
import hashlib
from ccxt.base.types import Any, Balances, Int, Market, 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 ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import NotSupported
class blofin(ccxt.async_support.blofin):
def describe(self) -> Any:
return self.deep_extend(super(blofin, self).describe(), {
'has': {
'ws': True,
'watchTrades': True,
'watchTradesForSymbols': True,
'watchOrderBook': True,
'watchOrderBookForSymbols': True,
'watchTicker': True,
'watchTickers': True,
'watchBidsAsks': True,
'watchOHLCV': True,
'watchOHLCVForSymbols': True,
'watchOrders': True,
'watchOrdersForSymbols': True,
'watchPositions': True,
},
'urls': {
'api': {
'ws': {
'swap': {
'public': 'wss://openapi.blofin.com/ws/public',
'private': 'wss://openapi.blofin.com/ws/private',
},
},
},
'test': {
'ws': {
'swap': {
'public': 'wss://demo-trading-openapi.blofin.com/ws/public',
'private': 'wss://demo-trading-openapi.blofin.com/ws/private',
},
},
},
},
'options': {
'defaultType': 'swap',
'tradesLimit': 1000,
# orderbook channel can be one from:
# - "books": 200 depth levels will be pushed in the initial full snapshot. Incremental data will be pushed every 100 ms for the changes in the order book during that period of time.
# - "books5": 5 depth levels snapshot will be pushed every time. Snapshot data will be pushed every 100 ms when there are changes in the 5 depth levels snapshot.
'watchOrderBook': {
'channel': 'books',
},
'watchOrderBookForSymbols': {
'channel': 'books',
},
},
'streaming': {
'ping': self.ping,
'keepAlive': 25000, # 30 seconds max
},
})
def ping(self, client):
return 'ping'
def handle_pong(self, client: Client, message):
#
# 'pong'
#
client.lastPong = self.milliseconds()
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://docs.blofin.com/index.html#ws-trades-channel
: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>`
"""
params['callerMethodName'] = 'watchTrades'
return await self.watch_trades_for_symbols([symbol], since, limit, params)
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a list of symbols
https://docs.blofin.com/index.html#ws-trades-channel
:param str[] symbols: 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()
trades = await self.watch_multiple_wrapper(True, 'trades', 'watchTradesForSymbols', symbols, params)
if self.newUpdates:
firstMarket = self.safe_dict(trades, 0)
firstSymbol = self.safe_string(firstMarket, 'symbol')
limit = trades.getLimit(firstSymbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def handle_trades(self, client: Client, message):
#
# {
# arg: {
# channel: "trades",
# instId: "DOGE-USDT",
# },
# data : [
# <same object in REST example>,
# ...
# ]
# }
#
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_list(message, 'data')
if data is None:
return
for i in range(0, len(data)):
rawTrade = data[i]
trade = self.parse_ws_trade(rawTrade)
symbol = trade['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
stored.append(trade)
messageHash = channelName + ':' + symbol
client.resolve(stored, messageHash)
def parse_ws_trade(self, trade, market: Market = None) -> Trade:
return self.parse_trade(trade, market)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.blofin.com/index.html#ws-order-book-channel
: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
"""
params['callerMethodName'] = 'watchOrderBook'
return await self.watch_order_book_for_symbols([symbol], limit, params)
async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.blofin.com/index.html#ws-order-book-channel
:param str[] symbols: unified array of symbols
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.depth]: the type of order book to subscribe to, default is 'depth/increase100', also accepts 'depth5' or 'depth20' or depth50
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
callerMethodName = None
callerMethodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOrderBookForSymbols')
channelName = None
channelName, params = self.handle_option_and_params(params, callerMethodName, 'channel', 'books')
# due to some problem, temporarily disable other channels
if channelName != 'books':
raise NotSupported(self.id + ' ' + callerMethodName + '() at self moment ' + channelName + ' is not supported, coming soon')
orderbook = await self.watch_multiple_wrapper(True, channelName, callerMethodName, symbols, params)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# {
# arg: {
# channel: "books",
# instId: "DOGE-USDT",
# },
# action: "snapshot", # can be 'snapshot' or 'update'
# data: {
# asks: [ [0.08096, 1], [0.08097, 123], ... ],
# bids: [ [0.08095, 4], [0.08094, 237], ... ],
# ts: "1707491587909",
# prevSeqId: "0", # in case of 'update' there will be some value, less then seqId
# seqId: "3374250786",
# },
# }
#
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_dict(message, 'data')
marketId = self.safe_string(arg, 'instId')
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = channelName + ':' + symbol
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book()
orderbook = self.orderbooks[symbol]
timestamp = self.safe_integer(data, 'ts')
action = self.safe_string(message, 'action')
if action == 'snapshot':
orderBookSnapshot = self.parse_order_book(data, symbol, timestamp)
orderBookSnapshot['nonce'] = self.safe_integer(data, 'seqId')
orderbook.reset(orderBookSnapshot)
else:
asks = self.safe_list(data, 'asks', [])
bids = self.safe_list(data, 'bids', [])
self.handle_deltas_with_keys(orderbook['asks'], asks)
self.handle_deltas_with_keys(orderbook['bids'], bids)
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://docs.blofin.com/index.html#ws-tickers-channel
: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>`
"""
params['callerMethodName'] = 'watchTicker'
market = self.market(symbol)
symbol = market['symbol']
result = await self.watch_tickers([symbol], params)
return result[symbol]
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
https://docs.blofin.com/index.html#ws-tickers-channel
: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>`
"""
if symbols is None:
raise NotSupported(self.id + ' watchTickers() requires a list of symbols')
ticker = await self.watch_multiple_wrapper(True, 'tickers', 'watchTickers', symbols, params)
if self.newUpdates:
tickers = {}
tickers[ticker['symbol']] = ticker
return tickers
return self.filter_by_array(self.tickers, 'symbol', symbols)
def handle_ticker(self, client: Client, message):
#
# message
#
# {
# arg: {
# channel: "tickers",
# instId: "DOGE-USDT",
# },
# data: [
# <same object in REST example>
# ],
# }
#
self.handle_bid_ask(client, message)
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_list(message, 'data')
for i in range(0, len(data)):
ticker = self.parse_ws_ticker(data[i])
symbol = ticker['symbol']
messageHash = channelName + ':' + symbol
self.tickers[symbol] = ticker
client.resolve(self.tickers[symbol], messageHash)
def parse_ws_ticker(self, ticker, market: Market = None) -> Ticker:
return self.parse_ticker(ticker, market)
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
"""
watches best bid & ask for symbols
https://docs.blofin.com/index.html#ws-tickers-channel
: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, None, False)
firstMarket = self.market(symbols[0])
channel = 'tickers'
marketType = None
marketType, params = self.handle_market_type_and_params('watchBidsAsks', firstMarket, params)
url = self.implode_hostname(self.urls['api']['ws'][marketType]['public'])
messageHashes = []
args = []
for i in range(0, len(symbols)):
market = self.market(symbols[i])
messageHashes.append('bidask:' + market['symbol'])
args.append({
'channel': channel,
'instId': market['id'],
})
request = self.get_subscription_request(args)
ticker = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
if self.newUpdates:
tickers = {}
tickers[ticker['symbol']] = ticker
return tickers
return self.filter_by_array(self.bidsasks, 'symbol', symbols)
def handle_bid_ask(self, client: Client, message):
data = self.safe_list(message, 'data')
for i in range(0, len(data)):
ticker = self.parse_ws_bid_ask(data[i])
symbol = ticker['symbol']
messageHash = 'bidask:' + symbol
self.bidsasks[symbol] = ticker
client.resolve(ticker, messageHash)
def parse_ws_bid_ask(self, ticker, market=None):
marketId = self.safe_string(ticker, 'instId')
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, 'askPrice'),
'askVolume': self.safe_string(ticker, 'askSize'),
'bid': self.safe_string(ticker, 'bidPrice'),
'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
: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
"""
params['callerMethodName'] = 'watchOHLCV'
result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
return result[symbol][timeframe]
async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
"""
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://docs.blofin.com/index.html#ws-candlesticks-channel
:param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
: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
"""
symbolsLength = len(symbolsAndTimeframes)
if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]")
await self.load_markets()
symbol, timeframe, candles = await self.watch_multiple_wrapper(True, 'candle', 'watchOHLCVForSymbols', symbolsAndTimeframes, params)
if self.newUpdates:
limit = candles.getLimit(symbol, limit)
filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
return self.create_ohlcv_object(symbol, timeframe, filtered)
def handle_ohlcv(self, client: Client, message):
#
# message
#
# {
# arg: {
# channel: "candle1m",
# instId: "DOGE-USDT",
# },
# data: [
# [same object in REST example]
# ],
# }
#
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_list(message, 'data')
marketId = self.safe_string(arg, 'instId')
market = self.safe_market(marketId)
symbol = market['symbol']
interval = channelName.replace('candle', '')
unifiedTimeframe = self.find_timeframe(interval)
self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {})
stored = self.safe_value(self.ohlcvs[symbol], unifiedTimeframe)
if stored is None:
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
stored = ArrayCacheByTimestamp(limit)
self.ohlcvs[symbol][unifiedTimeframe] = stored
for i in range(0, len(data)):
candle = data[i]
parsed = self.parse_ohlcv(candle, market)
stored.append(parsed)
resolveData = [symbol, unifiedTimeframe, stored]
messageHash = 'candle' + interval + ':' + symbol
client.resolve(resolveData, messageHash)
async def watch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://docs.blofin.com/index.html#ws-account-channel
: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()
await self.authenticate()
marketType = None
marketType, params = self.handle_market_type_and_params('watchBalance', None, params)
if marketType == 'spot':
raise NotSupported(self.id + ' watchBalance() is not supported for spot markets yet')
messageHash = marketType + ':balance'
sub = {
'channel': 'account',
}
request = self.get_subscription_request([sub])
url = self.implode_hostname(self.urls['api']['ws'][marketType]['private'])
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
def handle_balance(self, client: Client, message):
#
# {
# arg: {
# channel: "account",
# },
# data: <same object in REST example>,
# }
#
marketType = 'swap' # for now
if not (marketType in self.balance):
self.balance[marketType] = {}
self.balance[marketType] = self.parse_ws_balance(message)
messageHash = marketType + ':balance'
client.resolve(self.balance[marketType], messageHash)
def parse_ws_balance(self, message):
return self.parse_balance(message)
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://docs.blofin.com/index.html#ws-order-channel
https://docs.blofin.com/index.html#ws-algo-orders-channel
: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 boolean [params.trigger]: set to True for trigger orders
:returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
"""
params['callerMethodName'] = 'watchOrders'
symbolsArray = [symbol] if (symbol is not None) else []
return await self.watch_orders_for_symbols(symbolsArray, since, limit, params)
async def watch_orders_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
watches information on multiple orders made by the user across multiple symbols
https://docs.blofin.com/index.html#ws-order-channel
https://docs.blofin.com/index.html#ws-algo-orders-channel
:param str[] symbols:
: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 boolean [params.trigger]: set to True for trigger orders
:returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
"""
await self.authenticate()
await self.load_markets()
trigger = self.safe_value_2(params, 'stop', 'trigger')
params = self.omit(params, ['stop', 'trigger'])
channel = 'orders-algo' if trigger else 'orders'
orders = await self.watch_multiple_wrapper(False, channel, 'watchOrdersForSymbols', symbols, params)
if self.newUpdates:
first = self.safe_value(orders, 0)
tradeSymbol = self.safe_string(first, 'symbol')
limit = orders.getLimit(tradeSymbol, limit)
return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)
def handle_orders(self, client: Client, message):
#
# {
# action: 'update',
# arg: {channel: 'orders'},
# data: [
# <same object in REST example>
# ]
# }
#
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_list(message, 'data')
for i in range(0, len(data)):
order = self.parse_ws_order(data[i])
symbol = order['symbol']
messageHash = channelName + ':' + symbol
orders.append(order)
client.resolve(orders, messageHash)
client.resolve(orders, channelName)
def parse_ws_order(self, order, market: Market = None) -> Order:
return self.parse_order(order, market)
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
"""
https://docs.blofin.com/index.html#ws-positions-channel
watch all open positions
:param str[]|None symbols: list of unified market symbols
:param int [since]: the earliest time in ms to fetch positions for
:param int [limit]: the maximum number of positions to retrieve
:param dict params: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
"""
await self.authenticate()
await self.load_markets()
newPositions = await self.watch_multiple_wrapper(False, 'positions', 'watchPositions', symbols, params)
if self.newUpdates:
return newPositions
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit)
def handle_positions(self, client: Client, message):
#
# {
# arg: {channel: 'positions'},
# data: [
# <same object in REST example>
# ]
# }
#
if self.positions is None:
self.positions = ArrayCacheBySymbolBySide()
cache = self.positions
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
data = self.safe_list(message, 'data')
newPositions = []
for i in range(0, len(data)):
position = self.parse_ws_position(data[i])
newPositions.append(position)
cache.append(position)
messageHash = channelName + ':' + position['symbol']
client.resolve(position, messageHash)
def parse_ws_position(self, position, market: Market = None) -> Position:
return self.parse_position(position, market)
async def watch_multiple_wrapper(self, isPublic: bool, channelName: str, callerMethodName: str, symbolsArray: List[Any] = None, params={}):
# underlier method for all watch-multiple symbols
await self.load_markets()
callerMethodName, params = self.handle_param_string(params, 'callerMethodName', callerMethodName)
# if OHLCV method are being called, then symbols would be symbolsAndTimeframes(multi-dimensional) array
isOHLCV = (channelName == 'candle')
symbols = self.get_list_from_object_values(symbolsArray, 0) if isOHLCV else symbolsArray
symbols = self.market_symbols(symbols, None, True, True)
firstMarket = None
firstSymbol = self.safe_string(symbols, 0)
if firstSymbol is not None:
firstMarket = self.market(firstSymbol)
marketType = None
marketType, params = self.handle_market_type_and_params(callerMethodName, firstMarket, params)
if marketType != 'swap':
raise NotSupported(self.id + ' ' + callerMethodName + '() does not support ' + marketType + ' markets yet')
rawSubscriptions = []
messageHashes = []
if symbols is None:
symbols = []
symbolsLength = len(symbols)
if symbolsLength > 0:
for i in range(0, len(symbols)):
current = symbols[i]
market = None
channel = channelName
if isOHLCV:
market = self.market(current)
tfArray = symbolsArray[i]
tf = tfArray[1]
interval = self.safe_string(self.timeframes, tf, tf)
channel += interval
else:
market = self.market(current)
topic = {
'channel': channel,
'instId': market['id'],
}
rawSubscriptions.append(topic)
messageHashes.append(channel + ':' + market['symbol'])
else:
rawSubscriptions.append({'channel': channelName})
messageHashes.append(channelName)
# private channel are difference, they only need plural channel name for multiple symbols
if self.in_array(channelName, ['orders', 'orders-algo', 'positions']):
rawSubscriptions = [{'channel': channelName}]
request = self.get_subscription_request(rawSubscriptions)
privateOrPublic = 'public' if isPublic else 'private'
url = self.implode_hostname(self.urls['api']['ws'][marketType][privateOrPublic])
return await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
def get_subscription_request(self, args):
return {
'op': 'subscribe',
'args': args,
}
def handle_message(self, client: Client, message):
#
# message examples
#
# {
# arg: {
# channel: "trades",
# instId: "DOGE-USDT",
# },
# event: "subscribe"
# }
#
# incoming data updates' examples can be seen under each handler method
#
methods = {
# public
'pong': self.handle_pong,
'trades': self.handle_trades,
'books': self.handle_order_book,
'tickers': self.handle_ticker,
'candle': self.handle_ohlcv, # candle1m, candle5m, etc
# private
'account': self.handle_balance,
'orders': self.handle_orders,
'orders-algo': self.handle_orders,
'positions': self.handle_positions,
}
method = None
if message == 'pong':
method = self.safe_value(methods, 'pong')
else:
event = self.safe_string(message, 'event')
if event == 'subscribe':
return
elif event == 'login':
future = self.safe_value(client.futures, 'authenticate_hash')
future.resolve(True)
return
elif event == 'error':
raise ExchangeError(self.id + ' error: ' + self.json(message))
arg = self.safe_dict(message, 'arg')
channelName = self.safe_string(arg, 'channel')
method = self.safe_value(methods, channelName)
if not method and channelName.find('candle') >= 0:
method = methods['candle']
if method:
method(client, message)
async def authenticate(self, params={}):
self.check_required_credentials()
milliseconds = self.milliseconds()
messageHash = 'authenticate_hash'
timestamp = str(milliseconds)
nonce = 'n_' + timestamp
auth = '/users/self/verify' + 'GET' + timestamp + '' + nonce
signature = self.string_to_base64(self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256))
request = {
'op': 'login',
'args': [
{
'apiKey': self.apiKey,
'passphrase': self.password,
'timestamp': timestamp,
'nonce': nonce,
'sign': signature,
},
],
}
marketType = 'swap' # for now
url = self.implode_hostname(self.urls['api']['ws'][marketType]['private'])
await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)

Some files were not shown because too many files have changed in this diff Show More