add
This commit is contained in:
212
ccxt/pro/__init__.py
Normal file
212
ccxt/pro/__init__.py
Normal 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',
|
||||
]
|
||||
BIN
ccxt/pro/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/alpaca.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/alpaca.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/apex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/apex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/arkham.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/arkham.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/ascendex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/ascendex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/backpack.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/backpack.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bequant.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bequant.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/binance.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/binance.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/binancecoinm.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/binancecoinm.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/binanceus.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/binanceus.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/binanceusdm.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/binanceusdm.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bingx.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bingx.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitfinex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitfinex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitget.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitget.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bithumb.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bithumb.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitmart.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitmart.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitmex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitmex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitopro.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitopro.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitrue.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitrue.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitstamp.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitstamp.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bittrade.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bittrade.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bitvavo.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bitvavo.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/blockchaincom.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/blockchaincom.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/blofin.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/blofin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/bybit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/bybit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/cex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/cex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinbase.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinbase.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinbaseadvanced.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinbaseadvanced.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinbaseexchange.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinbaseexchange.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinbaseinternational.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinbaseinternational.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coincatch.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coincatch.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coincheck.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coincheck.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/coinone.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/coinone.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/cryptocom.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/cryptocom.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/deepcoin.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/deepcoin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/defx.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/defx.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/deribit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/deribit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/derive.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/derive.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/exmo.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/exmo.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/gate.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/gate.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/gateio.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/gateio.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/gemini.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/gemini.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/hashkey.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/hashkey.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/hitbtc.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/hitbtc.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/hollaex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/hollaex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/htx.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/htx.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/huobi.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/huobi.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/hyperliquid.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/hyperliquid.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/independentreserve.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/independentreserve.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/kraken.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/kraken.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/krakenfutures.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/krakenfutures.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/kucoin.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/kucoin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/kucoinfutures.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/kucoinfutures.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/lbank.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/lbank.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/luno.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/luno.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/mexc.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/mexc.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/modetrade.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/modetrade.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/mt5.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/mt5.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/myokx.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/myokx.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/ndax.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/ndax.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/okx.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/okx.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/okxus.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/okxus.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/onetrading.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/onetrading.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/oxfun.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/oxfun.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/p2b.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/p2b.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/paradex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/paradex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/phemex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/phemex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/poloniex.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/poloniex.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/probit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/probit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/toobit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/toobit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/upbit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/upbit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/whitebit.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/whitebit.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/woo.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/woo.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/woofipro.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/woofipro.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ccxt/pro/__pycache__/xt.cpython-311.pyc
Normal file
BIN
ccxt/pro/__pycache__/xt.cpython-311.pyc
Normal file
Binary file not shown.
716
ccxt/pro/alpaca.py
Normal file
716
ccxt/pro/alpaca.py
Normal 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
1004
ccxt/pro/apex.py
Normal file
File diff suppressed because it is too large
Load Diff
686
ccxt/pro/arkham.py
Normal file
686
ccxt/pro/arkham.py
Normal 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
964
ccxt/pro/ascendex.py
Normal 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
1240
ccxt/pro/backpack.py
Normal file
File diff suppressed because it is too large
Load Diff
43
ccxt/pro/bequant.py
Normal file
43
ccxt/pro/bequant.py
Normal 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
4201
ccxt/pro/binance.py
Normal file
File diff suppressed because it is too large
Load Diff
32
ccxt/pro/binancecoinm.py
Normal file
32
ccxt/pro/binancecoinm.py
Normal 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
71
ccxt/pro/binanceus.py
Normal 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
36
ccxt/pro/binanceusdm.py
Normal 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
1459
ccxt/pro/bingx.py
Normal file
File diff suppressed because it is too large
Load Diff
1218
ccxt/pro/bitfinex.py
Normal file
1218
ccxt/pro/bitfinex.py
Normal file
File diff suppressed because it is too large
Load Diff
2712
ccxt/pro/bitget.py
Normal file
2712
ccxt/pro/bitget.py
Normal file
File diff suppressed because it is too large
Load Diff
622
ccxt/pro/bithumb.py
Normal file
622
ccxt/pro/bithumb.py
Normal 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
1578
ccxt/pro/bitmart.py
Normal file
File diff suppressed because it is too large
Load Diff
1694
ccxt/pro/bitmex.py
Normal file
1694
ccxt/pro/bitmex.py
Normal file
File diff suppressed because it is too large
Load Diff
457
ccxt/pro/bitopro.py
Normal file
457
ccxt/pro/bitopro.py
Normal 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
447
ccxt/pro/bitrue.py
Normal 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
555
ccxt/pro/bitstamp.py
Normal 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
571
ccxt/pro/bittrade.py
Normal 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
1394
ccxt/pro/bitvavo.py
Normal file
File diff suppressed because it is too large
Load Diff
751
ccxt/pro/blockchaincom.py
Normal file
751
ccxt/pro/blockchaincom.py
Normal 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
711
ccxt/pro/blofin.py
Normal 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
Reference in New Issue
Block a user