diff --git a/bfxapi/rest/BfxRestInterface.py b/bfxapi/rest/BfxRestInterface.py index c11f7af..4994bad 100644 --- a/bfxapi/rest/BfxRestInterface.py +++ b/bfxapi/rest/BfxRestInterface.py @@ -340,4 +340,7 @@ class _RestAuthenticatedEndpoints(_Requests): "flags": flags } - return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/submit", data=data)) \ No newline at end of file + return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/submit", data=data)) + + def cancel_funding_offer(self, id: int) -> Notification: + return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/cancel", data={ "id": id })) \ No newline at end of file diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index 7cd8728..c5b2f31 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -1,12 +1,10 @@ import traceback, json, asyncio, hmac, hashlib, time, uuid, websockets -from typing import Tuple, Union, Literal, TypeVar, Callable, cast - -from enum import Enum +from typing import Literal, TypeVar, Callable, cast from pyee.asyncio import AsyncIOEventEmitter -from .typings import Inputs +from ._BfxWebsocketInputs import _BfxWebsocketInputs from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHandler from .exceptions import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion @@ -230,29 +228,4 @@ class _BfxWebsocketBucket(object): @_require_websocket_connection async def _close(self, code=1000, reason=str()): - await self.websocket.close(code=code, reason=reason) - -class _BfxWebsocketInputs(object): - def __init__(self, __handle_websocket_input): - self.__handle_websocket_input = __handle_websocket_input - - async def order_new(self, data: Inputs.Order.New): - await self.__handle_websocket_input("on", data) - - async def order_update(self, data: Inputs.Order.Update): - await self.__handle_websocket_input("ou", data) - - async def order_cancel(self, data: Inputs.Order.Cancel): - await self.__handle_websocket_input("oc", data) - - async def order_multiple_operations(self, *args: Tuple[str, Union[Inputs.Order.New, Inputs.Order.Update, Inputs.Order.Cancel]]): - await self.__handle_websocket_input("ox_multi", args) - - async def offer_new(self, data: Inputs.Offer.New): - await self.__handle_websocket_input("fon", data) - - async def offer_cancel(self, data: Inputs.Offer.Cancel): - await self.__handle_websocket_input("foc", data) - - async def calc(self, *args: str): - await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args))) \ No newline at end of file + await self.websocket.close(code=code, reason=reason) \ No newline at end of file diff --git a/bfxapi/websocket/_BfxWebsocketInputs.py b/bfxapi/websocket/_BfxWebsocketInputs.py new file mode 100644 index 0000000..fda6e19 --- /dev/null +++ b/bfxapi/websocket/_BfxWebsocketInputs.py @@ -0,0 +1,78 @@ +from decimal import Decimal +from datetime import datetime + +from typing import Union, Optional, List, Tuple +from .typings import JSON +from .enums import OrderType, FundingOfferType + +def _strip(dictionary): + return { key: value for key, value in dictionary.items() if value != None} + +class _BfxWebsocketInputs(object): + def __init__(self, __handle_websocket_input): + self.__handle_websocket_input = __handle_websocket_input + + async def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str], + price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None, + price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None, + gid: Optional[int] = None, cid: Optional[int] = None, + flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None): + data = _strip({ + "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, "meta": meta + }) + + await self.__handle_websocket_input("on", data) + + async def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, str]] = None, + cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None, + flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, str]] = None, + price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None): + data = _strip({ + "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 + }) + + await self.__handle_websocket_input("ou", data) + + async def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None): + data = _strip({ + "id": id, + "cid": cid, + "cid_date": cid_date + }) + + await self.__handle_websocket_input("oc", data) + + async def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False): + data = _strip({ + "ids": ids, + "cids": cids, + "gids": gids, + + "all": int(all) + }) + + await self.__handle_websocket_input("oc_multi", data) + + async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str], + rate: Union[Decimal, str], period: int, + flags: Optional[int] = 0): + data = { + "type": type, "symbol": symbol, "amount": amount, + "rate": rate, "period": period, + "flags": flags + } + + await self.__handle_websocket_input("fon", data) + + async def cancel_funding_offer(self, id: int): + await self.__handle_websocket_input("foc", { "id": id }) + + async def calc(self, *args: str): + await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args))) \ No newline at end of file diff --git a/bfxapi/websocket/handlers.py b/bfxapi/websocket/handlers.py index 9edd986..83c1408 100644 --- a/bfxapi/websocket/handlers.py +++ b/bfxapi/websocket/handlers.py @@ -146,7 +146,13 @@ class AuthenticatedChannelsHandler(object): ("bu",): serializers.BalanceInfo } - EVENTS = [ "notification", *list(__abbreviations.values()) ] + EVENTS = [ + "notification", + "on-req-notification", "ou-req-notification", "oc-req-notification", + "oc_multi-notification", + "fon-req-notification", "foc-req-notification", + *list(__abbreviations.values()) + ] def __init__(self, event_emitter, strict = False): self.event_emitter, self.strict = event_emitter, strict @@ -168,4 +174,13 @@ class AuthenticatedChannelsHandler(object): raise BfxWebsocketException(f"Event of type <{type}> not found in self.__handlers.") def __notification(self, stream): - return self.event_emitter.emit("notification", serializers.Notification.parse(*stream)) \ No newline at end of file + if stream[1] == "on-req" or stream[1] == "ou-req" or stream[1] == "oc-req": + return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order).parse(*stream)) + + if stream[1] == "oc_multi-req": + return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order, iterate=True).parse(*stream)) + + if stream[1] == "fon-req" or stream[1] == "foc-req": + return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.FundingOffer).parse(*stream)) + + return self.event_emitter.emit("notification", serializers._Notification(serializer=None).parse(*stream)) \ No newline at end of file diff --git a/bfxapi/websocket/serializers.py b/bfxapi/websocket/serializers.py index 00f43d2..a9dd805 100644 --- a/bfxapi/websocket/serializers.py +++ b/bfxapi/websocket/serializers.py @@ -2,6 +2,8 @@ from . import typings from .. labeler import _Serializer +from .. notification import _Notification + #region Serializers definition for Websocket Public Channels TradingPairTicker = _Serializer[typings.TradingPairTicker]("TradingPairTicker", labels=[ @@ -292,19 +294,4 @@ BalanceInfo = _Serializer[typings.BalanceInfo]("BalanceInfo", labels=[ "AUM_NET", ]) -#endregion - -#region Serializers definition for Notifications channel - -Notification = _Serializer[typings.Notification]("Notification", labels=[ - "MTS", - "TYPE", - "MESSAGE_ID", - "_PLACEHOLDER", - "NOTIFY_INFO", - "CODE", - "STATUS", - "TEXT" -]) - #endregion \ No newline at end of file diff --git a/bfxapi/websocket/typings.py b/bfxapi/websocket/typings.py index ee55dd3..ced6aaa 100644 --- a/bfxapi/websocket/typings.py +++ b/bfxapi/websocket/typings.py @@ -1,10 +1,6 @@ -from decimal import Decimal - -from datetime import datetime - from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any -from ..utils.integers import Int16, Int32, Int45, Int64 +from .. notification import Notification JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]] @@ -278,69 +274,4 @@ class BalanceInfo(TypedDict): AUM: float AUM_NET: float -#endregion - -#region Type hinting for Notifications channel - -class Notification(TypedDict): - MTS: int - TYPE: str - MESSAGE_ID: int - NOTIFY_INFO: JSON - CODE: int - STATUS: str - TEXT: str - -#endregion - -#region Type hinting for Websocket Authenticated Inputs - -class Inputs: - class Order: - class New(TypedDict, total=False): - gid: Union[Int32, int] - cid: Union[Int45, int] - type: str - symbol: str - amount: Union[Decimal, str] - price: Union[Decimal, str] - lev: Union[Int32, int] - price_trailing: Union[Decimal, str] - price_aux_limit: Union[Decimal, str] - price_oco_stop: Union[Decimal, str] - flags: Union[Int16, int] - tif: Union[datetime, str] - meta: JSON - - class Update(TypedDict, total=False): - id: Union[Int64, int] - cid: Union[Int45, int] - cid_date: str - gid: Union[Int32, int] - price: Union[Decimal, str] - amount: Union[Decimal, str] - lev: Union[Int32, int] - delta: Union[Decimal, str] - price_aux_limit: Union[Decimal, str] - price_trailing: Union[Decimal, str] - flags: Union[Int16, int] - tif: Union[datetime, str] - - class Cancel(TypedDict, total=False): - id: Union[Int64, int] - cid: Union[Int45, int] - cid_date: Union[datetime, str] - - class Offer: - class New(TypedDict, total=False): - type: str - symbol: str - amount: Union[Decimal, str] - rate: Union[Decimal, str] - period: Union[Int32, int] - flags: Union[Int16, int] - - class Cancel(TypedDict, total=False): - id: Union[Int32, int] - #endregion \ No newline at end of file diff --git a/examples/rest/create_funding_offer.py b/examples/rest/create_funding_offer.py index ecd470b..2be1e5a 100644 --- a/examples/rest/create_funding_offer.py +++ b/examples/rest/create_funding_offer.py @@ -21,6 +21,6 @@ notification = bfx.rest.auth.submit_funding_offer( print("Offer notification:", notification) -offers = bfx.rest.auth.get_active_funding_offers() +offers = bfx.rest.auth.get_active_funding_offers(symbol="fUSD") print("Offers:", offers) \ No newline at end of file diff --git a/examples/websocket/create_order.py b/examples/websocket/create_order.py index 36cd5c2..b3146e9 100644 --- a/examples/websocket/create_order.py +++ b/examples/websocket/create_order.py @@ -3,9 +3,8 @@ import os from bfxapi.client import Client, Constants -from bfxapi.utils.cid import generate_unique_cid from bfxapi.websocket.enums import Error, OrderType -from bfxapi.websocket.typings import Inputs +from bfxapi.websocket.typings import Notification, Order bfx = Client( WSS_HOST=Constants.WSS_HOST, @@ -18,30 +17,28 @@ def on_wss_error(code: Error, msg: str): print(code, msg) @bfx.wss.on("authenticated") -async def on_open(event): - print(f"Auth event {event}") +async def on_authenticated(event): + print(f"Authentication: {event}.") - order: Inputs.Order.New = { - "gid": generate_unique_cid(), - "type": OrderType.EXCHANGE_LIMIT, - "symbol": "tBTCUST", - "amount": "0.1", - "price": "10000.0" - } - await bfx.wss.inputs.order_new(order) + await bfx.wss.inputs.submit_order( + type=OrderType.EXCHANGE_LIMIT, + symbol="tBTCUSD", + amount="0.1", + price="10000.0" + ) - print(f"Order sent") + print("The order has been sent.") -@bfx.wss.on("notification") -async def on_notification(notification): - print(f"Notification {notification}") +@bfx.wss.on("on-req-notification") +async def on_notification(notification: Notification): + print(f"Notification: {notification}.") @bfx.wss.on("order_new") -async def on_order_new(order_new: Inputs.Order.New): - print(f"Order new {order_new}") +async def on_order_new(order_new: Order): + print(f"Order new: {order_new}") @bfx.wss.on("subscribed") def on_subscribed(subscription): - print(f"Subscription successful <{subscription}>") + print(f"Subscription successful for <{subscription}>.") bfx.wss.run() \ No newline at end of file