mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2026-01-07 16:04:21 +01:00
Apply black to all python files (bfxapi/**/*.py).
This commit is contained in:
@@ -13,9 +13,10 @@ from bfxapi.websocket.subscriptions import Subscription
|
||||
|
||||
_CHECKSUM_FLAG_VALUE = 131_072
|
||||
|
||||
|
||||
def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]:
|
||||
return { key: value for key, value in message.items() \
|
||||
if not key in keys }
|
||||
return {key: value for key, value in message.items() if not key in keys}
|
||||
|
||||
|
||||
class BfxWebSocketBucket(Connection):
|
||||
__MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
|
||||
@@ -24,28 +25,26 @@ class BfxWebSocketBucket(Connection):
|
||||
super().__init__(host)
|
||||
|
||||
self.__event_emitter = event_emitter
|
||||
self.__pendings: List[Dict[str, Any]] = [ ]
|
||||
self.__subscriptions: Dict[int, Subscription] = { }
|
||||
self.__pendings: List[Dict[str, Any]] = []
|
||||
self.__subscriptions: Dict[int, Subscription] = {}
|
||||
|
||||
self.__condition = asyncio.locks.Condition()
|
||||
|
||||
self.__handler = PublicChannelsHandler( \
|
||||
event_emitter=self.__event_emitter)
|
||||
self.__handler = PublicChannelsHandler(event_emitter=self.__event_emitter)
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
return len(self.__pendings) + \
|
||||
len(self.__subscriptions)
|
||||
return len(self.__pendings) + len(self.__subscriptions)
|
||||
|
||||
@property
|
||||
def is_full(self) -> bool:
|
||||
return self.count == \
|
||||
BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
|
||||
return self.count == BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
|
||||
|
||||
@property
|
||||
def ids(self) -> List[str]:
|
||||
return [ pending["subId"] for pending in self.__pendings ] + \
|
||||
[ subscription["sub_id"] for subscription in self.__subscriptions.values() ]
|
||||
return [pending["subId"] for pending in self.__pendings] + [
|
||||
subscription["sub_id"] for subscription in self.__subscriptions.values()
|
||||
]
|
||||
|
||||
async def start(self) -> None:
|
||||
async with websockets.client.connect(self._host) as websocket:
|
||||
@@ -64,20 +63,25 @@ class BfxWebSocketBucket(Connection):
|
||||
self.__on_subscribed(message)
|
||||
|
||||
if isinstance(message, list):
|
||||
if (chan_id := cast(int, message[0])) and \
|
||||
(subscription := self.__subscriptions.get(chan_id)) and \
|
||||
(message[1] != Connection._HEARTBEAT):
|
||||
if (
|
||||
(chan_id := cast(int, message[0]))
|
||||
and (subscription := self.__subscriptions.get(chan_id))
|
||||
and (message[1] != Connection._HEARTBEAT)
|
||||
):
|
||||
self.__handler.handle(subscription, message[1:])
|
||||
|
||||
def __on_subscribed(self, message: Dict[str, Any]) -> None:
|
||||
chan_id = cast(int, message["chan_id"])
|
||||
|
||||
subscription = cast(Subscription, _strip(message, \
|
||||
keys=["chan_id", "event", "pair", "currency"]))
|
||||
subscription = cast(
|
||||
Subscription, _strip(message, keys=["chan_id", "event", "pair", "currency"])
|
||||
)
|
||||
|
||||
self.__pendings = [ pending \
|
||||
for pending in self.__pendings \
|
||||
if pending["subId"] != message["sub_id"] ]
|
||||
self.__pendings = [
|
||||
pending
|
||||
for pending in self.__pendings
|
||||
if pending["subId"] != message["sub_id"]
|
||||
]
|
||||
|
||||
self.__subscriptions[chan_id] = subscription
|
||||
|
||||
@@ -85,47 +89,43 @@ class BfxWebSocketBucket(Connection):
|
||||
|
||||
async def __recover_state(self) -> None:
|
||||
for pending in self.__pendings:
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(pending))
|
||||
await self._websocket.send(message=json.dumps(pending))
|
||||
|
||||
for chan_id in list(self.__subscriptions.keys()):
|
||||
subscription = self.__subscriptions.pop(chan_id)
|
||||
|
||||
await self.subscribe(**subscription)
|
||||
|
||||
await self.__set_config([ _CHECKSUM_FLAG_VALUE ])
|
||||
await self.__set_config([_CHECKSUM_FLAG_VALUE])
|
||||
|
||||
async def __set_config(self, flags: List[int]) -> None:
|
||||
await self._websocket.send(json.dumps( \
|
||||
{ "event": "conf", "flags": sum(flags) }))
|
||||
await self._websocket.send(json.dumps({"event": "conf", "flags": sum(flags)}))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def subscribe(self,
|
||||
channel: str,
|
||||
sub_id: Optional[str] = None,
|
||||
**kwargs: Any) -> None:
|
||||
subscription: Dict[str, Any] = \
|
||||
{ **kwargs, "event": "subscribe", "channel": channel }
|
||||
async def subscribe(
|
||||
self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
|
||||
) -> None:
|
||||
subscription: Dict[str, Any] = {
|
||||
**kwargs,
|
||||
"event": "subscribe",
|
||||
"channel": channel,
|
||||
}
|
||||
|
||||
subscription["subId"] = sub_id or str(uuid.uuid4())
|
||||
|
||||
self.__pendings.append(subscription)
|
||||
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(subscription))
|
||||
await self._websocket.send(message=json.dumps(subscription))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def unsubscribe(self, sub_id: str) -> None:
|
||||
for chan_id, subscription in list(self.__subscriptions.items()):
|
||||
if subscription["sub_id"] == sub_id:
|
||||
unsubscription = {
|
||||
"event": "unsubscribe",
|
||||
"chanId": chan_id }
|
||||
unsubscription = {"event": "unsubscribe", "chanId": chan_id}
|
||||
|
||||
del self.__subscriptions[chan_id]
|
||||
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(unsubscription))
|
||||
await self._websocket.send(message=json.dumps(unsubscription))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def resubscribe(self, sub_id: str) -> None:
|
||||
@@ -148,5 +148,4 @@ class BfxWebSocketBucket(Connection):
|
||||
|
||||
async def wait(self) -> None:
|
||||
async with self.__condition:
|
||||
await self.__condition \
|
||||
.wait_for(lambda: self.open)
|
||||
await self.__condition.wait_for(lambda: self.open)
|
||||
|
||||
@@ -28,14 +28,17 @@ from bfxapi.websocket.exceptions import (
|
||||
from .bfx_websocket_bucket import BfxWebSocketBucket
|
||||
from .bfx_websocket_inputs import BfxWebSocketInputs
|
||||
|
||||
_Credentials = TypedDict("_Credentials", \
|
||||
{ "api_key": str, "api_secret": str, "filters": Optional[List[str]] })
|
||||
_Credentials = TypedDict(
|
||||
"_Credentials", {"api_key": str, "api_secret": str, "filters": Optional[List[str]]}
|
||||
)
|
||||
|
||||
_Reconnection = TypedDict("_Reconnection",
|
||||
{ "attempts": int, "reason": str, "timestamp": datetime })
|
||||
_Reconnection = TypedDict(
|
||||
"_Reconnection", {"attempts": int, "reason": str, "timestamp": datetime}
|
||||
)
|
||||
|
||||
_DEFAULT_LOGGER = Logger("bfxapi.websocket._client", level=0)
|
||||
|
||||
|
||||
class _Delay:
|
||||
__BACKOFF_MIN = 1.92
|
||||
|
||||
@@ -54,59 +57,61 @@ class _Delay:
|
||||
return _backoff_delay
|
||||
|
||||
def peek(self) -> float:
|
||||
return (self.__backoff_delay == _Delay.__BACKOFF_MIN) \
|
||||
and self.__initial_delay or self.__backoff_delay
|
||||
return (
|
||||
(self.__backoff_delay == _Delay.__BACKOFF_MIN)
|
||||
and self.__initial_delay
|
||||
or self.__backoff_delay
|
||||
)
|
||||
|
||||
def reset(self) -> None:
|
||||
self.__backoff_delay = _Delay.__BACKOFF_MIN
|
||||
|
||||
#pylint: disable-next=too-many-instance-attributes
|
||||
|
||||
# pylint: disable-next=too-many-instance-attributes
|
||||
class BfxWebSocketClient(Connection):
|
||||
def __init__(self,
|
||||
host: str,
|
||||
*,
|
||||
credentials: Optional[_Credentials] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
logger: Logger = _DEFAULT_LOGGER) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
*,
|
||||
credentials: Optional[_Credentials] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
logger: Logger = _DEFAULT_LOGGER,
|
||||
) -> None:
|
||||
super().__init__(host)
|
||||
|
||||
self.__credentials, self.__timeout, self.__logger = \
|
||||
credentials, \
|
||||
timeout, \
|
||||
logger
|
||||
self.__credentials, self.__timeout, self.__logger = credentials, timeout, logger
|
||||
|
||||
self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = { }
|
||||
self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = {}
|
||||
|
||||
self.__reconnection: Optional[_Reconnection] = None
|
||||
|
||||
self.__event_emitter = BfxEventEmitter(loop=None)
|
||||
|
||||
self.__handler = AuthEventsHandler( \
|
||||
event_emitter=self.__event_emitter)
|
||||
self.__handler = AuthEventsHandler(event_emitter=self.__event_emitter)
|
||||
|
||||
self.__inputs = BfxWebSocketInputs( \
|
||||
handle_websocket_input=self.__handle_websocket_input)
|
||||
self.__inputs = BfxWebSocketInputs(
|
||||
handle_websocket_input=self.__handle_websocket_input
|
||||
)
|
||||
|
||||
@self.__event_emitter.listens_to("error")
|
||||
def error(exception: Exception) -> None:
|
||||
header = f"{type(exception).__name__}: {str(exception)}"
|
||||
|
||||
stack_trace = traceback.format_exception( \
|
||||
type(exception), exception, exception.__traceback__)
|
||||
stack_trace = traceback.format_exception(
|
||||
type(exception), exception, exception.__traceback__
|
||||
)
|
||||
|
||||
#pylint: disable-next=logging-not-lazy
|
||||
self.__logger.critical(header + "\n" + \
|
||||
str().join(stack_trace)[:-1])
|
||||
# pylint: disable-next=logging-not-lazy
|
||||
self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1])
|
||||
|
||||
@property
|
||||
def inputs(self) -> BfxWebSocketInputs:
|
||||
return self.__inputs
|
||||
|
||||
def run(self) -> None:
|
||||
return asyncio.get_event_loop() \
|
||||
.run_until_complete(self.start())
|
||||
return asyncio.get_event_loop().run_until_complete(self.start())
|
||||
|
||||
#pylint: disable-next=too-many-branches
|
||||
# pylint: disable-next=too-many-branches
|
||||
async def start(self) -> None:
|
||||
_delay = _Delay(backoff_factor=1.618)
|
||||
|
||||
@@ -119,19 +124,20 @@ class BfxWebSocketClient(Connection):
|
||||
|
||||
while True:
|
||||
if self.__reconnection:
|
||||
_sleep = asyncio.create_task( \
|
||||
asyncio.sleep(int(_delay.next())))
|
||||
_sleep = asyncio.create_task(asyncio.sleep(int(_delay.next())))
|
||||
|
||||
try:
|
||||
await _sleep
|
||||
except asyncio.CancelledError:
|
||||
raise ReconnectionTimeoutError("Connection has been offline for too long " \
|
||||
f"without being able to reconnect (timeout: {self.__timeout}s).") \
|
||||
from None
|
||||
raise ReconnectionTimeoutError(
|
||||
"Connection has been offline for too long "
|
||||
f"without being able to reconnect (timeout: {self.__timeout}s)."
|
||||
) from None
|
||||
|
||||
try:
|
||||
await self.__connect()
|
||||
except (ConnectionClosedError, InvalidStatusCode, gaierror) as error:
|
||||
|
||||
async def _cancel(task: Task) -> None:
|
||||
task.cancel()
|
||||
|
||||
@@ -150,69 +156,87 @@ class BfxWebSocketClient(Connection):
|
||||
|
||||
await _cancel(task)
|
||||
|
||||
if isinstance(error, ConnectionClosedError) and error.code in (1006, 1012):
|
||||
if isinstance(error, ConnectionClosedError) and error.code in (
|
||||
1006,
|
||||
1012,
|
||||
):
|
||||
if error.code == 1006:
|
||||
self.__logger.error("Connection lost: trying to reconnect...")
|
||||
|
||||
if error.code == 1012:
|
||||
self.__logger.warning("WSS server is restarting: all " \
|
||||
"clients need to reconnect (server sent 20051).")
|
||||
self.__logger.warning(
|
||||
"WSS server is restarting: all "
|
||||
"clients need to reconnect (server sent 20051)."
|
||||
)
|
||||
|
||||
if self.__timeout:
|
||||
asyncio.get_event_loop().call_later(
|
||||
self.__timeout, _on_timeout)
|
||||
asyncio.get_event_loop().call_later(self.__timeout, _on_timeout)
|
||||
|
||||
self.__reconnection = \
|
||||
{ "attempts": 1, "reason": error.reason, "timestamp": datetime.now() }
|
||||
self.__reconnection = {
|
||||
"attempts": 1,
|
||||
"reason": error.reason,
|
||||
"timestamp": datetime.now(),
|
||||
}
|
||||
|
||||
self._authentication = False
|
||||
|
||||
_delay.reset()
|
||||
elif ((isinstance(error, InvalidStatusCode) and error.status_code == 408) or \
|
||||
isinstance(error, gaierror)) and self.__reconnection:
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning("Reconnection attempt unsuccessful (no." \
|
||||
f"{self.__reconnection['attempts']}): next attempt in " \
|
||||
f"~{int(_delay.peek())}.0s.")
|
||||
elif (
|
||||
(isinstance(error, InvalidStatusCode) and error.status_code == 408)
|
||||
or isinstance(error, gaierror)
|
||||
) and self.__reconnection:
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning(
|
||||
"Reconnection attempt unsuccessful (no."
|
||||
f"{self.__reconnection['attempts']}): next attempt in "
|
||||
f"~{int(_delay.peek())}.0s."
|
||||
)
|
||||
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.info(f"The client has been offline for " \
|
||||
f"{datetime.now() - self.__reconnection['timestamp']}.")
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.info(
|
||||
f"The client has been offline for "
|
||||
f"{datetime.now() - self.__reconnection['timestamp']}."
|
||||
)
|
||||
|
||||
self.__reconnection["attempts"] += 1
|
||||
else:
|
||||
raise error
|
||||
|
||||
if not self.__reconnection:
|
||||
self.__event_emitter.emit("disconnected",
|
||||
self._websocket.close_code, \
|
||||
self._websocket.close_reason)
|
||||
self.__event_emitter.emit(
|
||||
"disconnected",
|
||||
self._websocket.close_code,
|
||||
self._websocket.close_reason,
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
async def __connect(self) -> None:
|
||||
async with websockets.client.connect(self._host) as websocket:
|
||||
if self.__reconnection:
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning("Reconnection attempt successful (no." \
|
||||
f"{self.__reconnection['attempts']}): recovering " \
|
||||
"connection state...")
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning(
|
||||
"Reconnection attempt successful (no."
|
||||
f"{self.__reconnection['attempts']}): recovering "
|
||||
"connection state..."
|
||||
)
|
||||
|
||||
self.__reconnection = None
|
||||
|
||||
self._websocket = websocket
|
||||
|
||||
for bucket in self.__buckets:
|
||||
self.__buckets[bucket] = \
|
||||
asyncio.create_task(bucket.start())
|
||||
self.__buckets[bucket] = asyncio.create_task(bucket.start())
|
||||
|
||||
if len(self.__buckets) == 0 or \
|
||||
(await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])):
|
||||
if len(self.__buckets) == 0 or (
|
||||
await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])
|
||||
):
|
||||
self.__event_emitter.emit("open")
|
||||
|
||||
if self.__credentials:
|
||||
authentication = Connection. \
|
||||
_get_authentication_message(**self.__credentials)
|
||||
authentication = Connection._get_authentication_message(
|
||||
**self.__credentials
|
||||
)
|
||||
|
||||
await self._websocket.send(authentication)
|
||||
|
||||
@@ -222,61 +246,64 @@ class BfxWebSocketClient(Connection):
|
||||
if isinstance(message, dict):
|
||||
if message["event"] == "info" and "version" in message:
|
||||
if message["version"] != 2:
|
||||
raise VersionMismatchError("Mismatch between the client and the server version: " + \
|
||||
"please update bitfinex-api-py to the latest version to resolve this error " + \
|
||||
f"(client version: 2, server version: {message['version']}).")
|
||||
raise VersionMismatchError(
|
||||
"Mismatch between the client and the server version: "
|
||||
+ "please update bitfinex-api-py to the latest version to resolve this error "
|
||||
+ f"(client version: 2, server version: {message['version']})."
|
||||
)
|
||||
elif message["event"] == "info" and message["code"] == 20051:
|
||||
rcvd = websockets.frames.Close( \
|
||||
1012, "Stop/Restart WebSocket Server (please reconnect).")
|
||||
rcvd = websockets.frames.Close(
|
||||
1012, "Stop/Restart WebSocket Server (please reconnect)."
|
||||
)
|
||||
|
||||
raise ConnectionClosedError(rcvd=rcvd, sent=None)
|
||||
elif message["event"] == "auth":
|
||||
if message["status"] != "OK":
|
||||
raise InvalidCredentialError("Can't authenticate " + \
|
||||
"with given API-KEY and API-SECRET.")
|
||||
raise InvalidCredentialError(
|
||||
"Can't authenticate "
|
||||
+ "with given API-KEY and API-SECRET."
|
||||
)
|
||||
|
||||
self.__event_emitter.emit("authenticated", message)
|
||||
|
||||
self._authentication = True
|
||||
|
||||
if isinstance(message, list) and \
|
||||
message[0] == 0 and message[1] != Connection._HEARTBEAT:
|
||||
if (
|
||||
isinstance(message, list)
|
||||
and message[0] == 0
|
||||
and message[1] != Connection._HEARTBEAT
|
||||
):
|
||||
self.__handler.handle(message[1], message[2])
|
||||
|
||||
async def __new_bucket(self) -> BfxWebSocketBucket:
|
||||
bucket = BfxWebSocketBucket( \
|
||||
self._host, self.__event_emitter)
|
||||
bucket = BfxWebSocketBucket(self._host, self.__event_emitter)
|
||||
|
||||
self.__buckets[bucket] = asyncio \
|
||||
.create_task(bucket.start())
|
||||
self.__buckets[bucket] = asyncio.create_task(bucket.start())
|
||||
|
||||
await bucket.wait()
|
||||
|
||||
return bucket
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def subscribe(self,
|
||||
channel: str,
|
||||
sub_id: Optional[str] = None,
|
||||
**kwargs: Any) -> None:
|
||||
async def subscribe(
|
||||
self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
|
||||
) -> None:
|
||||
if not channel in ["ticker", "trades", "book", "candles", "status"]:
|
||||
raise UnknownChannelError("Available channels are: " + \
|
||||
"ticker, trades, book, candles and status.")
|
||||
raise UnknownChannelError(
|
||||
"Available channels are: " + "ticker, trades, book, candles and status."
|
||||
)
|
||||
|
||||
for bucket in self.__buckets:
|
||||
if sub_id in bucket.ids:
|
||||
raise SubIdError("sub_id must be " + \
|
||||
"unique for all subscriptions.")
|
||||
raise SubIdError("sub_id must be " + "unique for all subscriptions.")
|
||||
|
||||
for bucket in self.__buckets:
|
||||
if not bucket.is_full:
|
||||
return await bucket.subscribe( \
|
||||
channel, sub_id, **kwargs)
|
||||
return await bucket.subscribe(channel, sub_id, **kwargs)
|
||||
|
||||
bucket = await self.__new_bucket()
|
||||
|
||||
return await bucket.subscribe( \
|
||||
channel, sub_id, **kwargs)
|
||||
return await bucket.subscribe(channel, sub_id, **kwargs)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def unsubscribe(self, sub_id: str) -> None:
|
||||
@@ -286,13 +313,13 @@ class BfxWebSocketClient(Connection):
|
||||
if bucket.count == 1:
|
||||
del self.__buckets[bucket]
|
||||
|
||||
return await bucket.close( \
|
||||
code=1001, reason="Going Away")
|
||||
return await bucket.close(code=1001, reason="Going Away")
|
||||
|
||||
return await bucket.unsubscribe(sub_id)
|
||||
|
||||
raise UnknownSubscriptionError("Unable to find " + \
|
||||
f"a subscription with sub_id <{sub_id}>.")
|
||||
raise UnknownSubscriptionError(
|
||||
"Unable to find " + f"a subscription with sub_id <{sub_id}>."
|
||||
)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def resubscribe(self, sub_id: str) -> None:
|
||||
@@ -300,8 +327,9 @@ class BfxWebSocketClient(Connection):
|
||||
if bucket.has(sub_id):
|
||||
return await bucket.resubscribe(sub_id)
|
||||
|
||||
raise UnknownSubscriptionError("Unable to find " + \
|
||||
f"a subscription with sub_id <{sub_id}>.")
|
||||
raise UnknownSubscriptionError(
|
||||
"Unable to find " + f"a subscription with sub_id <{sub_id}>."
|
||||
)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def close(self, code: int = 1000, reason: str = str()) -> None:
|
||||
@@ -309,22 +337,21 @@ class BfxWebSocketClient(Connection):
|
||||
await bucket.close(code=code, reason=reason)
|
||||
|
||||
if self._websocket.open:
|
||||
await self._websocket.close( \
|
||||
code=code, reason=reason)
|
||||
await self._websocket.close(code=code, reason=reason)
|
||||
|
||||
@Connection._require_websocket_authentication
|
||||
async def notify(self,
|
||||
info: Any,
|
||||
message_id: Optional[int] = None,
|
||||
**kwargs: Any) -> None:
|
||||
async def notify(
|
||||
self, info: Any, message_id: Optional[int] = None, **kwargs: Any
|
||||
) -> None:
|
||||
await self._websocket.send(
|
||||
json.dumps([ 0, "n", message_id,
|
||||
{ "type": "ucm-test", "info": info, **kwargs } ]))
|
||||
json.dumps(
|
||||
[0, "n", message_id, {"type": "ucm-test", "info": info, **kwargs}]
|
||||
)
|
||||
)
|
||||
|
||||
@Connection._require_websocket_authentication
|
||||
async def __handle_websocket_input(self, event: str, data: Any) -> None:
|
||||
await self._websocket.send(json.dumps( \
|
||||
[ 0, event, None, data], cls=JSONEncoder))
|
||||
await self._websocket.send(json.dumps([0, event, None, data], cls=JSONEncoder))
|
||||
|
||||
def on(self, event, callback = None):
|
||||
def on(self, event, callback=None):
|
||||
return self.__event_emitter.on(event, callback)
|
||||
|
||||
@@ -3,89 +3,127 @@ from typing import Any, Awaitable, Callable, List, Optional, Tuple, Union
|
||||
|
||||
_Handler = Callable[[str, Any], Awaitable[None]]
|
||||
|
||||
|
||||
class BfxWebSocketInputs:
|
||||
def __init__(self, handle_websocket_input: _Handler) -> None:
|
||||
self.__handle_websocket_input = handle_websocket_input
|
||||
|
||||
async def submit_order(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("on", {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"price": price, "lev": lev, "price_trailing": price_trailing,
|
||||
"price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid,
|
||||
"cid": cid, "flags": flags, "tif": tif
|
||||
})
|
||||
async def submit_order(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"on",
|
||||
{
|
||||
"type": type,
|
||||
"symbol": symbol,
|
||||
"amount": amount,
|
||||
"price": price,
|
||||
"lev": lev,
|
||||
"price_trailing": price_trailing,
|
||||
"price_aux_limit": price_aux_limit,
|
||||
"price_oco_stop": price_oco_stop,
|
||||
"gid": gid,
|
||||
"cid": cid,
|
||||
"flags": flags,
|
||||
"tif": tif,
|
||||
},
|
||||
)
|
||||
|
||||
async def update_order(self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("ou", {
|
||||
"id": id, "amount": amount, "price": price,
|
||||
"cid": cid, "cid_date": cid_date, "gid": gid,
|
||||
"flags": flags, "lev": lev, "delta": delta,
|
||||
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
|
||||
})
|
||||
async def update_order(
|
||||
self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"ou",
|
||||
{
|
||||
"id": id,
|
||||
"amount": amount,
|
||||
"price": price,
|
||||
"cid": cid,
|
||||
"cid_date": cid_date,
|
||||
"gid": gid,
|
||||
"flags": flags,
|
||||
"lev": lev,
|
||||
"delta": delta,
|
||||
"price_aux_limit": price_aux_limit,
|
||||
"price_trailing": price_trailing,
|
||||
"tif": tif,
|
||||
},
|
||||
)
|
||||
|
||||
async def cancel_order(self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("oc", {
|
||||
"id": id, "cid": cid, "cid_date": cid_date
|
||||
})
|
||||
async def cancel_order(
|
||||
self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"oc", {"id": id, "cid": cid, "cid_date": cid_date}
|
||||
)
|
||||
|
||||
async def cancel_order_multi(self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None) -> None:
|
||||
await self.__handle_websocket_input("oc_multi", {
|
||||
"id": id, "cid": cid, "gid": gid,
|
||||
"all": all
|
||||
})
|
||||
async def cancel_order_multi(
|
||||
self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"oc_multi", {"id": id, "cid": cid, "gid": gid, "all": all}
|
||||
)
|
||||
|
||||
#pylint: disable-next=too-many-arguments
|
||||
async def submit_funding_offer(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None) -> None:
|
||||
await self.__handle_websocket_input("fon", {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period, "flags": flags
|
||||
})
|
||||
# pylint: disable-next=too-many-arguments
|
||||
async def submit_funding_offer(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"fon",
|
||||
{
|
||||
"type": type,
|
||||
"symbol": symbol,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"period": period,
|
||||
"flags": flags,
|
||||
},
|
||||
)
|
||||
|
||||
async def cancel_funding_offer(self, id: int) -> None:
|
||||
await self.__handle_websocket_input("foc", { "id": id })
|
||||
await self.__handle_websocket_input("foc", {"id": id})
|
||||
|
||||
async def calc(self, *args: str) -> None:
|
||||
await self.__handle_websocket_input("calc",
|
||||
list(map(lambda arg: [arg], args)))
|
||||
await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
|
||||
|
||||
@@ -18,6 +18,7 @@ _R = TypeVar("_R")
|
||||
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
|
||||
class Connection(ABC):
|
||||
_HEARTBEAT = "hb"
|
||||
|
||||
@@ -30,8 +31,7 @@ class Connection(ABC):
|
||||
|
||||
@property
|
||||
def open(self) -> bool:
|
||||
return self.__protocol is not None and \
|
||||
self.__protocol.open
|
||||
return self.__protocol is not None and self.__protocol.open
|
||||
|
||||
@property
|
||||
def authentication(self) -> bool:
|
||||
@@ -46,12 +46,11 @@ class Connection(ABC):
|
||||
self.__protocol = protocol
|
||||
|
||||
@abstractmethod
|
||||
async def start(self) -> None:
|
||||
...
|
||||
async def start(self) -> None: ...
|
||||
|
||||
@staticmethod
|
||||
def _require_websocket_connection(
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]]
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]],
|
||||
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
|
||||
@wraps(function)
|
||||
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
|
||||
@@ -64,13 +63,15 @@ class Connection(ABC):
|
||||
|
||||
@staticmethod
|
||||
def _require_websocket_authentication(
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]]
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]],
|
||||
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
|
||||
@wraps(function)
|
||||
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
|
||||
if not self.authentication:
|
||||
raise ActionRequiresAuthentication("To perform this action you need to " \
|
||||
"authenticate using your API_KEY and API_SECRET.")
|
||||
raise ActionRequiresAuthentication(
|
||||
"To perform this action you need to "
|
||||
"authenticate using your API_KEY and API_SECRET."
|
||||
)
|
||||
|
||||
internal = Connection._require_websocket_connection(function)
|
||||
|
||||
@@ -80,12 +81,13 @@ class Connection(ABC):
|
||||
|
||||
@staticmethod
|
||||
def _get_authentication_message(
|
||||
api_key: str,
|
||||
api_secret: str,
|
||||
filters: Optional[List[str]] = None
|
||||
api_key: str, api_secret: str, filters: Optional[List[str]] = None
|
||||
) -> str:
|
||||
message: Dict[str, Any] = \
|
||||
{ "event": "auth", "filter": filters, "apiKey": api_key }
|
||||
message: Dict[str, Any] = {
|
||||
"event": "auth",
|
||||
"filter": filters,
|
||||
"apiKey": api_key,
|
||||
}
|
||||
|
||||
message["authNonce"] = round(datetime.now().timestamp() * 1_000_000)
|
||||
|
||||
@@ -94,7 +96,7 @@ class Connection(ABC):
|
||||
auth_sig = hmac.new(
|
||||
key=api_secret.encode("utf8"),
|
||||
msg=message["authPayload"].encode("utf8"),
|
||||
digestmod=hashlib.sha384
|
||||
digestmod=hashlib.sha384,
|
||||
)
|
||||
|
||||
message["authSig"] = auth_sig.hexdigest()
|
||||
|
||||
@@ -9,57 +9,86 @@ from bfxapi.websocket.exceptions import UnknownEventError
|
||||
_Handler = TypeVar("_Handler", bound=Callable[..., None])
|
||||
|
||||
_ONCE_PER_CONNECTION = [
|
||||
"open", "authenticated", "order_snapshot",
|
||||
"position_snapshot", "funding_offer_snapshot", "funding_credit_snapshot",
|
||||
"funding_loan_snapshot", "wallet_snapshot"
|
||||
"open",
|
||||
"authenticated",
|
||||
"order_snapshot",
|
||||
"position_snapshot",
|
||||
"funding_offer_snapshot",
|
||||
"funding_credit_snapshot",
|
||||
"funding_loan_snapshot",
|
||||
"wallet_snapshot",
|
||||
]
|
||||
|
||||
_ONCE_PER_SUBSCRIPTION = [
|
||||
"subscribed", "t_trades_snapshot", "f_trades_snapshot",
|
||||
"t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot",
|
||||
"f_raw_book_snapshot", "candles_snapshot"
|
||||
"subscribed",
|
||||
"t_trades_snapshot",
|
||||
"f_trades_snapshot",
|
||||
"t_book_snapshot",
|
||||
"f_book_snapshot",
|
||||
"t_raw_book_snapshot",
|
||||
"f_raw_book_snapshot",
|
||||
"candles_snapshot",
|
||||
]
|
||||
|
||||
_COMMON = [
|
||||
"disconnected", "t_ticker_update", "f_ticker_update",
|
||||
"t_trade_execution", "t_trade_execution_update", "f_trade_execution",
|
||||
"f_trade_execution_update", "t_book_update", "f_book_update",
|
||||
"t_raw_book_update", "f_raw_book_update", "candles_update",
|
||||
"derivatives_status_update", "liquidation_feed_update", "checksum",
|
||||
"order_new", "order_update", "order_cancel",
|
||||
"position_new", "position_update", "position_close",
|
||||
"funding_offer_new", "funding_offer_update", "funding_offer_cancel",
|
||||
"funding_credit_new", "funding_credit_update", "funding_credit_close",
|
||||
"funding_loan_new", "funding_loan_update", "funding_loan_close",
|
||||
"trade_execution", "trade_execution_update", "wallet_update",
|
||||
"notification", "on-req-notification", "ou-req-notification",
|
||||
"oc-req-notification", "fon-req-notification", "foc-req-notification"
|
||||
"disconnected",
|
||||
"t_ticker_update",
|
||||
"f_ticker_update",
|
||||
"t_trade_execution",
|
||||
"t_trade_execution_update",
|
||||
"f_trade_execution",
|
||||
"f_trade_execution_update",
|
||||
"t_book_update",
|
||||
"f_book_update",
|
||||
"t_raw_book_update",
|
||||
"f_raw_book_update",
|
||||
"candles_update",
|
||||
"derivatives_status_update",
|
||||
"liquidation_feed_update",
|
||||
"checksum",
|
||||
"order_new",
|
||||
"order_update",
|
||||
"order_cancel",
|
||||
"position_new",
|
||||
"position_update",
|
||||
"position_close",
|
||||
"funding_offer_new",
|
||||
"funding_offer_update",
|
||||
"funding_offer_cancel",
|
||||
"funding_credit_new",
|
||||
"funding_credit_update",
|
||||
"funding_credit_close",
|
||||
"funding_loan_new",
|
||||
"funding_loan_update",
|
||||
"funding_loan_close",
|
||||
"trade_execution",
|
||||
"trade_execution_update",
|
||||
"wallet_update",
|
||||
"notification",
|
||||
"on-req-notification",
|
||||
"ou-req-notification",
|
||||
"oc-req-notification",
|
||||
"fon-req-notification",
|
||||
"foc-req-notification",
|
||||
]
|
||||
|
||||
|
||||
class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
_EVENTS = _ONCE_PER_CONNECTION + \
|
||||
_ONCE_PER_SUBSCRIPTION + \
|
||||
_COMMON
|
||||
_EVENTS = _ONCE_PER_CONNECTION + _ONCE_PER_SUBSCRIPTION + _COMMON
|
||||
|
||||
def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None:
|
||||
super().__init__(loop)
|
||||
|
||||
self._connection: List[str] = [ ]
|
||||
self._connection: List[str] = []
|
||||
|
||||
self._subscriptions: Dict[str, List[str]] = \
|
||||
defaultdict(lambda: [ ])
|
||||
self._subscriptions: Dict[str, List[str]] = defaultdict(lambda: [])
|
||||
|
||||
def emit(
|
||||
self,
|
||||
event: str,
|
||||
*args: Any,
|
||||
**kwargs: Any
|
||||
) -> bool:
|
||||
def emit(self, event: str, *args: Any, **kwargs: Any) -> bool:
|
||||
if event in _ONCE_PER_CONNECTION:
|
||||
if event in self._connection:
|
||||
return self._has_listeners(event)
|
||||
|
||||
self._connection += [ event ]
|
||||
self._connection += [event]
|
||||
|
||||
if event in _ONCE_PER_SUBSCRIPTION:
|
||||
sub_id = args[0]["sub_id"]
|
||||
@@ -67,7 +96,7 @@ class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
if event in self._subscriptions[sub_id]:
|
||||
return self._has_listeners(event)
|
||||
|
||||
self._subscriptions[sub_id] += [ event ]
|
||||
self._subscriptions[sub_id] += [event]
|
||||
|
||||
return super().emit(event, *args, **kwargs)
|
||||
|
||||
@@ -75,8 +104,10 @@ class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
self, event: str, f: Optional[_Handler] = None
|
||||
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
||||
if event not in BfxEventEmitter._EVENTS:
|
||||
raise UnknownEventError(f"Can't register to unknown event: <{event}> " + \
|
||||
"(to get a full list of available events see https://docs.bitfinex.com/).")
|
||||
raise UnknownEventError(
|
||||
f"Can't register to unknown event: <{event}> "
|
||||
+ "(to get a full list of available events see https://docs.bitfinex.com/)."
|
||||
)
|
||||
|
||||
return super().on(event, f)
|
||||
|
||||
|
||||
@@ -9,14 +9,30 @@ from bfxapi.types.serializers import _Notification
|
||||
|
||||
class AuthEventsHandler:
|
||||
__ABBREVIATIONS = {
|
||||
"os": "order_snapshot", "on": "order_new", "ou": "order_update",
|
||||
"oc": "order_cancel", "ps": "position_snapshot", "pn": "position_new",
|
||||
"pu": "position_update", "pc": "position_close", "te": "trade_execution",
|
||||
"tu": "trade_execution_update", "fos": "funding_offer_snapshot", "fon": "funding_offer_new",
|
||||
"fou": "funding_offer_update", "foc": "funding_offer_cancel", "fcs": "funding_credit_snapshot",
|
||||
"fcn": "funding_credit_new", "fcu": "funding_credit_update", "fcc": "funding_credit_close",
|
||||
"fls": "funding_loan_snapshot", "fln": "funding_loan_new", "flu": "funding_loan_update",
|
||||
"flc": "funding_loan_close", "ws": "wallet_snapshot", "wu": "wallet_update"
|
||||
"os": "order_snapshot",
|
||||
"on": "order_new",
|
||||
"ou": "order_update",
|
||||
"oc": "order_cancel",
|
||||
"ps": "position_snapshot",
|
||||
"pn": "position_new",
|
||||
"pu": "position_update",
|
||||
"pc": "position_close",
|
||||
"te": "trade_execution",
|
||||
"tu": "trade_execution_update",
|
||||
"fos": "funding_offer_snapshot",
|
||||
"fon": "funding_offer_new",
|
||||
"fou": "funding_offer_update",
|
||||
"foc": "funding_offer_cancel",
|
||||
"fcs": "funding_credit_snapshot",
|
||||
"fcn": "funding_credit_new",
|
||||
"fcu": "funding_credit_update",
|
||||
"fcc": "funding_credit_close",
|
||||
"fls": "funding_loan_snapshot",
|
||||
"fln": "funding_loan_new",
|
||||
"flu": "funding_loan_update",
|
||||
"flc": "funding_loan_close",
|
||||
"ws": "wallet_snapshot",
|
||||
"wu": "wallet_update",
|
||||
}
|
||||
|
||||
__SERIALIZERS: Dict[Tuple[str, ...], serializers._Serializer] = {
|
||||
@@ -26,7 +42,7 @@ class AuthEventsHandler:
|
||||
("fos", "fon", "fou", "foc"): serializers.FundingOffer,
|
||||
("fcs", "fcn", "fcu", "fcc"): serializers.FundingCredit,
|
||||
("fls", "fln", "flu", "flc"): serializers.FundingLoan,
|
||||
("ws", "wu"): serializers.Wallet
|
||||
("ws", "wu"): serializers.Wallet,
|
||||
}
|
||||
|
||||
def __init__(self, event_emitter: EventEmitter) -> None:
|
||||
@@ -41,7 +57,7 @@ class AuthEventsHandler:
|
||||
event = AuthEventsHandler.__ABBREVIATIONS[abbrevation]
|
||||
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream):
|
||||
data = [ serializer.parse(*sub_stream) for sub_stream in stream ]
|
||||
data = [serializer.parse(*sub_stream) for sub_stream in stream]
|
||||
else:
|
||||
data = serializer.parse(*stream)
|
||||
|
||||
@@ -53,11 +69,13 @@ class AuthEventsHandler:
|
||||
serializer: _Notification = _Notification[None](serializer=None)
|
||||
|
||||
if stream[1] in ("on-req", "ou-req", "oc-req"):
|
||||
event, serializer = f"{stream[1]}-notification", \
|
||||
_Notification[Order](serializer=serializers.Order)
|
||||
event, serializer = f"{stream[1]}-notification", _Notification[Order](
|
||||
serializer=serializers.Order
|
||||
)
|
||||
|
||||
if stream[1] in ("fon-req", "foc-req"):
|
||||
event, serializer = f"{stream[1]}-notification", \
|
||||
_Notification[FundingOffer](serializer=serializers.FundingOffer)
|
||||
event, serializer = f"{stream[1]}-notification", _Notification[
|
||||
FundingOffer
|
||||
](serializer=serializers.FundingOffer)
|
||||
|
||||
self.__event_emitter.emit(event, serializer.parse(*stream))
|
||||
|
||||
@@ -14,6 +14,7 @@ from bfxapi.websocket.subscriptions import (
|
||||
|
||||
_CHECKSUM = "cs"
|
||||
|
||||
|
||||
class PublicChannelsHandler:
|
||||
def __init__(self, event_emitter: EventEmitter) -> None:
|
||||
self.__event_emitter = event_emitter
|
||||
@@ -38,101 +39,167 @@ class PublicChannelsHandler:
|
||||
elif subscription["channel"] == "status":
|
||||
self.__status_channel_handler(cast(Status, subscription), stream)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit("t_ticker_update", subscription, \
|
||||
serializers.TradingPairTicker.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_ticker_update",
|
||||
subscription,
|
||||
serializers.TradingPairTicker.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit("f_ticker_update", subscription, \
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_ticker_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __trades_channel_handler(self, subscription: Trades, stream: List[Any]):
|
||||
if (event := stream[0]) and event in [ "te", "tu", "fte", "ftu" ]:
|
||||
events = { "te": "t_trade_execution", "tu": "t_trade_execution_update", \
|
||||
"fte": "f_trade_execution", "ftu": "f_trade_execution_update" }
|
||||
if (event := stream[0]) and event in ["te", "tu", "fte", "ftu"]:
|
||||
events = {
|
||||
"te": "t_trade_execution",
|
||||
"tu": "t_trade_execution_update",
|
||||
"fte": "f_trade_execution",
|
||||
"ftu": "f_trade_execution_update",
|
||||
}
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit(events[event], subscription, \
|
||||
serializers.TradingPairTrade.parse(*stream[1]))
|
||||
return self.__event_emitter.emit(
|
||||
events[event],
|
||||
subscription,
|
||||
serializers.TradingPairTrade.parse(*stream[1]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit(events[event], subscription, \
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1]))
|
||||
return self.__event_emitter.emit(
|
||||
events[event],
|
||||
subscription,
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit("t_trades_snapshot", subscription, \
|
||||
[ serializers.TradingPairTrade.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_trades_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairTrade.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit("f_trades_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyTrade.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_trades_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyTrade.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __book_channel_handler(self, subscription: Book, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("t_book_snapshot", subscription, \
|
||||
[ serializers.TradingPairBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("t_book_update", subscription, \
|
||||
serializers.TradingPairBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_book_update",
|
||||
subscription,
|
||||
serializers.TradingPairBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("f_book_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("f_book_update", subscription, \
|
||||
serializers.FundingCurrencyBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_book_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("t_raw_book_snapshot", subscription, \
|
||||
[ serializers.TradingPairRawBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_raw_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairRawBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("t_raw_book_update", subscription, \
|
||||
serializers.TradingPairRawBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_raw_book_update",
|
||||
subscription,
|
||||
serializers.TradingPairRawBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("f_raw_book_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyRawBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_raw_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyRawBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("f_raw_book_update", subscription, \
|
||||
serializers.FundingCurrencyRawBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_raw_book_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyRawBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __candles_channel_handler(self, subscription: Candles, stream: List[Any]):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("candles_snapshot", subscription, \
|
||||
[ serializers.Candle.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"candles_snapshot",
|
||||
subscription,
|
||||
[serializers.Candle.parse(*sub_stream) for sub_stream in stream[0]],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("candles_update", subscription, \
|
||||
serializers.Candle.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"candles_update", subscription, serializers.Candle.parse(*stream[0])
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __status_channel_handler(self, subscription: Status, stream: List[Any]):
|
||||
if subscription["key"].startswith("deriv:"):
|
||||
return self.__event_emitter.emit("derivatives_status_update", subscription, \
|
||||
serializers.DerivativesStatus.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"derivatives_status_update",
|
||||
subscription,
|
||||
serializers.DerivativesStatus.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["key"].startswith("liq:"):
|
||||
return self.__event_emitter.emit("liquidation_feed_update", subscription, \
|
||||
serializers.Liquidation.parse(*stream[0][0]))
|
||||
return self.__event_emitter.emit(
|
||||
"liquidation_feed_update",
|
||||
subscription,
|
||||
serializers.Liquidation.parse(*stream[0][0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __checksum_handler(self, subscription: Book, value: int):
|
||||
return self.__event_emitter.emit( \
|
||||
"checksum", subscription, value & 0xFFFFFFFF)
|
||||
return self.__event_emitter.emit("checksum", subscription, value & 0xFFFFFFFF)
|
||||
|
||||
@@ -4,23 +4,30 @@ from bfxapi.exceptions import BfxBaseException
|
||||
class ConnectionNotOpen(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class ActionRequiresAuthentication(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class ReconnectionTimeoutError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class VersionMismatchError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class SubIdError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownChannelError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownEventError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSubscriptionError(BfxBaseException):
|
||||
pass
|
||||
|
||||
@@ -4,16 +4,19 @@ Subscription = Union["Ticker", "Trades", "Book", "Candles", "Status"]
|
||||
|
||||
Channel = Literal["ticker", "trades", "book", "candles", "status"]
|
||||
|
||||
|
||||
class Ticker(TypedDict):
|
||||
channel: Literal["ticker"]
|
||||
sub_id: str
|
||||
symbol: str
|
||||
|
||||
|
||||
class Trades(TypedDict):
|
||||
channel: Literal["trades"]
|
||||
sub_id: str
|
||||
symbol: str
|
||||
|
||||
|
||||
class Book(TypedDict):
|
||||
channel: Literal["book"]
|
||||
sub_id: str
|
||||
@@ -22,11 +25,13 @@ class Book(TypedDict):
|
||||
freq: Literal["F0", "F1"]
|
||||
len: Literal["1", "25", "100", "250"]
|
||||
|
||||
|
||||
class Candles(TypedDict):
|
||||
channel: Literal["candles"]
|
||||
sub_id: str
|
||||
key: str
|
||||
|
||||
|
||||
class Status(TypedDict):
|
||||
channel: Literal["status"]
|
||||
sub_id: str
|
||||
|
||||
Reference in New Issue
Block a user