Add support for various new authenticated channels. Add new typings in bfxapi/websocket/typings.py. Add BfxWebsocketException in bfxapi/websocket/errors.py.

This commit is contained in:
Davide Casale
2022-11-11 18:54:38 +01:00
parent 2c70d299b3
commit a03a82d57a
5 changed files with 483 additions and 45 deletions

View File

@@ -4,7 +4,7 @@ from pyee.asyncio import AsyncIOEventEmitter
from .handlers import Channels, PublicChannelsHandler, AuthenticatedEventsHandler from .handlers import Channels, PublicChannelsHandler, AuthenticatedEventsHandler
from .errors import ConnectionNotOpen, AuthenticationCredentialsError from .errors import BfxWebsocketException, ConnectionNotOpen, InvalidAuthenticationCredentials
HEARTBEAT = "hb" HEARTBEAT = "hb"
@@ -42,7 +42,7 @@ class BfxWebsocketClient(object):
elif isinstance(message, dict) and message["event"] == "auth": elif isinstance(message, dict) and message["event"] == "auth":
if message["status"] == "OK": if message["status"] == "OK":
self.event_emitter.emit("authenticated", message) self.event_emitter.emit("authenticated", message)
else: raise AuthenticationCredentialsError("Cannot authenticate with given API-KEY and API-SECRET.") else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
elif isinstance(message, list) and ((chanId := message[0]) or True) and message[1] != HEARTBEAT: elif isinstance(message, list) and ((chanId := message[0]) or True) and message[1] != HEARTBEAT:
if chanId == 0: if chanId == 0:
self.handlers["authenticated"].handle(message[1], message[2]) self.handlers["authenticated"].handle(message[1], message[2])

View File

@@ -1,3 +1,3 @@
from .BfxWebsocketClient import BfxWebsocketClient from .BfxWebsocketClient import BfxWebsocketClient
from .handlers import Channels from .handlers import Channels
from .errors import ConnectionNotOpen, AuthenticationCredentialsError from .errors import BfxWebsocketException, ConnectionNotOpen, InvalidAuthenticationCredentials

View File

@@ -1,11 +1,24 @@
class ConnectionNotOpen(Exception): __all__ = [
"BfxWebsocketException",
"ConnectionNotOpen",
"InvalidAuthenticationCredentials"
]
class BfxWebsocketException(Exception):
"""
Base class for all exceptions defined in bfx/websocket/errors.py.
"""
pass
class ConnectionNotOpen(BfxWebsocketException):
""" """
This error indicates an attempt to communicate via websocket before starting the connection with the servers. This error indicates an attempt to communicate via websocket before starting the connection with the servers.
""" """
pass pass
class AuthenticationCredentialsError(Exception): class InvalidAuthenticationCredentials(BfxWebsocketException):
""" """
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication. This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
""" """

View File

