From e37474cafd8ec00927e983302d18e786274be9e2 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 11:14:00 +0100 Subject: [PATCH 01/12] models: add notification object --- bfxapi/models/__init__.py | 1 + bfxapi/models/notification.py | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 bfxapi/models/notification.py diff --git a/bfxapi/models/__init__.py b/bfxapi/models/__init__.py index e1ec275..13fc713 100644 --- a/bfxapi/models/__init__.py +++ b/bfxapi/models/__init__.py @@ -12,5 +12,6 @@ from .position import Position from .funding_loan import FundingLoan from .funding_offer import FundingOffer from .funding_credit import FundingCredit +from .notification import Notification NAME = 'models' diff --git a/bfxapi/models/notification.py b/bfxapi/models/notification.py new file mode 100644 index 0000000..9d65ad2 --- /dev/null +++ b/bfxapi/models/notification.py @@ -0,0 +1,103 @@ +""" +Module used to describe all of the different notification data types +""" + +from . import Order + +class NotificationModal: + """ + Enum used index the different values in a raw order array + """ + MTS = 0 + TYPE = 1 + MESSAGE_ID = 2 + NOTIFY_INFO = 4 + CODE = 5 + STATUS = 6 + TEXT = 7 + +class NotificationError: + """ + Enum used to hold the error response statuses + """ + SUCCESS = "SUCCESS" + ERROR = "ERROR" + FAILURE = "FAILURE" + +class NotificationTypes: + """ + Enum used to hold the different notification types + """ + ORDER_NEW_REQ = "on-req" + ORDER_CANCELED_REQ = "oc-req" + ORDER_UPDATED_REQ = "ou-req" + FUNDING_OFFER_NEW = "fon-req" + FUNDING_OFFER_CANCEL = "foc-req" + # uca ? + # acc_tf ? + # pm-req ? + + +class Notification: + """ + MTS int Millisecond Time Stamp of the update + TYPE string Purpose of notification ('on-req', 'oc-req', 'uca', 'fon-req', 'foc-req') + MESSAGE_ID int unique ID of the message + NOTIFY_INFO array/object A message containing information regarding the notification + CODE null or integer Work in progress + STATUS string Status of the notification; it may vary over time (SUCCESS, ERROR, FAILURE, ...) + TEXT string Text of the notification + """ + + def __init__(self, mts, notify_type, message_id, notify_info, code, status, text): + self.mts = mts + self.notify_type = notify_type + self.message_id = message_id + self.notify_info = notify_info + self.code = code + self.status = status + self.text = text + + def is_success(self): + """ + Check if the notification status was a success. + + @return bool: True if is success else False + """ + if self.status == NotificationError.SUCCESS: + return True + return False + + @staticmethod + def from_raw_order(raw_notification): + """ + Parse a raw order object into an Order object + + @return Order + """ + mts = raw_notification[NotificationModal.MTS] + notify_type = raw_notification[NotificationModal.TYPE] + message_id = raw_notification[NotificationModal.MESSAGE_ID] + notify_info = raw_notification[NotificationModal.NOTIFY_INFO] + code = raw_notification[NotificationModal.CODE] + status = raw_notification[NotificationModal.STATUS] + text = raw_notification[NotificationModal.TEXT] + + basic = Notification(mts, notify_type, message_id, notify_info, code, + status, text) + # if failure notification then just return as is + if not basic.is_success(): + return basic + # parse additional notification data + if basic.notify_type == NotificationTypes.ORDER_NEW_REQ: + basic.notify_info = Order.from_raw_order_snapshot(basic.notify_info) + elif basic.notify_type == NotificationTypes.ORDER_CANCELED_REQ: + basic.notify_info = Order.from_raw_order(basic.notify_info) + elif basic.notify_type == NotificationTypes.ORDER_UPDATED_REQ: + basic.notify_info = Order.from_raw_order(basic.notify_info) + return basic + + def __str__(self): + ''' Allow us to print the Notification object in a pretty format ''' + text = "Notification <'{}' ({}) - {} notify_info={}>" + return text.format(self.notify_type, self.status, self.text, self.notify_info) From f6837452fb41e1c5fb3ad0d647713ccce75ba68d Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 11:15:06 +0100 Subject: [PATCH 02/12] bfxapi: add rest order endpoints --- bfxapi/models/order.py | 19 ++++- bfxapi/rest/bfx_rest.py | 133 +++++++++++++++++++++-------- bfxapi/utils/auth.py | 13 +++ bfxapi/websockets/order_manager.py | 22 ++--- 4 files changed, 134 insertions(+), 53 deletions(-) diff --git a/bfxapi/models/order.py b/bfxapi/models/order.py index f209bb8..7afb347 100644 --- a/bfxapi/models/order.py +++ b/bfxapi/models/order.py @@ -1,5 +1,5 @@ """ -Module used to describe all of the different data types +Module used to describe all of the different order data types """ import time @@ -38,8 +38,7 @@ class OrderSide: class OrderClosedModel: """ - Enum used ad an index match to locate the different values in a - raw order array + Enum used index the different values in a raw order array """ ID = 0 GID = 1 @@ -151,7 +150,7 @@ class Order: @staticmethod def from_raw_order(raw_order): """ - Parse a raw order object into an Order oject + Parse a raw order object into an Order object @return Order """ @@ -178,6 +177,18 @@ class Order: amount_orig, o_type, type_prev, flags, status, price, price_avg, price_trailing, price_aux_limit, notfiy, place_id) + @staticmethod + def from_raw_order_snapshot(raw_order_snapshot): + """ + Parse a raw order snapshot array into an array of order objects + + @return Orders: array of order objects + """ + parsed_orders = [] + for raw_order in raw_order_snapshot: + parsed_orders += [Order.from_raw_order(raw_order)] + return parsed_orders + def set_confirmed(self): """ Set the state of the order to be confirmed diff --git a/bfxapi/rest/bfx_rest.py b/bfxapi/rest/bfx_rest.py index c60859b..3c37b54 100644 --- a/bfxapi/rest/bfx_rest.py +++ b/bfxapi/rest/bfx_rest.py @@ -8,9 +8,9 @@ import time import json from ..utils.custom_logger import CustomLogger -from ..utils.auth import generate_auth_headers +from ..utils.auth import generate_auth_headers, calculate_order_flags, gen_unique_cid from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer -from ..models import FundingCredit +from ..models import FundingCredit, Notification class BfxRest: @@ -61,7 +61,7 @@ class BfxRest: async with aiohttp.ClientSession() as session: async with session.post(url + params, headers=headers, data=sData) as resp: text = await resp.text() - if resp.status is not 200: + if resp.status < 200 or resp.status > 299: raise Exception('POST {} failed with status {} - {}' .format(url, resp.status, text)) parsed = json.loads(text, parse_float=self.parse_float) @@ -355,44 +355,109 @@ class BfxRest: # Orders # ################################################## - async def __submit_order(self, symbol, amount, price, oType=Order.Type.LIMIT, - is_hidden=False, is_postonly=False, use_all_available=False, - stop_order=False, stop_buy_price=0, stop_sell_price=0): + async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, + hidden=False, price_trailing=None, price_aux_limit=None, + oco_stop_price=None, close=False, reduce_only=False, + post_only=False, oco=False, time_in_force=None, gid=None): """ Submit a new order + @param gid: assign the order to a group identifier @param symbol: the name of the symbol i.e 'tBTCUSD + @param price: the price you want to buy/sell at (must be positive) @param amount: order size: how much you want to buy/sell, a negative amount indicates a sell order and positive a buy order - @param price: the price you want to buy/sell at (must be positive) - @param oType: order type, see Order.Type enum - @param is_hidden: True if order should be hidden from orderbooks - @param is_postonly: True if should be post only. Only relevant for limit - @param use_all_available: True if order should use entire balance - @param stop_order: True to set an additional STOP OCO order linked to the - current order - @param stop_buy_price: set the OCO stop buy price (requires stop_order True) - @param stop_sell_price: set the OCO stop sell price (requires stop_order True) + @param market_type Order.Type: please see Order.Type enum + amount decimal string Positive for buy, Negative for sell + @param hidden: if True, order should be hidden from orderbooks + @param price_trailing: decimal trailing price + @param price_aux_limit: decimal auxiliary Limit price (only for STOP LIMIT) + @param oco_stop_price: set the oco stop price (requires oco = True) + @param close: if True, close position if position present + @param reduce_only: if True, ensures that the executed order does not flip the opened position + @param post_only: if True, ensures the limit order will be added to the order book and not + match with a pre-existing order + @param oco: cancels other order option allows you to place a pair of orders stipulating + that if one order is executed fully or partially, then the other is automatically canceled + @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 """ - raise NotImplementedError( - "V2 submit order has not yet been added to the bfx api. Please use bfxapi.ws") - side = Order.Side.SELL if amount < 0 else Order.Side.BUY - use_all_balance = 1 if use_all_available else 0 - payload = {} - payload['symbol'] = symbol - payload['amount'] = abs(amount) - payload['price'] = price - payload['side'] = side - payload['type'] = oType - payload['is_hidden'] = is_hidden - payload['is_postonly'] = is_postonly - payload['use_all_available'] = use_all_balance - payload['ocoorder'] = stop_order - if stop_order: - payload['buy_price_oco'] = stop_buy_price - payload['sell_price_oco'] = stop_sell_price - endpoint = 'order/new' - return await self.post(endpoint, data=payload) + cid = gen_unique_cid() + payload = { + "cid": cid, + "type": str(market_type), + "symbol": symbol, + "amount": str(amount), + "price": str(price), + } + # calculate and add flags + flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco) + payload['flags'] = flags + # add extra parameters + if (price_trailing): + payload['price_trailing'] = price_trailing + if (price_aux_limit): + payload['price_aux_limit'] = price_aux_limit + if (oco_stop_price): + payload['price_oco_stop'] = str(oco_stop_price) + if (time_in_force): + payload['tif'] = time_in_force + if (gid): + payload['gid'] = gid + endpoint = "auth/w/order/submit" + raw_notification = await self.post(endpoint, payload) + return Notification.from_raw_order(raw_notification) + + async def submit_cancel_order(self, orderId): + """ + Cancel an existing open order + + @param orderId: the id of the order that you want to update + """ + endpoint = "auth/w/order/cancel" + raw_notification = await self.post(endpoint, { 'id': orderId }) + return Notification.from_raw_order(raw_notification) + + async def submit_update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, + price_trailing=None, hidden=False, close=False, reduce_only=False, + post_only=False, time_in_force=None): + """ + Update an existing order + + @param orderId: the id of the order that you want to update + @param price: the price you want to buy/sell at (must be positive) + @param amount: order size: how much you want to buy/sell, + a negative amount indicates a sell order and positive a buy order + @param delta: change of amount + @param price_trailing: decimal trailing price + @param price_aux_limit: decimal auxiliary Limit price (only for STOP LIMIT) + @param hidden: if True, order should be hidden from orderbooks + @param close: if True, close position if position present + @param reduce_only: if True, ensures that the executed order does not flip the opened position + @param post_only: if True, ensures the limit order will be added to the order book and not + match with a pre-existing order + @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + """ + payload = {"id": orderId} + if price is not None: + payload['price'] = str(price) + if amount is not None: + payload['amount'] = str(amount) + if delta is not None: + payload['delta'] = str(delta) + if price_aux_limit is not None: + payload['price_aux_limit'] = str(price_aux_limit) + if price_trailing is not None: + payload['price_trailing'] = str(price_trailing) + if time_in_force is not None: + payload['time_in_force'] = str(time_in_force) + flags = calculate_order_flags( + hidden, close, reduce_only, post_only, False) + payload['flags'] = flags + endpoint = "auth/w/order/update" + raw_notification = await self.post(endpoint, payload) + print (raw_notification) + return Notification.from_raw_order(raw_notification) + ################################################## # Derivatives # diff --git a/bfxapi/utils/auth.py b/bfxapi/utils/auth.py index 31006ed..097c88e 100644 --- a/bfxapi/utils/auth.py +++ b/bfxapi/utils/auth.py @@ -6,6 +6,7 @@ to handle the http authentication of the client import hashlib import hmac import time +from ..models import Order def generate_auth_payload(API_KEY, API_SECRET): """ @@ -48,3 +49,15 @@ def _gen_signature(API_KEY, API_SECRET, nonce): def _gen_nonce(): return int(round(time.time() * 1000000)) + +def gen_unique_cid(): + return int(round(time.time() * 1000)) + +def calculate_order_flags(hidden, close, reduce_only, post_only, oco): + flags = 0 + flags = flags + Order.Flags.HIDDEN if hidden else flags + flags = flags + Order.Flags.CLOSE if close else flags + flags = flags + Order.Flags.REDUCE_ONLY if reduce_only else flags + flags = flags + Order.Flags.POST_ONLY if post_only else flags + flags = flags + Order.Flags.OCO if oco else flags + return flags diff --git a/bfxapi/websockets/order_manager.py b/bfxapi/websockets/order_manager.py index bb478ae..4154733 100644 --- a/bfxapi/websockets/order_manager.py +++ b/bfxapi/websockets/order_manager.py @@ -7,6 +7,7 @@ import asyncio from ..utils.custom_logger import CustomLogger from ..models import Order +from ..utils.auth import calculate_order_flags, gen_unique_cid class OrderManager: @@ -83,9 +84,6 @@ class OrderManager: self.logger.info("Order new: {}".format(order)) self.bfxapi._emit('order_new', order) - def _gen_unqiue_cid(self): - return int(round(time.time() * 1000)) - async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, hidden=False, price_trailing=None, price_aux_limit=None, oco_stop_price=None, close=False, reduce_only=False, @@ -94,7 +92,7 @@ class OrderManager: """ Submit a new order - @param gid: assign the order to a group identitfier + @param gid: assign the order to a group identifier @param symbol: the name of the symbol i.e 'tBTCUSD @param price: the price you want to buy/sell at (must be positive) @param amount: order size: how much you want to buy/sell, @@ -118,7 +116,7 @@ class OrderManager: @param onClose: function called when the bitfinex websocket receives signal that the order was closed due to being filled or cancelled """ - cid = self._gen_unqiue_cid() + cid = self._gen_unique_cid() # create base payload with required data payload = { "cid": cid, @@ -128,7 +126,7 @@ class OrderManager: "price": str(price), } # caclulate and add flags - flags = self._calculate_flags(hidden, close, reduce_only, post_only, oco) + flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco) payload['flags'] = flags # add extra parameters if (price_trailing): @@ -187,7 +185,7 @@ class OrderManager: payload['price_trailing'] = str(price_trailing) if time_in_force is not None: payload['time_in_force'] = str(time_in_force) - flags = self._calculate_flags( + flags = calculate_order_flags( hidden, close, reduce_only, post_only, False) payload['flags'] = flags await self.bfxapi._send_auth_command('ou', payload) @@ -261,11 +259,5 @@ class OrderManager: del callback_storage[key] await asyncio.gather(*tasks) - def _calculate_flags(self, hidden, close, reduce_only, post_only, oco): - flags = 0 - flags = flags + Order.Flags.HIDDEN if hidden else flags - flags = flags + Order.Flags.CLOSE if close else flags - flags = flags + Order.Flags.REDUUCE_ONLY if reduce_only else flags - flags = flags + Order.Flags.POST_ONLY if post_only else flags - flags = flags + Order.Flags.OCO if oco else flags - return flags + def _gen_unique_cid(self): + return gen_unique_cid() From 9a84dd71148309e8c4c42a411a29675199ec4d01 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 11:15:53 +0100 Subject: [PATCH 03/12] examples: add rest create order example --- bfxapi/examples/rest/create_order.py | 43 ++++++++++++++++++++++++++++ bfxapi/examples/ws/cancel_order.py | 10 +++---- bfxapi/tests/test_ws_orders.py | 12 ++++---- 3 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 bfxapi/examples/rest/create_order.py diff --git a/bfxapi/examples/rest/create_order.py b/bfxapi/examples/rest/create_order.py new file mode 100644 index 0000000..ed0a927 --- /dev/null +++ b/bfxapi/examples/rest/create_order.py @@ -0,0 +1,43 @@ +import os +import sys +import asyncio +import time +sys.path.append('../') + +from bfxapi import Client + +API_KEY=os.getenv("BFX_KEY") +API_SECRET=os.getenv("BFX_SECRET") + +bfx = Client( + API_KEY=API_KEY, + API_SECRET=API_SECRET, + logLevel='DEBUG' +) + +async def create_order(): + response = await bfx.rest.submit_order("tBTCUSD", 10, 0.1) + # response is in the form of a Notification object + for o in response.notify_info: + # each item is in the form of an Order object + print ("Order: ", o) + +async def cancel_order(): + response = await bfx.rest.submit_cancel_order(1185510865) + # response is in the form of a Notification object + # notify_info is in the form of an order object + print ("Order: ", response.notify_info) + +async def update_order(): + response = await bfx.rest.submit_update_order(1185510771, price=15, amount=0.055) + # response is in the form of a Notification object + # notify_info is in the form of an order object + print ("Order: ", response.notify_info) + +async def run(): + await create_order() + await cancel_order() + await update_order() + +t = asyncio.ensure_future(run()) +asyncio.get_event_loop().run_until_complete(t) diff --git a/bfxapi/examples/ws/cancel_order.py b/bfxapi/examples/ws/cancel_order.py index 18d0703..2c50b25 100644 --- a/bfxapi/examples/ws/cancel_order.py +++ b/bfxapi/examples/ws/cancel_order.py @@ -10,20 +10,18 @@ API_SECRET=os.getenv("BFX_SECRET") bfx = Client( API_KEY=API_KEY, API_SECRET=API_SECRET, - logLevel='INFO' + logLevel='DEBUG' ) @bfx.ws.on('order_closed') -def order_cancelled(order, trade): +def order_cancelled(order): print ("Order cancelled.") print (order) - print (trade) @bfx.ws.on('order_confirmed') -async def trade_completed(order, trade): +async def trade_completed(order): print ("Order confirmed.") print (order) - print (trade) await bfx.ws.cancel_order(order.id) @bfx.ws.on('error') @@ -32,7 +30,7 @@ def log_error(msg): @bfx.ws.once('authenticated') async def submit_order(auth_message): - # create an inital order a really low price so it stays open + # create an initial order at a really low price so it stays open await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT) bfx.ws.run() diff --git a/bfxapi/tests/test_ws_orders.py b/bfxapi/tests/test_ws_orders.py index ebf5e5b..23ee04e 100644 --- a/bfxapi/tests/test_ws_orders.py +++ b/bfxapi/tests/test_ws_orders.py @@ -122,7 +122,7 @@ async def test_closed_callback_on_submit_order_closed(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onClose=c) await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() @@ -139,7 +139,7 @@ async def test_confirmed_callback_on_submit_order_closed(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c) await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() @@ -155,7 +155,7 @@ async def test_confirmed_callback_on_submit_new_order(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c) await client.ws.publish([0,"on",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262833410,-1,-1,"EXCHANGE LIMIT",None,None,None,0,"ACTIVE",None,None,15980,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() @@ -171,7 +171,7 @@ async def test_confirmed_callback_on_submit_order_update(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.update_order(123, price=100, onConfirm=c) await client.ws.publish([0,"ou",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262846964,-0.5,-1,"EXCHANGE LIMIT",None,None,None,0,"PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() @@ -187,7 +187,7 @@ async def test_confirmed_callback_on_submit_cancel_order(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.cancel_order(123, onConfirm=c) await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() @@ -203,7 +203,7 @@ async def test_confirmed_callback_on_submit_cancel_group_order(): client.ws._emit('c1', order) callback_wait = EventWatcher.watch(client.ws, 'c1') # override cid generation - client.ws.orderManager._gen_unqiue_cid = lambda: 123 + client.ws.orderManager._gen_unique_cid = lambda: 123 await client.ws.cancel_order_group(123, onConfirm=c) await client.ws.publish([0,"oc",[1548262833910,123,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]]) callback_wait.wait_until_complete() From dfabb438dead3169bb5998528242d399d0bdbd21 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 11:34:29 +0100 Subject: [PATCH 04/12] bfxapi: support leverage for derivatives --- bfxapi/rest/bfx_rest.py | 21 ++++++++++++++------- bfxapi/websockets/order_manager.py | 22 ++++++++++++++-------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/bfxapi/rest/bfx_rest.py b/bfxapi/rest/bfx_rest.py index 3c37b54..7278e27 100644 --- a/bfxapi/rest/bfx_rest.py +++ b/bfxapi/rest/bfx_rest.py @@ -358,7 +358,8 @@ class BfxRest: async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, hidden=False, price_trailing=None, price_aux_limit=None, oco_stop_price=None, close=False, reduce_only=False, - post_only=False, oco=False, time_in_force=None, gid=None): + post_only=False, oco=False, time_in_force=None, leverage=None, + gid=None): """ Submit a new order @@ -380,6 +381,7 @@ class BfxRest: @param oco: cancels other order option allows you to place a pair of orders stipulating that if one order is executed fully or partially, then the other is automatically canceled @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + @param leverage: the amount of leverage to apply to the order as an integer """ cid = gen_unique_cid() payload = { @@ -393,16 +395,18 @@ class BfxRest: flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco) payload['flags'] = flags # add extra parameters - if (price_trailing): + if price_trailing is not None: payload['price_trailing'] = price_trailing - if (price_aux_limit): + if price_aux_limit is not None: payload['price_aux_limit'] = price_aux_limit - if (oco_stop_price): + if oco_stop_price is not None: payload['price_oco_stop'] = str(oco_stop_price) - if (time_in_force): + if time_in_force is not None: payload['tif'] = time_in_force - if (gid): + if gid is not None: payload['gid'] = gid + if leverage is not None: + payload['lev'] = str(leverage) endpoint = "auth/w/order/submit" raw_notification = await self.post(endpoint, payload) return Notification.from_raw_order(raw_notification) @@ -419,7 +423,7 @@ class BfxRest: async def submit_update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, hidden=False, close=False, reduce_only=False, - post_only=False, time_in_force=None): + post_only=False, time_in_force=None, leverage=None): """ Update an existing order @@ -436,6 +440,7 @@ class BfxRest: @param post_only: if True, ensures the limit order will be added to the order book and not match with a pre-existing order @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + @param leverage: the amount of leverage to apply to the order as an integer """ payload = {"id": orderId} if price is not None: @@ -450,6 +455,8 @@ class BfxRest: payload['price_trailing'] = str(price_trailing) if time_in_force is not None: payload['time_in_force'] = str(time_in_force) + if leverage is not None: + payload["lev"] = str(leverage) flags = calculate_order_flags( hidden, close, reduce_only, post_only, False) payload['flags'] = flags diff --git a/bfxapi/websockets/order_manager.py b/bfxapi/websockets/order_manager.py index 4154733..e3cdf1b 100644 --- a/bfxapi/websockets/order_manager.py +++ b/bfxapi/websockets/order_manager.py @@ -87,7 +87,7 @@ class OrderManager: async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, hidden=False, price_trailing=None, price_aux_limit=None, oco_stop_price=None, close=False, reduce_only=False, - post_only=False, oco=False, time_in_force=None, + post_only=False, oco=False, time_in_force=None, leverage=None, onConfirm=None, onClose=None, gid=None, *args, **kwargs): """ Submit a new order @@ -111,6 +111,7 @@ class OrderManager: that if one order is executed fully or partially, then the other is automatically canceled @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + @param leverage: the amount of leverage to apply to the order as an integer @param onConfirm: function called when the bitfinex websocket receives signal that the order was confirmed @param onClose: function called when the bitfinex websocket receives signal that the order @@ -129,16 +130,18 @@ class OrderManager: flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco) payload['flags'] = flags # add extra parameters - if (price_trailing): + if price_trailing is not None: payload['price_trailing'] = price_trailing - if (price_aux_limit): + if price_aux_limit is not None: payload['price_aux_limit'] = price_aux_limit - if (oco_stop_price): + if oco_stop_price is not None: payload['price_oco_stop'] = str(oco_stop_price) - if (time_in_force): + if time_in_force is not None: payload['tif'] = time_in_force - if (gid): + if gid is not None: payload['gid'] = gid + if leverage is not None: + payload['lev'] = str(leverage) # submit the order self.pending_orders[cid] = payload self._create_callback(cid, onConfirm, self.pending_order_confirm_callbacks) @@ -149,7 +152,7 @@ class OrderManager: async def update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, hidden=False, close=False, reduce_only=False, - post_only=False, time_in_force=None, onConfirm=None): + post_only=False, time_in_force=None, leverage=None, onConfirm=None): """ Update an existing order @@ -165,7 +168,8 @@ class OrderManager: @param reduce_only: if True, ensures that the executed order does not flip the opened position @param post_only: if True, ensures the limit order will be added to the order book and not match with a pre-existing order - @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23 + @param leverage: the amount of leverage to apply to the order as an integer @param onConfirm: function called when the bitfinex websocket receives signal that the order was confirmed @param onClose: function called when the bitfinex websocket receives signal that the order @@ -185,6 +189,8 @@ class OrderManager: payload['price_trailing'] = str(price_trailing) if time_in_force is not None: payload['time_in_force'] = str(time_in_force) + if leverage is not None: + payload['lev'] = str(leverage) flags = calculate_order_flags( hidden, close, reduce_only, post_only, False) payload['flags'] = flags From edfd4fc28007103d4271b55e57d34da8fbbd586c Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 12:45:13 +0100 Subject: [PATCH 05/12] bfxapi/rest: add submit/cancel funding --- bfxapi/models/funding_offer.py | 8 ++++++ bfxapi/models/notification.py | 7 ++++- bfxapi/rest/bfx_rest.py | 48 +++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/bfxapi/models/funding_offer.py b/bfxapi/models/funding_offer.py index 16743a7..d848b63 100644 --- a/bfxapi/models/funding_offer.py +++ b/bfxapi/models/funding_offer.py @@ -2,6 +2,12 @@ Module used to describe all of the different data types """ +class FundingOfferTypes: + """ + Enum used to define the different funding offer types + """ + LIMIT = 'LIMIT' + FRR_DELTA = 'FRRDELTAVAR' class FundingOfferModel: """ @@ -41,6 +47,8 @@ class FundingOffer: RENEW int 0 if false, 1 if true """ + Type = FundingOfferTypes() + def __init__(self, fid, symbol, mts_create, mts_updated, amount, amount_orig, f_type, flags, status, rate, period, notify, hidden, renew): # pylint: disable=invalid-name diff --git a/bfxapi/models/notification.py b/bfxapi/models/notification.py index 9d65ad2..91bd6dd 100644 --- a/bfxapi/models/notification.py +++ b/bfxapi/models/notification.py @@ -2,7 +2,8 @@ Module used to describe all of the different notification data types """ -from . import Order +from .order import Order +from .funding_offer import FundingOffer class NotificationModal: """ @@ -95,6 +96,10 @@ class Notification: basic.notify_info = Order.from_raw_order(basic.notify_info) elif basic.notify_type == NotificationTypes.ORDER_UPDATED_REQ: basic.notify_info = Order.from_raw_order(basic.notify_info) + elif basic.notify_type == NotificationTypes.FUNDING_OFFER_NEW: + basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info) + elif basic.notify_type == NotificationTypes.FUNDING_OFFER_CANCEL: + basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info) return basic def __str__(self): diff --git a/bfxapi/rest/bfx_rest.py b/bfxapi/rest/bfx_rest.py index 7278e27..9d66fea 100644 --- a/bfxapi/rest/bfx_rest.py +++ b/bfxapi/rest/bfx_rest.py @@ -351,6 +351,53 @@ class BfxRest: credits = await self.post(endpoint, params=params) return [FundingCredit.from_raw_credit(c) for c in credits] + async def submit_funding_offer(self, symbol, amount, rate, period, + funding_type=FundingOffer.Type.LIMIT, hidden=False): + """ + Submits a new funding offer + + @param symbol string: pair symbol i.e fUSD + @param amount float: funding size + @param rate float: percentage rate to charge per a day + @param period int: number of days for funding to remain active once accepted + """ + payload = { + "type": funding_type, + "symbol": symbol, + "amount": str(amount), + "rate": str(rate), + "period": period, + } + # calculate and add flags + flags = calculate_order_flags(hidden, None, None, None, None) + payload['flags'] = flags + endpoint = "auth/w/funding/offer/submit" + raw_notification = await self.post(endpoint, payload) + return Notification.from_raw_order(raw_notification) + + async def submit_cancel_funding_offer(self, fundingId): + """ + Cancel a funding offer + + @param fundingId int: the id of the funding offer + """ + endpoint = "auth/w/funding/offer/cancel" + raw_notification = await self.post(endpoint, { 'id': fundingId }) + return Notification.from_raw_order(raw_notification) + + # async def submit_close_funding(self, id, type): + # """ + # `/v2/auth/w/funding/close` (params: `id`, `type` (credit|loan)) + # """ + # pass + + # async def submit_auto_funding(self, ): + # """ + # `/v2/auth/w/funding/auto` (params: `status` (1|0), `currency`, `amount`, `rate`, `period`) + # (`rate === 0` means `FRR`) + # """ + # pass + ################################################## # Orders # ################################################## @@ -462,7 +509,6 @@ class BfxRest: payload['flags'] = flags endpoint = "auth/w/order/update" raw_notification = await self.post(endpoint, payload) - print (raw_notification) return Notification.from_raw_order(raw_notification) From a4ae07393fb521aea1bbdaa2e291499167531c42 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 11 Sep 2019 12:45:55 +0100 Subject: [PATCH 06/12] examples/rest: add create funding offer example --- bfxapi/examples/rest/create_funding.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 bfxapi/examples/rest/create_funding.py diff --git a/bfxapi/examples/rest/create_funding.py b/bfxapi/examples/rest/create_funding.py new file mode 100644 index 0000000..55de3b7 --- /dev/null +++ b/bfxapi/examples/rest/create_funding.py @@ -0,0 +1,35 @@ +import os +import sys +import asyncio +import time +sys.path.append('../') + +from bfxapi import Client + +API_KEY=os.getenv("BFX_KEY") +API_SECRET=os.getenv("BFX_SECRET") + +bfx = Client( + API_KEY=API_KEY, + API_SECRET=API_SECRET, + logLevel='DEBUG' +) + +async def create_funding(): + response = await bfx.rest.submit_funding_offer("fUSD", 1000, 0.012, 7) + # response is in the form of a Notification object + # notify_info is in the form of a FundingOffer + print ("Offer: ", response.notify_info) + +async def cancel_funding(): + response = await bfx.rest.submit_cancel_funding_offer(41235958) + # response is in the form of a Notification object + # notify_info is in the form of a FundingOffer + print ("Offer: ", response.notify_info) + +async def run(): + await create_funding() + await cancel_funding() + +t = asyncio.ensure_future(run()) +asyncio.get_event_loop().run_until_complete(t) From 8135183edc36ea2ff937350b0bff30a3b1ddd451 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 13:52:32 +0100 Subject: [PATCH 07/12] bfx_rest: add wallet address/withdraw endpoints --- bfxapi/models/__init__.py | 3 ++ bfxapi/models/deposit_address.py | 42 +++++++++++++++++++++ bfxapi/models/notification.py | 19 ++++++++-- bfxapi/models/transfer.py | 53 ++++++++++++++++++++++++++ bfxapi/models/withdraw.py | 51 +++++++++++++++++++++++++ bfxapi/rest/bfx_rest.py | 65 +++++++++++++++++++++++++++++++- 6 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 bfxapi/models/deposit_address.py create mode 100644 bfxapi/models/transfer.py create mode 100644 bfxapi/models/withdraw.py diff --git a/bfxapi/models/__init__.py b/bfxapi/models/__init__.py index 13fc713..4da024e 100644 --- a/bfxapi/models/__init__.py +++ b/bfxapi/models/__init__.py @@ -13,5 +13,8 @@ from .funding_loan import FundingLoan from .funding_offer import FundingOffer from .funding_credit import FundingCredit from .notification import Notification +from .transfer import Transfer +from .deposit_address import DepositAddress +from .withdraw import Withdraw NAME = 'models' diff --git a/bfxapi/models/deposit_address.py b/bfxapi/models/deposit_address.py new file mode 100644 index 0000000..02497de --- /dev/null +++ b/bfxapi/models/deposit_address.py @@ -0,0 +1,42 @@ +""" +Module used to describe a DepositAddress object +""" + +class DepositModel: + """ + Enum used to index the location of each value in a raw array + """ + METHOD = 1 + CURRENCY = 2 + ADDRESS = 4 + +class DepositAddress: + """ + [None, 'BITCOIN', 'BTC', None, '38zsUkv8q2aiXK9qsZVwepXjWeh3jKvvZw'] + + METHOD string Protocol used for funds transfer + SYMBOL string Currency symbol + ADDRESS string Deposit address for funds transfer + """ + + def __init__(self, method, currency, address): + self.method = method + self.currency = currency + self.address = address + + @staticmethod + def from_raw_deposit_address(raw_add): + """ + Parse a raw deposit object into a DepositAddress object + + @return DepositAddress + """ + method = raw_add[DepositModel.METHOD] + currency = raw_add[DepositModel.CURRENCY] + address = raw_add[DepositModel.ADDRESS] + return DepositAddress(method, currency, address) + + def __str__(self): + ''' Allow us to print the Transfer object in a pretty format ''' + text = "DepositAddress <{} method={} currency={}>" + return text.format(self.address, self.method, self.currency) diff --git a/bfxapi/models/notification.py b/bfxapi/models/notification.py index 91bd6dd..84d91b5 100644 --- a/bfxapi/models/notification.py +++ b/bfxapi/models/notification.py @@ -4,6 +4,9 @@ Module used to describe all of the different notification data types from .order import Order from .funding_offer import FundingOffer +from .transfer import Transfer +from .deposit_address import DepositAddress +from .withdraw import Withdraw class NotificationModal: """ @@ -34,8 +37,10 @@ class NotificationTypes: ORDER_UPDATED_REQ = "ou-req" FUNDING_OFFER_NEW = "fon-req" FUNDING_OFFER_CANCEL = "foc-req" + ACCOUNT_TRANSFER = "acc_tf" + ACCOUNT_DEPOSIT = "acc_dep" + ACCOUNT_WITHDRAW_REQ = "acc_wd-req" # uca ? - # acc_tf ? # pm-req ? @@ -70,11 +75,11 @@ class Notification: return False @staticmethod - def from_raw_order(raw_notification): + def from_raw_notification(raw_notification): """ - Parse a raw order object into an Order object + Parse a raw notification object into an Order object - @return Order + @return Notification """ mts = raw_notification[NotificationModal.MTS] notify_type = raw_notification[NotificationModal.TYPE] @@ -100,6 +105,12 @@ class Notification: basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info) elif basic.notify_type == NotificationTypes.FUNDING_OFFER_CANCEL: basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info) + elif basic.notify_type == NotificationTypes.ACCOUNT_TRANSFER: + basic.notify_info = Transfer.from_raw_transfer(basic.notify_info) + elif basic.notify_type == NotificationTypes.ACCOUNT_DEPOSIT: + basic.notify_info = DepositAddress.from_raw_deposit_address(basic.notify_info) + elif basic.notify_type == NotificationTypes.ACCOUNT_WITHDRAW_REQ: + basic.notify_info = Withdraw.from_raw_withdraw(basic.notify_info) return basic def __str__(self): diff --git a/bfxapi/models/transfer.py b/bfxapi/models/transfer.py new file mode 100644 index 0000000..c6db63e --- /dev/null +++ b/bfxapi/models/transfer.py @@ -0,0 +1,53 @@ +""" +Module used to describe a transfer object +""" + +class TransferModel: + """ + Enum used to index the location of each value in a raw array + """ + MTS = 0 + W_FROM = 1 + W_TO = 2 + C_FROM = 4 + C_TO = 5 + AMOUNT = 7 + +class Transfer: + """ + MTS int Millisecond Time Stamp of the update + WALLET_FROM string Wallet name (exchange, margin, funding) + WALLET_TO string Wallet name (exchange, margin, funding) + CURRENCY_FROM string Currency (BTC, etc) + CURRENCY_TO string Currency (BTC, etc) + AMOUNT string Amount of funds to transfer + """ + + def __init__(self, mts, wallet_from, wallet_to, currency_from, currency_to, amount): + self.mts = mts + self.wallet_from = wallet_from + self.wallet_to = wallet_to + self.currency_from = currency_from + self.currency_to = currency_to + self.amount = amount + + @staticmethod + def from_raw_transfer(raw_transfer): + """ + Parse a raw transfer object into a Transfer object + + @return Transfer + """ + mts = raw_transfer[TransferModel.MTS] + wallet_from = raw_transfer[TransferModel.W_FROM] + wallet_to = raw_transfer[TransferModel.W_TO] + currency_from = raw_transfer[TransferModel.C_FROM] + currency_to = raw_transfer[TransferModel.C_TO] + amount = raw_transfer[TransferModel.AMOUNT] + return Transfer(mts, wallet_from, wallet_to, currency_from, currency_to, amount) + + def __str__(self): + ''' Allow us to print the Transfer object in a pretty format ''' + text = "Transfer <{} from {} ({}) to {} ({}) mts={}>" + return text.format(self.amount, self.wallet_from, self.currency_from, + self.wallet_to, self.currency_to, self.mts) diff --git a/bfxapi/models/withdraw.py b/bfxapi/models/withdraw.py new file mode 100644 index 0000000..9412d3f --- /dev/null +++ b/bfxapi/models/withdraw.py @@ -0,0 +1,51 @@ +""" +Module used to describe a withdraw object +""" + +class WithdrawModel: + """ + Enum used to index the location of each value in a raw array + """ + ID = 0 + METHOD = 2 + WALLET = 4 + AMOUNT = 5 + FEE = 7 + +class Withdraw: + """ + [13063236, None, 'tetheruse', None, 'exchange', 5, None, None, 0.00135] + + MTS int Millisecond Time Stamp of the update + WALLET_FROM string Wallet name (exchange, margin, funding) + WALLET_TO string Wallet name (exchange, margin, funding) + CURRENCY_FROM string Currency (BTC, etc) + CURRENCY_TO string Currency (BTC, etc) + AMOUNT string Amount of funds to transfer + """ + + def __init__(self, w_id, method, wallet, amount, fee=0): + self.id = w_id + self.method = method + self.wallet = wallet + self.amount = amount + self.fee = fee + + @staticmethod + def from_raw_withdraw(raw_withdraw): + """ + Parse a raw withdraw object into a Withdraw object + + @return Withdraw + """ + w_id = raw_withdraw[WithdrawModel.ID] + method = raw_withdraw[WithdrawModel.METHOD] + wallet = raw_withdraw[WithdrawModel.WALLET] + amount = raw_withdraw[WithdrawModel.AMOUNT] + return Withdraw(w_id, method, wallet, amount) + + def __str__(self): + ''' Allow us to print the Transfer object in a pretty format ''' + text = "Withdraw " + return text.format(self.id, self.wallet, self.method, self.amount, + self.fee) diff --git a/bfxapi/rest/bfx_rest.py b/bfxapi/rest/bfx_rest.py index 9d66fea..0333208 100644 --- a/bfxapi/rest/bfx_rest.py +++ b/bfxapi/rest/bfx_rest.py @@ -373,7 +373,7 @@ class BfxRest: payload['flags'] = flags endpoint = "auth/w/funding/offer/submit" raw_notification = await self.post(endpoint, payload) - return Notification.from_raw_order(raw_notification) + return Notification.from_raw_notification(raw_notification) async def submit_cancel_funding_offer(self, fundingId): """ @@ -383,7 +383,68 @@ class BfxRest: """ endpoint = "auth/w/funding/offer/cancel" raw_notification = await self.post(endpoint, { 'id': fundingId }) - return Notification.from_raw_order(raw_notification) + return Notification.from_raw_notification(raw_notification) + + async def submit_wallet_transfer(self, from_wallet, to_wallet, from_currency, to_currency, amount): + """ + Transfer funds between wallets + + @param from_wallet string: wallet name to transfer from i.e margin, exchange + @param to_wallet string: wallet name to transfer to i.e margin, exchange + @param from_currency string: currency symbol to tranfer from i.e BTC, USD + @param to_currency string: currency symbol to transfer to i.e BTC, USD + @param amount float: amount of funds to transfer + """ + endpoint = "auth/w/transfer" + payload = { + "from": from_wallet, + "to": to_wallet, + "currency": from_currency, + "currency_to": to_currency, + "amount": str(amount), + } + raw_transfer = await self.post(endpoint, payload) + return Notification.from_raw_notification(raw_transfer) + + async def get_wallet_deposit_address(self, wallet, method, renew=0): + """ + Get the deposit address for the given wallet and protocol + + @param wallet string: wallet name i.e margin, exchange + @param method string: transfer protocol i.e bitcoin + """ + endpoint = "auth/w/deposit/address" + payload = { + "wallet": wallet, + "method": method, + "op_renew": renew, + } + raw_deposit = await self.post(endpoint, payload) + return Notification.from_raw_notification(raw_deposit) + + async def create_wallet_deposit_address(self, wallet, method): + """ + Creates a new deposit address for the given wallet and protocol. + Previously generated addresses remain linked. + + @param wallet string: wallet name i.e margin, exchange + @param method string: transfer protocol i.e bitcoin + """ + return await self.get_wallet_deposit_address(wallet, method, renew=1) + + async def submit_wallet_withdraw(self, wallet, method, amount, address): + """ + `/v2/auth/w/withdraw` (params: `wallet`, `method`, `amount`, `address + """ + endpoint = "auth/w/withdraw" + payload = { + "wallet": wallet, + "method": method, + "amount": str(amount), + "address": str(address) + } + raw_deposit = await self.post(endpoint, payload) + return Notification.from_raw_notification(raw_deposit) # async def submit_close_funding(self, id, type): # """ From 45696f5598d758c78c79a8cee193ee334205a540 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 13:53:36 +0100 Subject: [PATCH 08/12] rest/examples: add wallet transfer examples --- bfxapi/examples/rest/transfer_wallet.py | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 bfxapi/examples/rest/transfer_wallet.py diff --git a/bfxapi/examples/rest/transfer_wallet.py b/bfxapi/examples/rest/transfer_wallet.py new file mode 100644 index 0000000..46bfc41 --- /dev/null +++ b/bfxapi/examples/rest/transfer_wallet.py @@ -0,0 +1,49 @@ +import os +import sys +import asyncio +import time +sys.path.append('../') + +from bfxapi import Client + +API_KEY=os.getenv("BFX_KEY") +API_SECRET=os.getenv("BFX_SECRET") + +bfx = Client( + API_KEY=API_KEY, + API_SECRET=API_SECRET, + logLevel='DEBUG' +) + +async def transfer_wallet(): + response = await bfx.rest.submit_wallet_transfer("exchange", "margin", "BTC", "BTC", 0.1) + # response is in the form of a Notification object + # notify_info is in the form of a Transfer object + print ("Transfer: ", response.notify_info) + +async def deposit_address(): + response = await bfx.rest.get_wallet_deposit_address("exchange", "bitcoin") + # response is in the form of a Notification object + # notify_info is in the form of a DepositAddress object + print ("Address: ", response.notify_info) + +async def create_new_address(): + response = await bfx.rest.create_wallet_deposit_address("exchange", "bitcoin") + # response is in the form of a Notification object + # notify_info is in the form of a DepositAddress object + print ("Address: ", response.notify_info) + +async def withdraw(): + # tetheruse = Tether (ERC20) + response = await bfx.rest.submit_wallet_withdraw("exchange", "tetheruse", 5, "0xc5bbb852f82c24327693937d4012f496cff7eddf") + # response is in the form of a Notification object + # notify_info is in the form of a DepositAddress object + print ("Address: ", response.notify_info) + +async def run(): + await transfer_wallet() + await deposit_address() + await withdraw() + +t = asyncio.ensure_future(run()) +asyncio.get_event_loop().run_until_complete(t) From 25151f50113ba0d4d7be97aa76a7e7633a143081 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 14:20:22 +0100 Subject: [PATCH 09/12] websocket: convert notfication to notification object --- bfxapi/websockets/order_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bfxapi/websockets/order_manager.py b/bfxapi/websockets/order_manager.py index e3cdf1b..90f5a7b 100644 --- a/bfxapi/websockets/order_manager.py +++ b/bfxapi/websockets/order_manager.py @@ -126,7 +126,7 @@ class OrderManager: "amount": str(amount), "price": str(price), } - # caclulate and add flags + # calculate and add flags flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco) payload['flags'] = flags # add extra parameters From 62c5625f385db78e095434a3620f668fed31a205 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 14:34:31 +0100 Subject: [PATCH 10/12] Readme: add new endpoints --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 09c48fe..f84e7b2 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,12 @@ python3 subsribe_trades_candles.py ``` # Features -- Fast websocket connection -- Event based routing -- Subscribe to trade, candles and orderbook channels +- Wbsocket support with multiplexing +- Real-time Trade, Orderbook, Account data feeds and more - Authenticate with api key/secret -- Orderbook checksum validation +- Data checksum verifications - Create, update and close orders -- Track wallet updates +- Wallet withdraws/transfers # Quickstart @@ -105,7 +104,7 @@ The websocket exposes a collection of events that are triggered when certain dat - `disconnected`: () called when a connection is ended (A reconnect attempt may follow) - `stopped`: () called when max amount of connection retries is met and the socket is closed - `authenticated` (): called when the websocket passes authentication -- `notification` (array): incoming account notification +- `notification` (Notification): incoming account notification - `error` (array): error from the websocket - `order_closed` (Order, Trade): when an order has been closed - `order_new` (Order, Trade): when an order has been created but not closed. Note: will not be called if order is executed and filled instantly @@ -166,7 +165,7 @@ The websocket exposes a collection of events that are triggered when certain dat #### `submit_order(symbol, price, amount, market_type, hidden=False, onConfirm=None, onClose=None, *args, **kwargs)` - Submits an order to the Bitfinex api. When it has been verified that bitfine xhas received the order then the `onConfirm` callback will be called followed by the `order_confirmed` event. Once it has been verified that the order has completely closed due to either being filled or canceled then the `onClose` function will be called, followed by the `order_closed` event. + Submits an order to the Bitfinex api. When it has been verified that bitfinex has received the order then the `onConfirm` callback will be called followed by the `order_confirmed` event. Once it has been verified that the order has completely closed due to either being filled or canceled then the `onClose` function will be called, followed by the `order_closed` event. #### `update_order(orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None, onConfirm=None, onClose=None)` @@ -290,6 +289,42 @@ Get the public orderbook of a given symbol Set the amount of collateral used to back a derivative position. +#### `submit_order(symbol, price, amount, market_type, hidden=False, *args, **kwargs)` + + Submits an order to the Bitfinex api. + +#### `update_order(orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None)` + + Attempts to update an order with the given values. If the order is no longer open then the update will be ignored. + +#### `close_order(self, orderId):` + + Close the order with the given orderId if it still open. + +#### `submit_wallet_withdraw(wallet, method, amount, address):` + + Withdraw funds from the given wallet to the provided address + +#### `create_wallet_deposit_address(wallet, method):` + + Create a new deposit address for the given wallet and currency protocol. + +#### `get_wallet_deposit_address(wallet, method):` + + Get the deposit address for the given wallet and currency protocol + +#### `submit_wallet_transfer(from_wallet, to_wallet, from_currency, to_currency, amount):` + + Transfer funds from one internal wallet to another + +#### `submit_cancel_funding_offer(fundingId):` + + Cancel a funding offer + +### `submit_funding_offer(self, symbol, amount, rate, period, funding_type, hidden=False):` + + Submit a new fund offer + # Examples For more info on how to use this library please see the example scripts in the `bfxapi/examples` directory. Here you will find usage of all interface exposed functions for both the rest and websocket. From d9e3ea437f1abd1dd0ef5d83a8c32618e2abb612 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 14:36:30 +0100 Subject: [PATCH 11/12] requirements: update pytest to 0.10.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 322b401..088a526 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ eventemitter==0.2.0 asyncio==3.4.3 websockets==7.0 pylint==2.3.0 -pytest-asyncio==0.9.0 +pytest-asyncio==0.10.0 six==1.12.0 pyee==5.0.0 aiohttp==3.4.4 From de81fc80bd0d0278e90a56c95956c14526c30e8d Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 12 Sep 2019 14:38:50 +0100 Subject: [PATCH 12/12] Bump to version 1.1.0 --- CHANGELOG | 13 +++++++++++++ bfxapi/version.py | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b96f481..3c544c2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,16 @@ +1.1.0 + +- Adds rest.submit_funding_offer +- Adds rest.submit_cancel_funding_offer +- Adds rest.submit_wallet_transfer +- Adds rest.get_wallet_deposit_address +- Adds rest.create_wallet_deposit_address +- Adds rest.submit_wallet_withdraw +- Adds rest.submit_order +- Adds rest.submit_cancel_order +- Adds rest.submit_update_order +- Updates websocket notification event to use Notfication model object + 1.0.1 - Added ws event `status_update` diff --git a/bfxapi/version.py b/bfxapi/version.py index fa1d97e..47357fd 100644 --- a/bfxapi/version.py +++ b/bfxapi/version.py @@ -2,4 +2,4 @@ This module contains the current version of the bfxapi lib """ -__version__ = '1.0.1' +__version__ = '1.1.0' diff --git a/setup.py b/setup.py index 0268470..fbb02b0 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ here = path.abspath(path.dirname(__file__)) setup( name='bitfinex-api-py', - version='1.0.1', # Required + version='1.1.0', # Required description='Official Bitfinex API', # Optional long_description='This is an official python library that is used to connect interact with the Bitfinex api.', # Optional long_description_content_type='text/markdown', # Optional