@@ -1,5 +1,7 @@
from enum import Enum from enum import Enum
from .errors import BfxWebsocketException
class Channels(str, Enum): class Channels(str, Enum):
TICKER = "ticker" TICKER = "ticker"
TRADES = "trades" TRADES = "trades"
@@ -46,33 +48,45 @@ class PublicChannelsHandler(object):
self.event_emitter.emit("status", subscription, parameters[0]) self.event_emitter.emit("status", subscription, parameters[0])
class AuthenticatedEventsHandler(object): class AuthenticatedEventsHandler(object):
def __init__(self, event_emitter): def __init__(self, event_emitter, strict = False):
self.event_emitter = event_emitter self.event_emitter, self.strict = event_emitter, strict
self.__handlers = { self.__handlers = {
"bu": self.__bu_event_handler, "bu": self.__bu_event_handler,
"ws": self.__ws_event_handler, "ws": self.__ws_event_handler,
"wu": self.__wu_event_handler, "wu": self.__wu_event_handler,
"os": self.__os_event_handler, "os": self.__os_event_handler,
"on": self.__on_event_handler "on": self.__on_event_handler,
"ou": self.__ou_event_handler,
"oc": self.__oc_event_handler,
"ps": self.__ps_event_handler,
"pn": self.__pn_event_handler,
"pu": self.__pu_event_handler,
"pc": self.__pc_event_handler,
"fos": self.__fos_event_handler,
"fon": self.__fon_event_handler,
"fou": self.__fou_event_handler,
"foc": self.__foc_event_handler,
} }
def handle(self, type, parameters): def handle(self, type, stream):
if type in self.__handlers: if type in self.__handlers:
self.__handlers[type](*parameters) self.__handlers[type](*stream)
elif self.strict == True:
raise BfxWebsocketException(f"Event of type <{type}> not found in self.__handlers.")
def __bu_event_handler(self, *parameters): def __bu_event_handler(self, *stream):
self.event_emitter.emit("balance_update", _label_array_elements( self.event_emitter.emit("balance_update", _label_stream_data(
[ [
"AUM", "AUM",
"AUM_NET" "AUM_NET"
], ],
*parameters *stream
)) ))
def __ws_event_handler(self, *parameters): def __ws_event_handler(self, *stream):
self.event_emitter.emit("wallet_snapshot", [ self.event_emitter.emit("wallet_snapshot", [
_label_array_elements( _label_stream_data(
[ [
"WALLET_TYPE", "WALLET_TYPE",
"CURRENCY", "CURRENCY",
@@ -82,12 +96,12 @@ class AuthenticatedEventsHandler(object):
"DESCRIPTION", "DESCRIPTION",
"META" "META"
], ],
*parameter *substream
) for parameter in parameters ) for substream in stream
]) ])
def __wu_event_handler(self, *parameters): def __wu_event_handler(self, *stream):
self.event_emitter.emit("wallet_update", _label_array_elements( self.event_emitter.emit("wallet_update", _label_stream_data(
[ [
"WALLET_TYPE", "WALLET_TYPE",
"CURRENCY", "CURRENCY",
@@ -97,12 +111,12 @@ class AuthenticatedEventsHandler(object):
"DESCRIPTION", "DESCRIPTION",
"META" "META"
], ],
*parameters *stream
)) ))
def __os_event_handler(self, *parameters): def __os_event_handler(self, *stream):
self.event_emitter.emit("order_snapshot", [ self.event_emitter.emit("order_snapshot", [
_label_array_elements( _label_stream_data(
[ [
"ID", "ID",
"GID", "GID",
@@ -137,12 +151,12 @@ class AuthenticatedEventsHandler(object):
"_PLACEHOLDER", "_PLACEHOLDER",
"META" "META"
], ],
*parameter *substream
) for parameter in parameters ) for substream in stream
]) ])
def __on_event_handler(self, *parameters): def __on_event_handler(self, *stream):
self.event_emitter.emit("new_order", _label_array_elements( self.event_emitter.emit("new_order", _label_stream_data(
[ [
"ID", "ID",
"GID", "GID",
@@ -177,13 +191,315 @@ class AuthenticatedEventsHandler(object):
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER" "_PLACEHOLDER"
], ],
*parameters *stream
)) ))
def _label_array_elements(labels, *args): def __ou_event_handler(self, *stream):
self.event_emitter.emit("order_update", _label_stream_data(
[
"ID",
"GID",
"CID",
"SYMBOL",
"MTS_CREATE",
"MTS_UPDATE",
"AMOUNT",
"AMOUNT_ORIG",
"ORDER_TYPE",
"TYPE_PREV",
"MTS_TIF",
"_PLACEHOLDER",
"FLAGS",
"ORDER_STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"PRICE",
"PRICE_AVG",
"PRICE_TRAILING",
"PRICE_AUX_LIMIT",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"NOTIFY",
"HIDDEN",
"PLACED_ID",
"_PLACEHOLDER",
"_PLACEHOLDER",
"ROUTING",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER"
],
*stream
))
def __oc_event_handler(self, *stream):
self.event_emitter.emit("order_cancel", _label_stream_data(
[
"ID",
"GID",
"CID",
"SYMBOL",
"MTS_CREATE",
"MTS_UPDATE",
"AMOUNT",
"AMOUNT_ORIG",
"ORDER_TYPE",
"TYPE_PREV",
"MTS_TIF",
"_PLACEHOLDER",
"FLAGS",
"ORDER_STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"PRICE",
"PRICE_AVG",
"PRICE_TRAILING",
"PRICE_AUX_LIMIT",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"NOTIFY",
"HIDDEN",
"PLACED_ID",
"_PLACEHOLDER",
"_PLACEHOLDER",
"ROUTING",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER"
],
*stream
))
def __ps_event_handler(self, *stream):
self.event_emitter.emit("position_snapshot", [
_label_stream_data(
[
"SYMBOL",
"STATUS",
"AMOUNT",
"BASE_PRICE",
"MARGIN_FUNDING",
"MARGIN_FUNDING_TYPE",
"PL",
"PL_PERC",
"PRICE_LIQ",
"LEVERAGE",
"FLAG",
"POSITION_ID",
"MTS_CREATE",
"MTS_UPDATE",
"_PLACEHOLDER",
"TYPE",
"_PLACEHOLDER",
"COLLATERAL",
"COLLATERAL_MIN",
"META"
],
*substream
)
for substream in stream
])
def __pn_event_handler(self, *stream):
self.event_emitter.emit("new_position", _label_stream_data(
[
"SYMBOL",
"STATUS",
"AMOUNT",
"BASE_PRICE",
"MARGIN_FUNDING",
"MARGIN_FUNDING_TYPE",
"PL",
"PL_PERC",
"PRICE_LIQ",
"LEVERAGE",
"FLAG",
"POSITION_ID",
"MTS_CREATE",
"MTS_UPDATE",
"_PLACEHOLDER",
"TYPE",
"_PLACEHOLDER",
"COLLATERAL",
"COLLATERAL_MIN",
"META"
],
*stream
))
def __pu_event_handler(self, *stream):
self.event_emitter.emit("position_update", _label_stream_data(
[
"SYMBOL",
"STATUS",
"AMOUNT",
"BASE_PRICE",
"MARGIN_FUNDING",
"MARGIN_FUNDING_TYPE",
"PL",
"PL_PERC",
"PRICE_LIQ",
"LEVERAGE",
"FLAG",
"POSITION_ID",
"MTS_CREATE",
"MTS_UPDATE",
"_PLACEHOLDER",
"TYPE",
"_PLACEHOLDER",
"COLLATERAL",
"COLLATERAL_MIN",
"META"
],
*stream
))
def __pc_event_handler(self, *stream):
self.event_emitter.emit("position_cancel", _label_stream_data(
[
"SYMBOL",
"STATUS",
"AMOUNT",
"BASE_PRICE",
"MARGIN_FUNDING",
"MARGIN_FUNDING_TYPE",
"PL",
"PL_PERC",
"PRICE_LIQ",
"LEVERAGE",
"FLAG",
"POSITION_ID",
"MTS_CREATE",
"MTS_UPDATE",
"_PLACEHOLDER",
"TYPE",
"_PLACEHOLDER",
"COLLATERAL",
"COLLATERAL_MIN",
"META"
],
*stream
))
def __fos_event_handler(self, *stream):
self.event_emitter.emit("funding_offer_snapshot", [
_label_stream_data(
[
"ID",
"SYMBOL",
"MTS_CREATED",
"MTS_UPDATED",
"AMOUNT",
"AMOUNT_ORIG",
"OFFER_TYPE",
"_PLACEHOLDER",
"_PLACEHOLDER",
"FLAGS",
"STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"RATE",
"PERIOD",
"NOTIFY",
"HIDDEN",
"_PLACEHOLDER",
"RENEW",
"_PLACEHOLDER",
],
*substream
)
for substream in stream
])
def __fon_event_handler(self, *stream):
self.event_emitter.emit("funding_offer_new", _label_stream_data(
[
"ID",
"SYMBOL",
"MTS_CREATED",
"MTS_UPDATED",
"AMOUNT",
"AMOUNT_ORIG",
"TYPE",
"_PLACEHOLDER",
"_PLACEHOLDER",
"FLAGS",
"STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"RATE",
"PERIOD",
"NOTIFY",
"HIDDEN",
"_PLACEHOLDER",
"RENEW",
"RATE_REAL"
],
*stream
))
def __fou_event_handler(self, *stream):
self.event_emitter.emit("funding_offer_update", _label_stream_data(
[
"ID",
"SYMBOL",
"MTS_CREATED",
"MTS_UPDATED",
"AMOUNT",
"AMOUNT_ORIG",
"TYPE",
"_PLACEHOLDER",
"_PLACEHOLDER",
"FLAGS",
"STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"RATE",
"PERIOD",
"NOTIFY",
"HIDDEN",
"_PLACEHOLDER",
"RENEW",
"RATE_REAL"
],
*stream
))
def __foc_event_handler(self, *stream):
self.event_emitter.emit("funding_offer_cancel", _label_stream_data(
[
"ID",
"SYMBOL",
"MTS_CREATED",
"MTS_UPDATED",
"AMOUNT",
"AMOUNT_ORIG",
"TYPE",
"_PLACEHOLDER",
"_PLACEHOLDER",
"FLAGS",
"STATUS",
"_PLACEHOLDER",
"_PLACEHOLDER",
"_PLACEHOLDER",
"RATE",
"PERIOD",
"NOTIFY",
"HIDDEN",
"_PLACEHOLDER",
"RENEW",
"RATE_REAL"
],
*stream
))
def _label_stream_data(labels, *args, IGNORE = [ "_PLACEHOLDER" ]):
if len(labels) != len(args): if len(labels) != len(args):
raise Exception("<labels> and <*args> arguments should contain the same amount of elements.") raise BfxWebsocketException("<labels> and <*args> arguments should contain the same amount of elements.")
_PLACEHOLDER = "_PLACEHOLDER" return { label: args[index] for index, label in enumerate(labels) if label not in IGNORE }
return { label: args[index] for index, label in enumerate(labels) if label != _PLACEHOLDER }

View File

@@ -1,16 +1,125 @@
from typing import TypedDict, List, Optional from typing import Type, List, Dict, TypedDict, Union, Optional, Any
class BalanceUpdateStream(TypedDict): JSON = Union[Dict[str, Any], List[Any], int, str, float, bool, Type[None]]
AUM: float
AUM_NET: float
class WalletUpdateStream(TypedDict): BalanceUpdateStream = TypedDict("BalanceUpdateStream", {
WALLET_TYPE: str "AUM": float,
CURRENCY: str "AUM_NET": float
BALANCE: float })
UNSETTLED_INTEREST: float
BALANCE_AVAILABLE: Optional[float]
DESCRIPTION: str
META: dict
WalletSnapshotStream = List[WalletUpdateStream] WalletSnapshotStream = List[TypedDict("WalletSnapshotStream", {
"WALLET_TYPE": str,
"CURRENCY": str,
"BALANCE": float,
"UNSETTLED_INTEREST": float,
"BALANCE_AVAILABLE": Optional[float],
"DESCRIPTION": str,
"META": JSON
})]
WalletUpdateStream = TypedDict("WalletUpdateStream", {
"WALLET_TYPE": str,
"CURRENCY": str,
"BALANCE": float,
"UNSETTLED_INTEREST": float,
"BALANCE_AVAILABLE": Optional[float],
"DESCRIPTION": str,
"META": JSON
})
OrderSnapshotStream = List[TypedDict("OrderSnapshotStream", {
"ID": int,
"GID": int,
"CID": int,
"SYMBOL": str,
"MTS_CREATE": int,
"MTS_UPDATE": int,
"AMOUNT": float,
"AMOUNT_ORIG": float,
"ORDER_TYPE": str,
"TYPE_PREV": str,
"MTS_TIF": int,
"FLAGS": int,
"STATUS": str,
"PRICE": float,
"PRICE_AVG": float,
"PRICE_TRAILING": float,
"PRICE_AUX_LIMIT": float,
"NOTIFY": int,
"HIDDEN": int,
"PLACED_ID": int,
"ROUTING": str,
"META": JSON
})]
NewOrderStream = TypedDict("NewOrderStream", {
"ID": int,
"GID": int,
"CID": int,
"SYMBOL": str,
"MTS_CREATE": int,
"MTS_UPDATE": int,
"AMOUNT": float,
"AMOUNT_ORIG": float,
"ORDER_TYPE": str,
"TYPE_PREV": str,
"MTS_TIF": int,
"FLAGS": int,
"ORDER_STATUS": str,
"PRICE": float,
"PRICE_AVG": float,
"PRICE_TRAILING": float,
"PRICE_AUX_LIMIT": float,
"NOTIFY": int,
"HIDDEN": int,
"PLACED_ID": int,
"ROUTING": str
})
OrderUpdateStream = TypedDict("OrderUpdateStream", {
"ID": int,
"GID": int,
"CID": int,
"SYMBOL": str,
"MTS_CREATE": int,
"MTS_UPDATE": int,
"AMOUNT": float,
"AMOUNT_ORIG": float,
"ORDER_TYPE": str,
"TYPE_PREV": str,
"MTS_TIF": int,
"FLAGS": int,
"ORDER_STATUS": str,
"PRICE": float,
"PRICE_AVG": float,
"PRICE_TRAILING": float,
"PRICE_AUX_LIMIT": float,
"NOTIFY": int,
"HIDDEN": int,
"PLACED_ID": int,
"ROUTING": str
})
OrderCancelStream = TypedDict("OrderCancelStream", {
"ID": int,
"GID": int,
"CID": int,
"SYMBOL": str,
"MTS_CREATE": int,
"MTS_UPDATE": int,
"AMOUNT": float,
"AMOUNT_ORIG": float,
"ORDER_TYPE": str,
"TYPE_PREV": str,
"MTS_TIF": int,
"FLAGS": int,
"ORDER_STATUS": str,
"PRICE": float,
"PRICE_AVG": float,
"PRICE_TRAILING": float,
"PRICE_AUX_LIMIT": float,
"NOTIFY": int,
"HIDDEN": int,
"PLACED_ID": int,
"ROUTING": str
})