From 405c1a7b7b9c7597c764891580dcccc595902554 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Mon, 3 Dec 2018 13:01:50 +0000 Subject: [PATCH 01/31] README: specifies language for syntax highlight --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index db8a087..10d9ad2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -``` +```python from bfxapi import Client bfx = Client( @@ -24,11 +24,11 @@ bfx.ws.run() This is an official python library that is used to connect interact with the Bitfinex api. Currently it only has support for websockets but will soon have Rest functionality as well. Install dependencies -``` +```sh pip3 install -r requirements.txt ``` Run the trades/candles example: -``` +```sh cd bfxapi/examples python3 subsribe_trades_candles.py ``` @@ -46,7 +46,7 @@ python3 subsribe_trades_candles.py ### Authenticate -``` +```python bfx = Client( API_KEY='' API_SECRET='' @@ -61,26 +61,26 @@ bfx.ws.run() ### Submit limit order -``` +```python await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT') ``` ### Listen for completion -``` +```python @bfx.ws.on('order_confirmed') async def order_completed(order, trade): print ("Order has been confrmed") ``` ### Get wallets -``` +```python wallets = bfxapi.wallets.get_wallets() # [ Wallet <'exchange_BTC' balance='41.25809589' unsettled='0'>, # Wallet <'exchange_USD' balance='62761.86070104' unsettled='0'> ] ``` ### Close all orders -``` +```python await bfx.ws.close_all_orders() ``` From d683faf459588e5fbd8fc2c1b7730dcac67f5327 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Mon, 3 Dec 2018 14:40:44 +0000 Subject: [PATCH 02/31] examples: seperate ws and rest examples --- bfxapi/examples/{ => ws}/candel_order.py | 76 +++++++-------- bfxapi/examples/{ => ws}/connect.py | 38 ++++---- bfxapi/examples/{ => ws}/get_seed_trades.py | 0 .../{ => ws}/resubscribe_orderbook.py | 0 bfxapi/examples/{ => ws}/send_order.py | 96 +++++++++---------- .../examples/{ => ws}/subscribe_orderbook.py | 0 .../{ => ws}/subscribe_trades_candles.py | 56 +++++------ bfxapi/examples/{ => ws}/update_order.py | 86 ++++++++--------- bfxapi/examples/{ => ws}/wallet_balance.py | 62 ++++++------ 9 files changed, 207 insertions(+), 207 deletions(-) rename bfxapi/examples/{ => ws}/candel_order.py (95%) rename bfxapi/examples/{ => ws}/connect.py (93%) rename bfxapi/examples/{ => ws}/get_seed_trades.py (100%) rename bfxapi/examples/{ => ws}/resubscribe_orderbook.py (100%) rename bfxapi/examples/{ => ws}/send_order.py (95%) rename bfxapi/examples/{ => ws}/subscribe_orderbook.py (100%) rename bfxapi/examples/{ => ws}/subscribe_trades_candles.py (95%) rename bfxapi/examples/{ => ws}/update_order.py (95%) rename bfxapi/examples/{ => ws}/wallet_balance.py (94%) diff --git a/bfxapi/examples/candel_order.py b/bfxapi/examples/ws/candel_order.py similarity index 95% rename from bfxapi/examples/candel_order.py rename to bfxapi/examples/ws/candel_order.py index 6571e25..b13b3af 100644 --- a/bfxapi/examples/candel_order.py +++ b/bfxapi/examples/ws/candel_order.py @@ -1,38 +1,38 @@ -import os -import sys -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='INFO' -) - -@bfx.ws.on('order_closed') -def order_cancelled(order, trade): - print ("Order cancelled.") - print (order) - print (trade) - -@bfx.ws.on('order_confirmed') -async def trade_completed(order, trade): - print ("Order confirmed.") - print (order) - print (trade) - await bfx.ws.cancel_order(order.id) - -@bfx.ws.on('error') -def log_error(msg): - print ("Error: {}".format(msg)) - -@bfx.ws.once('authenticated') -async def submit_order(auth_message): - # create an inital order a really low price so it stays open - await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') - -bfx.ws.run() +import os +import sys +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='INFO' +) + +@bfx.ws.on('order_closed') +def order_cancelled(order, trade): + print ("Order cancelled.") + print (order) + print (trade) + +@bfx.ws.on('order_confirmed') +async def trade_completed(order, trade): + print ("Order confirmed.") + print (order) + print (trade) + await bfx.ws.cancel_order(order.id) + +@bfx.ws.on('error') +def log_error(msg): + print ("Error: {}".format(msg)) + +@bfx.ws.once('authenticated') +async def submit_order(auth_message): + # create an inital order a really low price so it stays open + await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') + +bfx.ws.run() diff --git a/bfxapi/examples/connect.py b/bfxapi/examples/ws/connect.py similarity index 93% rename from bfxapi/examples/connect.py rename to bfxapi/examples/ws/connect.py index c37de49..87d35c5 100644 --- a/bfxapi/examples/connect.py +++ b/bfxapi/examples/ws/connect.py @@ -1,19 +1,19 @@ -import os -import sys -sys.path.append('../') - -from bfxapi import Client - -bfx = Client( - logLevel='DEBUG' -) - -@bfx.ws.on('error') -def log_error(msg): - print ("Error: {}".format(msg)) - -@bfx.ws.on('all') -async def log_output(output): - print ("WS: {}".format(output)) - -bfx.ws.run() +import os +import sys +sys.path.append('../') + +from bfxapi import Client + +bfx = Client( + logLevel='DEBUG' +) + +@bfx.ws.on('error') +def log_error(msg): + print ("Error: {}".format(msg)) + +@bfx.ws.on('all') +async def log_output(output): + print ("WS: {}".format(output)) + +bfx.ws.run() diff --git a/bfxapi/examples/get_seed_trades.py b/bfxapi/examples/ws/get_seed_trades.py similarity index 100% rename from bfxapi/examples/get_seed_trades.py rename to bfxapi/examples/ws/get_seed_trades.py diff --git a/bfxapi/examples/resubscribe_orderbook.py b/bfxapi/examples/ws/resubscribe_orderbook.py similarity index 100% rename from bfxapi/examples/resubscribe_orderbook.py rename to bfxapi/examples/ws/resubscribe_orderbook.py diff --git a/bfxapi/examples/send_order.py b/bfxapi/examples/ws/send_order.py similarity index 95% rename from bfxapi/examples/send_order.py rename to bfxapi/examples/ws/send_order.py index a02d607..2a2bb5a 100644 --- a/bfxapi/examples/send_order.py +++ b/bfxapi/examples/ws/send_order.py @@ -1,48 +1,48 @@ -import os -import sys -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' -) - -@bfx.ws.on('order_snapshot') -async def close_all(data): - await bfx.ws.close_all_orders() - -@bfx.ws.on('order_confirmed') -async def trade_completed(order, trade): - print ("Order confirmed.") - print (order) - print (trade) - ## close the order - # await order.close() - # or - # await bfx.ws.close_order(order.id) - # or - # await bfx.ws.close_all_orders() - -@bfx.ws.on('error') -def log_error(msg): - print ("Error: {}".format(msg)) - -@bfx.ws.on('authenticated') -async def submit_order(auth_message): - await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT') - -# If you dont want to use a decorator -# ws.on('authenticated', submit_order) -# ws.on('error', log_error) - -# You can also provide a callback -# await ws.submit_order('tBTCUSD', 0, 0.01, -# 'EXCHANGE MARKET', onComplete=trade_complete) - -bfx.ws.run() +import os +import sys +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' +) + +@bfx.ws.on('order_snapshot') +async def close_all(data): + await bfx.ws.close_all_orders() + +@bfx.ws.on('order_confirmed') +async def trade_completed(order, trade): + print ("Order confirmed.") + print (order) + print (trade) + ## close the order + # await order.close() + # or + # await bfx.ws.close_order(order.id) + # or + # await bfx.ws.close_all_orders() + +@bfx.ws.on('error') +def log_error(msg): + print ("Error: {}".format(msg)) + +@bfx.ws.on('authenticated') +async def submit_order(auth_message): + await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT') + +# If you dont want to use a decorator +# ws.on('authenticated', submit_order) +# ws.on('error', log_error) + +# You can also provide a callback +# await ws.submit_order('tBTCUSD', 0, 0.01, +# 'EXCHANGE MARKET', onComplete=trade_complete) + +bfx.ws.run() diff --git a/bfxapi/examples/subscribe_orderbook.py b/bfxapi/examples/ws/subscribe_orderbook.py similarity index 100% rename from bfxapi/examples/subscribe_orderbook.py rename to bfxapi/examples/ws/subscribe_orderbook.py diff --git a/bfxapi/examples/subscribe_trades_candles.py b/bfxapi/examples/ws/subscribe_trades_candles.py similarity index 95% rename from bfxapi/examples/subscribe_trades_candles.py rename to bfxapi/examples/ws/subscribe_trades_candles.py index 1b1dd27..27530ed 100644 --- a/bfxapi/examples/subscribe_trades_candles.py +++ b/bfxapi/examples/ws/subscribe_trades_candles.py @@ -1,28 +1,28 @@ -import os -import sys -sys.path.append('../') - -from bfxapi import Client - -bfx = Client( - logLevel='DEBUG' -) - -@bfx.ws.on('error') -def log_error(err): - print ("Error: {}".format(err)) - -@bfx.ws.on('new_candle') -def log_candle(candle): - print ("New candle: {}".format(candle)) - -@bfx.ws.on('new_trade') -def log_trade(trade): - print ("New trade: {}".format(trade)) - -async def start(): - await bfx.ws.subscribe('candles', 'tBTCUSD', timeframe='1m') - await bfx.ws.subscribe('trades', 'tBTCUSD') - -bfx.ws.on('connected', start) -bfx.ws.run() +import os +import sys +sys.path.append('../') + +from bfxapi import Client + +bfx = Client( + logLevel='DEBUG' +) + +@bfx.ws.on('error') +def log_error(err): + print ("Error: {}".format(err)) + +@bfx.ws.on('new_candle') +def log_candle(candle): + print ("New candle: {}".format(candle)) + +@bfx.ws.on('new_trade') +def log_trade(trade): + print ("New trade: {}".format(trade)) + +async def start(): + await bfx.ws.subscribe('candles', 'tBTCUSD', timeframe='1m') + await bfx.ws.subscribe('trades', 'tBTCUSD') + +bfx.ws.on('connected', start) +bfx.ws.run() diff --git a/bfxapi/examples/update_order.py b/bfxapi/examples/ws/update_order.py similarity index 95% rename from bfxapi/examples/update_order.py rename to bfxapi/examples/ws/update_order.py index 2fe4f2a..d2cff40 100644 --- a/bfxapi/examples/update_order.py +++ b/bfxapi/examples/ws/update_order.py @@ -1,43 +1,43 @@ -import os -import sys -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' -) - -@bfx.ws.on('order_update') -def order_updated(order, trade): - print ("Order updated.") - print (order) - print (trade) - -@bfx.ws.once('order_update') -async def order_once_updated(order, trade): - # update a second time using the object function - await order.update(price=80, amount=0.02, flags="2nd update") - -@bfx.ws.once('order_confirmed') -async def trade_completed(order, trade): - print ("Order confirmed.") - print (order) - print (trade) - await bfx.ws.update_order(order.id, price=100, amount=0.01) - -@bfx.ws.on('error') -def log_error(msg): - print ("Error: {}".format(msg)) - -@bfx.ws.once('authenticated') -async def submit_order(auth_message): - # create an inital order a really low price so it stays open - await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') - -bfx.ws.run() +import os +import sys +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' +) + +@bfx.ws.on('order_update') +def order_updated(order, trade): + print ("Order updated.") + print (order) + print (trade) + +@bfx.ws.once('order_update') +async def order_once_updated(order, trade): + # update a second time using the object function + await order.update(price=80, amount=0.02, flags="2nd update") + +@bfx.ws.once('order_confirmed') +async def trade_completed(order, trade): + print ("Order confirmed.") + print (order) + print (trade) + await bfx.ws.update_order(order.id, price=100, amount=0.01) + +@bfx.ws.on('error') +def log_error(msg): + print ("Error: {}".format(msg)) + +@bfx.ws.once('authenticated') +async def submit_order(auth_message): + # create an inital order a really low price so it stays open + await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') + +bfx.ws.run() diff --git a/bfxapi/examples/wallet_balance.py b/bfxapi/examples/ws/wallet_balance.py similarity index 94% rename from bfxapi/examples/wallet_balance.py rename to bfxapi/examples/ws/wallet_balance.py index 0d9ab35..7df0e0b 100644 --- a/bfxapi/examples/wallet_balance.py +++ b/bfxapi/examples/ws/wallet_balance.py @@ -1,31 +1,31 @@ -import os -import sys -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='INFO' -) - -@bfx.ws.on('wallet_snapshot') -def log_snapshot(wallets): - for wallet in wallets: - print (wallet) - - # or bfx.ws.wallets.get_wallets() - -@bfx.ws.on('wallet_update') -def log_update(wallet): - print ("Balance updates: {}".format(wallet)) - -@bfx.ws.on('error') -def log_error(msg): - print ("Error: {}".format(msg)) - -bfx.ws.run() +import os +import sys +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='INFO' +) + +@bfx.ws.on('wallet_snapshot') +def log_snapshot(wallets): + for wallet in wallets: + print (wallet) + + # or bfx.ws.wallets.get_wallets() + +@bfx.ws.on('wallet_update') +def log_update(wallet): + print ("Balance updates: {}".format(wallet)) + +@bfx.ws.on('error') +def log_error(msg): + print ("Error: {}".format(msg)) + +bfx.ws.run() From 4fedaee51cae3b261937882f4e40e3afa02aee87 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Mon, 3 Dec 2018 14:41:30 +0000 Subject: [PATCH 03/31] Add authentication for rest client --- bfxapi/rest/BfxRest.py | 33 +++++++++++++++++++++++--- bfxapi/utils/auth.py | 39 +++++++++++++++++++++++++++++++ bfxapi/websockets/BfxWebsocket.py | 16 ++----------- 3 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 bfxapi/utils/auth.py diff --git a/bfxapi/rest/BfxRest.py b/bfxapi/rest/BfxRest.py index aad0f97..47bd8f9 100644 --- a/bfxapi/rest/BfxRest.py +++ b/bfxapi/rest/BfxRest.py @@ -4,12 +4,16 @@ import time import json from ..utils.CustomLogger import CustomLogger +from ..utils.auth import generate_auth_headers +from ..models import Wallet class BfxRest: def __init__(self, API_KEY, API_SECRET, host='https://api.bitfinex.com/v2', loop=None, logLevel='INFO', *args, **kwargs): self.loop = loop or asyncio.get_event_loop() + self.API_KEY = API_KEY + self.API_SECRET = API_SECRET self.host = host self.logger = CustomLogger('BfxRest', logLevel=logLevel) @@ -19,9 +23,23 @@ class BfxRest: async with session.get(url) as resp: text = await resp.text() if resp.status is not 200: - raise Exception('Unable to seed trades. Received status {} - {}' - .format(resp.status, text)) - return json.loads(text) + raise Exception('GET {} failed with status {} - {}' + .format(url, resp.status, text)) + return await resp.json(text) + + async def post(self, endpoint, data={}): + url = '{}/{}'.format(self.host, endpoint) + sData = json.dumps(data) + headers = generate_auth_headers( + self.API_KEY, self.API_SECRET, endpoint, sData) + headers["content-type"] = "application/json" + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=sData) as resp: + text = await resp.text() + if resp.status is not 200: + raise Exception('POST {} failed with status {} - {}' + .format(url, resp.status, text)) + return await resp.json() async def get_seed_candles(self, symbol): endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol) @@ -43,3 +61,12 @@ class BfxRest: candles.sort(key=lambda x: x[0], reverse=True) self.logger.info("Downloaded {} candles.".format(len(candles))) return candles + + ################################################## + # Wallets # + ################################################## + + async def get_wallets(self): + endpoint = "auth/r/wallets" + raw_wallets = await self.post(endpoint) + return [ Wallet(rw[0], rw[1], rw[2], rw[3]) for rw in raw_wallets ] diff --git a/bfxapi/utils/auth.py b/bfxapi/utils/auth.py new file mode 100644 index 0000000..358c953 --- /dev/null +++ b/bfxapi/utils/auth.py @@ -0,0 +1,39 @@ +import hashlib +import hmac +import time + +def generate_auth_payload(API_KEY, API_SECRET): + nonce = _gen_nonce() + authMsg, sig = _gen_signature(API_KEY, API_SECRET, nonce) + + return { + 'apiKey': API_KEY, + 'authSig': sig, + 'authNonce': nonce, + 'authPayload': authMsg, + 'event': 'auth' + } + +def generate_auth_headers(API_KEY, API_SECRET, path, body): + nonce = str(_gen_nonce()) + signature = "/api/v2/{}{}{}".format(path, nonce, body) + print (API_KEY) + print (API_SECRET) + h = hmac.new(API_SECRET.encode('utf8'), signature.encode('utf8'), hashlib.sha384) + signature = h.hexdigest() + + return { + "bfx-nonce": nonce, + "bfx-apikey": API_KEY, + "bfx-signature": signature + } + +def _gen_signature(API_KEY, API_SECRET, nonce): + authMsg = 'AUTH{}'.format(nonce) + secret = API_SECRET.encode('utf8') + sig = hmac.new(secret, authMsg.encode('utf8'), hashlib.sha384).hexdigest() + + return authMsg, sig + +def _gen_nonce(): + return int(round(time.time() * 1000000)) diff --git a/bfxapi/websockets/BfxWebsocket.py b/bfxapi/websockets/BfxWebsocket.py index e1081bd..cb04890 100644 --- a/bfxapi/websockets/BfxWebsocket.py +++ b/bfxapi/websockets/BfxWebsocket.py @@ -1,14 +1,13 @@ import asyncio import json import time -import hashlib -import hmac import random from .GenericWebsocket import GenericWebsocket, AuthError from .SubscriptionManager import SubscriptionManager from .WalletManager import WalletManager from .OrderManager import OrderManager +from ..utils.auth import generate_auth_payload from ..models import Order, Trade, OrderBook class Flags: @@ -392,18 +391,7 @@ class BfxWebsocket(GenericWebsocket): self.logger.warn('Unknown websocket response: {}'.format(msg)) async def _ws_authenticate_socket(self): - nonce = int(round(time.time() * 1000000)) - authMsg = 'AUTH{}'.format(nonce) - secret = self.API_SECRET.encode() - sig = hmac.new(secret, authMsg.encode(), hashlib.sha384).hexdigest() - hmac.new(secret, self.API_SECRET.encode('utf'), hashlib.sha384).hexdigest() - jdata = { - 'apiKey': self.API_KEY, - 'authSig': sig, - 'authNonce': nonce, - 'authPayload': authMsg, - 'event': 'auth' - } + jdata = generate_auth_payload(self.API_KEY, self.API_SECRET) await self.ws.send(json.dumps(jdata)) async def on_open(self): From 468c7911ab028a68bc4940da21fb3019cf63aea9 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Mon, 3 Dec 2018 14:41:43 +0000 Subject: [PATCH 04/31] examples.rest: add get wallets --- bfxapi/examples/rest/get_wallets.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 bfxapi/examples/rest/get_wallets.py diff --git a/bfxapi/examples/rest/get_wallets.py b/bfxapi/examples/rest/get_wallets.py new file mode 100644 index 0000000..c9ed271 --- /dev/null +++ b/bfxapi/examples/rest/get_wallets.py @@ -0,0 +1,23 @@ +import os +import sys +import asyncio +sys.path.append('../') + +from bfxapi import Client + +API_KEY=os.getenv("BFX_KEY") +API_SECRET=os.getenv("BFX_SECRET") + +bfx = Client( + API_KEY='zxXi3z6eMnRuW2mjvJSlJ08aqlHDCZbcKlqXWnzdXtF', + API_SECRET='WL6hp6eVboiTW0dYfvIpTrX8HFPioumBoJ1w1FbAEgF', + logLevel='DEBUG', + rest_host='https://test.bitfinex.com/v2' +) + +async def log_wallets(): + wallets = await bfx.rest.get_wallets() + print (wallets) + +t = asyncio.ensure_future(log_wallets()) +asyncio.get_event_loop().run_until_complete(t) From 1f2fdf9fa0ecf85ffa54eb1d3b05c20b1a5e19ba Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 11:05:16 +0000 Subject: [PATCH 05/31] Remove trade object from order manager --- bfxapi/examples/ws/send_order.py | 3 +-- bfxapi/examples/ws/update_order.py | 8 +++----- bfxapi/websockets/OrderManager.py | 29 ++++++++++++----------------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/bfxapi/examples/ws/send_order.py b/bfxapi/examples/ws/send_order.py index 2a2bb5a..b7a6fce 100644 --- a/bfxapi/examples/ws/send_order.py +++ b/bfxapi/examples/ws/send_order.py @@ -18,10 +18,9 @@ async def close_all(data): await bfx.ws.close_all_orders() @bfx.ws.on('order_confirmed') -async def trade_completed(order, trade): +async def trade_completed(order): print ("Order confirmed.") print (order) - print (trade) ## close the order # await order.close() # or diff --git a/bfxapi/examples/ws/update_order.py b/bfxapi/examples/ws/update_order.py index d2cff40..59abee5 100644 --- a/bfxapi/examples/ws/update_order.py +++ b/bfxapi/examples/ws/update_order.py @@ -14,21 +14,19 @@ bfx = Client( ) @bfx.ws.on('order_update') -def order_updated(order, trade): +def order_updated(order): print ("Order updated.") print (order) - print (trade) @bfx.ws.once('order_update') -async def order_once_updated(order, trade): +async def order_once_updated(order): # update a second time using the object function await order.update(price=80, amount=0.02, flags="2nd update") @bfx.ws.once('order_confirmed') -async def trade_completed(order, trade): +async def trade_completed(order): print ("Order confirmed.") print (order) - print (trade) await bfx.ws.update_order(order.id, price=100, amount=0.01) @bfx.ws.on('error') diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index 52df201..51c2e07 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -2,7 +2,7 @@ import time import asyncio from ..utils.CustomLogger import CustomLogger -from ..models import Order, Trade +from ..models import Order class OrderManager: @@ -23,7 +23,7 @@ class OrderManager: def get_pending_orders(self): return list(self.pending_orders.values()) - async def _confirm_order(self, order, trade): + async def _confirm_order(self, order): ''' Called once when we first recieve infomation back from the bitfinex api that the order has been accepted. @@ -31,12 +31,12 @@ class OrderManager: if order.cId in self.pending_orders: if self.pending_callbacks[order.cId][0]: # call onComplete callback - await self.pending_callbacks[order.cId][0](order, trade) + await self.pending_callbacks[order.cId][0](order) order.set_confirmed() # remove from pending orders list del self.pending_orders[order.cId] del self.pending_callbacks[order.cId] - self.bfxapi._emit('order_confirmed', order, trade) + self.bfxapi._emit('order_confirmed', order) async def confirm_order_closed(self, raw_ws_data): # order created and executed @@ -45,13 +45,12 @@ class OrderManager: # @ 18909.0(0.06700003)",null,null,18909,18913.2899961,0,0,null,null,null,0,0,null,null,null, # "API>BFX",null,null,null]] order = Order(self.bfxapi, raw_ws_data[2]) - trade = Trade(order) order.set_open_state(False) if order.id in self.open_orders: del self.open_orders[order.id] - await self._confirm_order(order, trade) + await self._confirm_order(order) self.logger.info("Order closed: {} {}".format(order.symbol, order.status)) - self.bfxapi._emit('order_closed', order, trade) + self.bfxapi._emit('order_closed', order) async def build_from_order_snapshot(self, raw_ws_data): ''' @@ -61,10 +60,8 @@ class OrderManager: self.open_orders = {} for raw_order in osData: order = Order(self.bfxapi, raw_order) - trade = Trade(order) order.set_open_state(True) self.open_orders[order.id] = order - # await self._confirm_order(order, trade) self.bfxapi._emit('order_snapshot', self.get_open_orders()) async def confirm_order_update(self, raw_ws_data): @@ -75,11 +72,10 @@ class OrderManager: # None, None, None]] order = Order(self.bfxapi, raw_ws_data[2]) order.set_open_state(True) - trade = Trade(order) self.open_orders[order.id] = order - await self._confirm_order(order, trade) - self.logger.info("Order update: {} {}".format(order, trade)) - self.bfxapi._emit('order_update', order, trade) + await self._confirm_order(order) + self.logger.info("Order update: {}".format(order)) + self.bfxapi._emit('order_update', order) async def confirm_order_new(self, raw_ws_data): # order created but not executed / created but partially filled @@ -89,11 +85,10 @@ class OrderManager: # None, None, None]] order = Order(self.bfxapi, raw_ws_data[2]) order.set_open_state(True) - trade = Trade(order) self.open_orders[order.id] = order - await self._confirm_order(order, trade) - self.logger.info("Order new: {} {}".format(order, trade)) - self.bfxapi._emit('order_new', order, trade) + await self._confirm_order(order) + self.logger.info("Order new: {}".format(order)) + self.bfxapi._emit('order_new', order) def _gen_unqiue_cid(self): return int(round(time.time() * 1000)) From 136773197d1bd6cf13242e4f7ae3acc966dcec9d Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 11:26:36 +0000 Subject: [PATCH 06/31] Remove order onComplete and add onConfirma and onClose --- README.md | 8 +++---- .../ws/{candel_order.py => cancel_order.py} | 2 +- bfxapi/examples/ws/send_order.py | 2 +- bfxapi/websockets/OrderManager.py | 22 ++++++++++--------- 4 files changed, 18 insertions(+), 16 deletions(-) rename bfxapi/examples/ws/{candel_order.py => cancel_order.py} (95%) diff --git a/README.md b/README.md index 10d9ad2..4d38965 100644 --- a/README.md +++ b/README.md @@ -148,15 +148,15 @@ The websocket exposes a collection of events that are triggered when certain dat Unsubscribe and subscribe to all data feeds -#### `submit_order(symbol, price, amount, market_type, hidden=False, onComplete=None, onError=None, *args, **kwargs)` +#### `submit_order(symbol, price, amount, market_type, hidden=False, onConfirm=None, onClose=None, *args, **kwargs)` - Submits an order to the Bitfinex api. If the order is successful then the order_closed event will be triggered and the onComplete function will also be called if provided in the parameters. + 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. -#### `update_order(orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None, onComplete=None, onError=None)` +#### `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)` 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, onComplete=None, onError=None):` +#### `close_order(self, orderId, onConfirm=None, onClose=None):` Close the order with the given orderId if it still open. diff --git a/bfxapi/examples/ws/candel_order.py b/bfxapi/examples/ws/cancel_order.py similarity index 95% rename from bfxapi/examples/ws/candel_order.py rename to bfxapi/examples/ws/cancel_order.py index b13b3af..2da2a44 100644 --- a/bfxapi/examples/ws/candel_order.py +++ b/bfxapi/examples/ws/cancel_order.py @@ -24,7 +24,7 @@ async def trade_completed(order, trade): print ("Order confirmed.") print (order) print (trade) - await bfx.ws.cancel_order(order.id) + await bfx.ws.close_order(order.id) @bfx.ws.on('error') def log_error(msg): diff --git a/bfxapi/examples/ws/send_order.py b/bfxapi/examples/ws/send_order.py index b7a6fce..e61442c 100644 --- a/bfxapi/examples/ws/send_order.py +++ b/bfxapi/examples/ws/send_order.py @@ -42,6 +42,6 @@ async def submit_order(auth_message): # You can also provide a callback # await ws.submit_order('tBTCUSD', 0, 0.01, -# 'EXCHANGE MARKET', onComplete=trade_complete) +# 'EXCHANGE MARKET', onClose=trade_complete) bfx.ws.run() diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index 51c2e07..c6976d1 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -23,19 +23,21 @@ class OrderManager: def get_pending_orders(self): return list(self.pending_orders.values()) - async def _confirm_order(self, order): + async def _confirm_order(self, order, isClosed=False): ''' Called once when we first recieve infomation back from the bitfinex api that the order has been accepted. ''' if order.cId in self.pending_orders: if self.pending_callbacks[order.cId][0]: - # call onComplete callback + # call onConfirm callback await self.pending_callbacks[order.cId][0](order) + if isClosed: + await self.pending_callbacks[order.cId][1](order) + del self.pending_callbacks[order.cId] order.set_confirmed() # remove from pending orders list del self.pending_orders[order.cId] - del self.pending_callbacks[order.cId] self.bfxapi._emit('order_confirmed', order) async def confirm_order_closed(self, raw_ws_data): @@ -48,7 +50,7 @@ class OrderManager: order.set_open_state(False) if order.id in self.open_orders: del self.open_orders[order.id] - await self._confirm_order(order) + await self._confirm_order(order, isClosed=True) self.logger.info("Order closed: {} {}".format(order.symbol, order.status)) self.bfxapi._emit('order_closed', order) @@ -94,7 +96,7 @@ class OrderManager: return int(round(time.time() * 1000)) async def submit_order(self, symbol, price, amount, market_type, - hidden=False, onComplete=None, onError=None, *args, **kwargs): + hidden=False, onConfirm=None, onClose=None, *args, **kwargs): cId = self._gen_unqiue_cid() # send order over websocket payload = { @@ -105,24 +107,24 @@ class OrderManager: "price": str(price) } self.pending_orders[cId] = payload - self.pending_callbacks[cId] = (onComplete, onError) + self.pending_callbacks[cId] = (onConfirm, onClose) await self.bfxapi._send_auth_command('on', payload) self.logger.info("Order cid={} ({} {} @ {}) dispatched".format( cId, symbol, amount, price)) - async def update_order(self, orderId, *args, onComplete=None, onError=None, **kwargs): + async def update_order(self, orderId, *args, onConfirm=None, onClose=None, **kwargs): if orderId not in self.open_orders: raise Exception("Order id={} is not open".format(orderId)) order = self.open_orders[orderId] - self.pending_callbacks[order.cId] = (onComplete, onError) + self.pending_callbacks[order.cId] = (onConfirm, onClose) await order.update(*args, **kwargs) self.logger.info("Update Order order_id={} dispatched".format(orderId)) - async def close_order(self, orderId, onComplete=None, onError=None): + async def close_order(self, orderId, onConfirm=None, onClose=None): if orderId not in self.open_orders: raise Exception("Order id={} is not open".format(orderId)) order = self.open_orders[orderId] - self.pending_callbacks[order.cId] = (onComplete, onError) + self.pending_callbacks[order.cId] = (onConfirm, onClose) await order.cancel() self.logger.info("Order cancel order_id={} dispatched".format(orderId)) From 3df460476624ceea8333a80bdbe2f713c5826379 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:39:07 +0000 Subject: [PATCH 07/31] models: create static create methods --- bfxapi/models/Order.py | 89 +++++++++++++++++-------------- bfxapi/models/Trade.py | 34 +++++++----- bfxapi/websockets/OrderManager.py | 34 +++++++++--- 3 files changed, 99 insertions(+), 58 deletions(-) diff --git a/bfxapi/models/Order.py b/bfxapi/models/Order.py index 1da0a7c..e2a1532 100644 --- a/bfxapi/models/Order.py +++ b/bfxapi/models/Order.py @@ -1,4 +1,5 @@ import time +import datetime class OrderClosedModel: ID = 0 @@ -24,51 +25,59 @@ def now_in_mills(): return int(round(time.time() * 1000)) class Order: - def __init__(self, bfxapi, closingOrderArray): - self.bfxapi = bfxapi - self.id = closingOrderArray[OrderClosedModel.ID] - self.gId = closingOrderArray[OrderClosedModel.GID] - self.cId = closingOrderArray[OrderClosedModel.CID] - self.symbol = closingOrderArray[OrderClosedModel.SYMBOL] - self.mtsCreate = closingOrderArray[OrderClosedModel.MTS_CREATE] - self.mtsUpdate = closingOrderArray[OrderClosedModel.MTS_UPDATE] - self.amount = closingOrderArray[OrderClosedModel.AMOUNT] - self.amountOrig = closingOrderArray[OrderClosedModel.AMOUNT_ORIG] - self.type = closingOrderArray[OrderClosedModel.TYPE] - self.typePrev = closingOrderArray[OrderClosedModel.TYPE_PREV] - self.flags = closingOrderArray[OrderClosedModel.FLAGS] - self.status = closingOrderArray[OrderClosedModel.STATUS] - self.price = closingOrderArray[OrderClosedModel.PRICE] - self.priceAvg = closingOrderArray[OrderClosedModel.PRIVE_AVG] - self.priceTrailing = closingOrderArray[OrderClosedModel.PRICE_TRAILING] - self.priceAuxLimit = closingOrderArray[OrderClosedModel.PRICE_AUX_LIMIT] - self.notfiy = closingOrderArray[OrderClosedModel.NOTIFY] - self.placeId = closingOrderArray[OrderClosedModel.PLACE_ID] + def __init__(self, id, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, + typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId): + self.id = id + self.gId = gId + self.cId = cId + self.symbol = symbol + self.mtsCreate = mtsCreate + self.mtsUpdate = mtsUpdate + self.amount = amount + self.amountOrig = amountOrig + self.type = oType + self.typePrev = typePrev + self.flags = flags + self.status = status + self.price = price + self.priceAvg = priceAvg + self.priceTrailing = priceTrailing + self.priceAuxLimit = priceAuxLimit + self.notfiy = notfiy + self.placeId = placeId + self.is_pending_bool = True self.is_confirmed_bool = False self.is_open_bool = False - async def update(self, price=None, amount=None, delta=None, price_aux_limit=None, - price_trailing=None, flags=None, time_in_force=None): - payload = { "id": self.id } - 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 flags is not None: - payload['flags'] = str(flags) - if time_in_force is not None: - payload['time_in_force'] = str(time_in_force) - await self.bfxapi._send_auth_command('ou', payload) + self.date = datetime.datetime.fromtimestamp(mtsCreate/1000.0) + if priceAvg: + ## if cancelled then priceAvg wont exist + self.fee = (priceAvg * abs(amount)) * 0.002 - async def close(self): - await self.bfxapi._send_auth_command('oc', { 'id': self.id }) + @staticmethod + def from_raw_order(raw_order): + id = raw_order[OrderClosedModel.ID] + gId = raw_order[OrderClosedModel.GID] + cId = raw_order[OrderClosedModel.CID] + symbol = raw_order[OrderClosedModel.SYMBOL] + mtsCreate = raw_order[OrderClosedModel.MTS_CREATE] + mtsUpdate = raw_order[OrderClosedModel.MTS_UPDATE] + amount = raw_order[OrderClosedModel.AMOUNT] + amountOrig = raw_order[OrderClosedModel.AMOUNT_ORIG] + oType = raw_order[OrderClosedModel.TYPE] + typePrev = raw_order[OrderClosedModel.TYPE_PREV] + flags = raw_order[OrderClosedModel.FLAGS] + status = raw_order[OrderClosedModel.STATUS] + price = raw_order[OrderClosedModel.PRICE] + priceAvg = raw_order[OrderClosedModel.PRIVE_AVG] + priceTrailing = raw_order[OrderClosedModel.PRICE_TRAILING] + priceAuxLimit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT] + notfiy = raw_order[OrderClosedModel.NOTIFY] + placeId = raw_order[OrderClosedModel.PLACE_ID] + + return Order(id, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, + typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId) def set_confirmed(self): self.is_pending_bool = False diff --git a/bfxapi/models/Trade.py b/bfxapi/models/Trade.py index 8fa60b7..bcb0e83 100644 --- a/bfxapi/models/Trade.py +++ b/bfxapi/models/Trade.py @@ -4,17 +4,27 @@ class Trade: SHORT = 'SHORT' LONG = 'LONG' - def __init__(self, order, tag=''): - self.order = order - self.amount = order.amount - self.price = order.priceAvg - self.fee = (order.priceAvg * abs(order.amount)) * 0.002 - self.mts = order.mtsCreate - self.date = datetime.datetime.fromtimestamp(order.mtsCreate/1000.0) - self.direction = self.SHORT if order.amount < 0 else self.LONG - self.tag = tag + def __init__(self, id, pair, mts_create, order_id, amount, price, order_type, + order_price, maker, fee, fee_currency): + self.id = id + self.pair = pair + self.mts_create = mts_create + self.date = datetime.datetime.fromtimestamp(mts_create/1000.0) + self.order_id = order_id + self.amount = amount + self.direction = Trade.SHORT if amount < 0 else Trade.LONG + self.price = price + self.order_type = order_type + self.order_price = order_price + self.maker = maker + self.fee = fee + self.fee_currency = fee_currency + + @staticmethod + def from_raw_rest_trade(raw_trade): + # [24224048, 'tBTCUSD', 1542800024000, 1151353484, 0.09399997, 19963, None, None, -1, -0.000188, 'BTC'] + return Trade(*raw_trade) def __str__(self): - ''' Allow us to print the Trade object in a pretty format ''' - return "Trade {} @ {} fee={} ".format( - self.amount, self.price, self.fee, self.order) + return "Trade '{}' x {} @ {} ".format( + self.pair, self.amount, self.price, self.direction, self.fee) diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index c6976d1..c7cecf0 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -46,7 +46,7 @@ class OrderManager: # "EXCHANGE MARKET",null,null,null,0,"EXECUTED @ 18922.0(0.03299997): was PARTIALLY FILLED # @ 18909.0(0.06700003)",null,null,18909,18913.2899961,0,0,null,null,null,0,0,null,null,null, # "API>BFX",null,null,null]] - order = Order(self.bfxapi, raw_ws_data[2]) + order = Order.from_raw_order(raw_ws_data[2]) order.set_open_state(False) if order.id in self.open_orders: del self.open_orders[order.id] @@ -61,7 +61,7 @@ class OrderManager: osData = raw_ws_data[2] self.open_orders = {} for raw_order in osData: - order = Order(self.bfxapi, raw_order) + order = Order.from_raw_order(raw_ws_data[2]) order.set_open_state(True) self.open_orders[order.id] = order self.bfxapi._emit('order_snapshot', self.get_open_orders()) @@ -72,7 +72,7 @@ class OrderManager: # 1542629458189, 0.01, 0.01, 'EXCHANGE LIMIT', None, None, None, 0, 'ACTIVE', # None, None, 100, 0, 0, 0, None, None, None, 0, 0, None, None, None, 'API>BFX', # None, None, None]] - order = Order(self.bfxapi, raw_ws_data[2]) + order = Order.from_raw_order(raw_ws_data[2]) order.set_open_state(True) self.open_orders[order.id] = order await self._confirm_order(order) @@ -85,7 +85,7 @@ class OrderManager: # 1542624024617, 0.01, 0.01, 'EXCHANGE LIMIT', None, None, None, 0, 'ACTIVE', # None, None, 100, 0, 0, 0, None, None, None, 0, 0, None, None, None, 'API>BFX', # None, None, None]] - order = Order(self.bfxapi, raw_ws_data[2]) + order = Order.from_raw_order(raw_ws_data[2]) order.set_open_state(True) self.open_orders[order.id] = order await self._confirm_order(order) @@ -117,7 +117,7 @@ class OrderManager: raise Exception("Order id={} is not open".format(orderId)) order = self.open_orders[orderId] self.pending_callbacks[order.cId] = (onConfirm, onClose) - await order.update(*args, **kwargs) + await order._update(orderId, *args, **kwargs) self.logger.info("Update Order order_id={} dispatched".format(orderId)) async def close_order(self, orderId, onConfirm=None, onClose=None): @@ -125,7 +125,7 @@ class OrderManager: raise Exception("Order id={} is not open".format(orderId)) order = self.open_orders[orderId] self.pending_callbacks[order.cId] = (onConfirm, onClose) - await order.cancel() + await self._close_order(orderId) self.logger.info("Order cancel order_id={} dispatched".format(orderId)) async def close_all_orders(self): @@ -139,3 +139,25 @@ class OrderManager: asyncio.ensure_future(self.open_orders[oid].close()) ] await asyncio.wait(*[ task_batch ]) + + async def _close_order(self, orderId): + await self.bfxapi._send_auth_command('oc', { 'id': orderId }) + + async def _update(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, + price_trailing=None, flags=None, time_in_force=None): + 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 flags is not None: + payload['flags'] = str(flags) + if time_in_force is not None: + payload['time_in_force'] = str(time_in_force) + await self.bfxapi._send_auth_command('ou', payload) From 6d4f840b2700888535c1a5916b11257976221c21 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:39:29 +0000 Subject: [PATCH 08/31] BfxWebsocket: pass through logLevel --- bfxapi/websockets/BfxWebsocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bfxapi/websockets/BfxWebsocket.py b/bfxapi/websockets/BfxWebsocket.py index cb04890..138b906 100644 --- a/bfxapi/websockets/BfxWebsocket.py +++ b/bfxapi/websockets/BfxWebsocket.py @@ -141,7 +141,7 @@ class BfxWebsocket(GenericWebsocket): self.pendingOrders = {} self.orderBooks = {} - super(BfxWebsocket, self).__init__(host, *args, **kwargs) + super(BfxWebsocket, self).__init__(host, logLevel=logLevel, *args, **kwargs) self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel) self.orderManager = OrderManager(self, logLevel=logLevel) self.wallets = WalletManager() From 0db3fa2f649fd45db01f4d5ac7ea66f10555ff24 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:39:53 +0000 Subject: [PATCH 09/31] models: create position object --- bfxapi/models/Position.py | 25 +++++++++++++++++++++++++ bfxapi/models/__init__.py | 1 + 2 files changed, 26 insertions(+) create mode 100644 bfxapi/models/Position.py diff --git a/bfxapi/models/Position.py b/bfxapi/models/Position.py new file mode 100644 index 0000000..e8bcdf0 --- /dev/null +++ b/bfxapi/models/Position.py @@ -0,0 +1,25 @@ + +class Position: + + def __init__(self, symbol, status, amount, bPrice, mFunding, mFundingType, + profit_loss, profit_loss_perc, lPrice, lev): + # [['tBTCUSD', 'ACTIVE', -2.37709587, 20720, -0.00066105, 0, 13115.99728968, 26.6296139, 104156.986252, -1.2332]] + self.symbol = symbol + self.status = status + self.amount = amount + self.base_price = bPrice + self.margin_funding = mFunding + self.margin_funding_type = mFundingType + self.profit_loss = profit_loss + self.profit_loss_percentage = profit_loss_perc + self.liquidation_price = lPrice + self.leverage = lev + + @staticmethod + def from_raw_rest_position(raw_position): + return Position(*raw_position) + + def __str__(self): + ''' Allow us to print the Trade object in a pretty format ''' + return "Position '{}' {} x {} ".format( + self.symbol, self.base_price, self.amount, self.status, self.profit_loss) diff --git a/bfxapi/models/__init__.py b/bfxapi/models/__init__.py index 19d1e18..dfb89d9 100644 --- a/bfxapi/models/__init__.py +++ b/bfxapi/models/__init__.py @@ -5,3 +5,4 @@ from .Trade import * from .OrderBook import * from .Subscription import * from .Wallet import * +from .Position import * From 8a63d361595544452d32cca9cb60c802f61c90b6 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:40:36 +0000 Subject: [PATCH 10/31] BfxRest: add auth and public endpoints --- bfxapi/rest/BfxRest.py | 66 +++++++++++++++++++++++++++++++++++++----- bfxapi/utils/auth.py | 2 -- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/bfxapi/rest/BfxRest.py b/bfxapi/rest/BfxRest.py index 47bd8f9..941bcbf 100644 --- a/bfxapi/rest/BfxRest.py +++ b/bfxapi/rest/BfxRest.py @@ -5,7 +5,7 @@ import json from ..utils.CustomLogger import CustomLogger from ..utils.auth import generate_auth_headers -from ..models import Wallet +from ..models import Wallet, Order, Position, Trade class BfxRest: @@ -17,30 +17,34 @@ class BfxRest: self.host = host self.logger = CustomLogger('BfxRest', logLevel=logLevel) - async def fetch(self, endpoint): - url = '{}/{}'.format(self.host, endpoint) + async def fetch(self, endpoint, params=""): + url = '{}/{}{}'.format(self.host, endpoint, params) async with aiohttp.ClientSession() as session: async with session.get(url) as resp: text = await resp.text() if resp.status is not 200: raise Exception('GET {} failed with status {} - {}' .format(url, resp.status, text)) - return await resp.json(text) + return await resp.json() - async def post(self, endpoint, data={}): + async def post(self, endpoint, data={}, params=""): url = '{}/{}'.format(self.host, endpoint) sData = json.dumps(data) headers = generate_auth_headers( self.API_KEY, self.API_SECRET, endpoint, sData) headers["content-type"] = "application/json" async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, data=sData) as resp: + async with session.post(url + params, headers=headers, data=sData) as resp: text = await resp.text() if resp.status is not 200: raise Exception('POST {} failed with status {} - {}' .format(url, resp.status, text)) return await resp.json() + ################################################## + # Public # + ################################################## + async def get_seed_candles(self, symbol): endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol) time_difference = (1000 * 60) * 5000 @@ -62,11 +66,57 @@ class BfxRest: self.logger.info("Downloaded {} candles.".format(len(candles))) return candles + async def get_public_candles(self, symbol, start, end, section='hist', + tf='1m', limit="100", sort=-1): + endpoint = "candles/trade:{}:{}/{}".format(tf, symbol, section) + params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) + candles = await self.fetch(endpoint, params=params) + return candles + + async def get_public_trades(self, symbol, start, end, limit="100", sort=-1): + endpoint = "trades/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) + trades = await self.fetch(endpoint, params=params) + return trades + + async def get_public_books(self, symbol, precision="P0", length=25): + endpoint = "book/{}/{}".format(symbol, precision) + params = "?len={}".format(length) + books = await self.fetch(endpoint, params) + return books + + async def get_public_ticker(self, symbol): + endpoint = "ticker/{}".format(symbol) + ticker = await self.fetch(endpoint) + return ticker + ################################################## - # Wallets # + # Authenticated # ################################################## async def get_wallets(self): endpoint = "auth/r/wallets" raw_wallets = await self.post(endpoint) - return [ Wallet(rw[0], rw[1], rw[2], rw[3]) for rw in raw_wallets ] + return [ Wallet(*rw[:4]) for rw in raw_wallets ] + + async def get_active_orders(self, symbol): + endpoint = "auth/r/orders/{}".format(symbol) + raw_orders = await self.post(endpoint) + return [ Order.from_raw_order(ro) for ro in raw_orders ] + + async def get_order_history(self, symbol, start, end, limit=25, sort=-1): + endpoint = "auth/r/orders/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) + raw_orders = await self.post(endpoint, params=params) + return [ Order.from_raw_order(ro) for ro in raw_orders ] + + async def get_active_position(self): + endpoint = "auth/r/positions" + raw_positions = await self.post(endpoint) + return [ Position.from_raw_rest_position(rp) for rp in raw_positions ] + + async def get_trades(self, symbol, start, end, limit=25): + endpoint = "auth/r/trades/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}".format(start, end, limit) + raw_trades = await self.post(endpoint, params=params) + return [ Trade.from_raw_rest_trade(rt) for rt in raw_trades ] diff --git a/bfxapi/utils/auth.py b/bfxapi/utils/auth.py index 358c953..6eebd65 100644 --- a/bfxapi/utils/auth.py +++ b/bfxapi/utils/auth.py @@ -17,8 +17,6 @@ def generate_auth_payload(API_KEY, API_SECRET): def generate_auth_headers(API_KEY, API_SECRET, path, body): nonce = str(_gen_nonce()) signature = "/api/v2/{}{}{}".format(path, nonce, body) - print (API_KEY) - print (API_SECRET) h = hmac.new(API_SECRET.encode('utf8'), signature.encode('utf8'), hashlib.sha384) signature = h.hexdigest() From 2a96cede5e63947e5dfee50c863c2d8b18235514 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:41:02 +0000 Subject: [PATCH 11/31] examples.rest: create public and auth examples --- .../examples/rest/get_authenticated_data.py | 59 +++++++++++++++++++ bfxapi/examples/rest/get_public_data.py | 44 ++++++++++++++ bfxapi/examples/rest/get_wallets.py | 23 -------- 3 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 bfxapi/examples/rest/get_authenticated_data.py create mode 100644 bfxapi/examples/rest/get_public_data.py delete mode 100644 bfxapi/examples/rest/get_wallets.py diff --git a/bfxapi/examples/rest/get_authenticated_data.py b/bfxapi/examples/rest/get_authenticated_data.py new file mode 100644 index 0000000..297b776 --- /dev/null +++ b/bfxapi/examples/rest/get_authenticated_data.py @@ -0,0 +1,59 @@ +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', + rest_host='https://test.bitfinex.com/v2' +) + +async def log_wallets(): + wallets = await bfx.rest.get_wallets() + print ("Wallets:") + [ print (w) for w in wallets ] + +async def log_active_orders(): + orders = await bfx.rest.get_active_orders('tBTCUSD') + print ("Orders:") + [ print (o) for o in orders ] + +async def log_orders_history(): + now = int(round(time.time() * 1000)) + then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago + + orders = await bfx.rest.get_order_history('tBTCUSD', 0, then) + print ("Orders:") + [ print (o) for o in orders ] + +async def log_active_positions(): + positions = await bfx.rest.get_active_position() + print ("Positions:") + [ print (p) for p in positions ] + +async def log_trades(): + now = int(round(time.time() * 1000)) + then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago + + trades = await bfx.rest.get_trades('tBTCUSD', 0, then) + print ("Trades") + [ print (t) for t in trades] + +async def run(): + await log_wallets() + await log_active_orders() + await log_orders_history() + await log_active_positions() + await log_trades() + + +t = asyncio.ensure_future(run()) +asyncio.get_event_loop().run_until_complete(t) diff --git a/bfxapi/examples/rest/get_public_data.py b/bfxapi/examples/rest/get_public_data.py new file mode 100644 index 0000000..4588b9f --- /dev/null +++ b/bfxapi/examples/rest/get_public_data.py @@ -0,0 +1,44 @@ +import os +import sys +import asyncio +import time +sys.path.append('../') + +from bfxapi import Client + +bfx = Client( + logLevel='DEBUG', + rest_host='https://test.bitfinex.com/v2' +) + +now = int(round(time.time() * 1000)) +then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago + +async def log_historical_candles(): + candles = await bfx.rest.get_public_candles('tBTCUSD', 0, then) + print ("Candles:") + [ print (c) for c in candles ] + +async def log_historical_trades(): + trades = await bfx.rest.get_public_trades('tBTCUSD', 0, then) + print ("Trades:") + [ print (t) for t in trades ] + +async def log_books(): + orders = await bfx.rest.get_public_books('tBTCUSD') + print ("Order book:") + [ print (o) for o in orders ] + +async def log_ticker(): + ticker = await bfx.rest.get_public_ticker('tBTCUSD') + print ("Ticker:") + print (ticker) + +async def run(): + await log_historical_candles() + await log_historical_trades() + await log_books() + await log_ticker() + +t = asyncio.ensure_future(run()) +asyncio.get_event_loop().run_until_complete(t) diff --git a/bfxapi/examples/rest/get_wallets.py b/bfxapi/examples/rest/get_wallets.py deleted file mode 100644 index c9ed271..0000000 --- a/bfxapi/examples/rest/get_wallets.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sys -import asyncio -sys.path.append('../') - -from bfxapi import Client - -API_KEY=os.getenv("BFX_KEY") -API_SECRET=os.getenv("BFX_SECRET") - -bfx = Client( - API_KEY='zxXi3z6eMnRuW2mjvJSlJ08aqlHDCZbcKlqXWnzdXtF', - API_SECRET='WL6hp6eVboiTW0dYfvIpTrX8HFPioumBoJ1w1FbAEgF', - logLevel='DEBUG', - rest_host='https://test.bitfinex.com/v2' -) - -async def log_wallets(): - wallets = await bfx.rest.get_wallets() - print (wallets) - -t = asyncio.ensure_future(log_wallets()) -asyncio.get_event_loop().run_until_complete(t) From 149a7559f4710066349eb1c6c7d7f783e8eea7b8 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Tue, 4 Dec 2018 15:42:06 +0000 Subject: [PATCH 12/31] remove reference to staging --- bfxapi/examples/rest/get_public_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bfxapi/examples/rest/get_public_data.py b/bfxapi/examples/rest/get_public_data.py index 4588b9f..a3555f0 100644 --- a/bfxapi/examples/rest/get_public_data.py +++ b/bfxapi/examples/rest/get_public_data.py @@ -8,7 +8,6 @@ from bfxapi import Client bfx = Client( logLevel='DEBUG', - rest_host='https://test.bitfinex.com/v2' ) now = int(round(time.time() * 1000)) From ca17ddf53bbac920aafe0bda3c829babf512fb41 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:51:55 +0000 Subject: [PATCH 13/31] Create funding credit model --- bfxapi/models/FundingCredit.py | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 bfxapi/models/FundingCredit.py diff --git a/bfxapi/models/FundingCredit.py b/bfxapi/models/FundingCredit.py new file mode 100644 index 0000000..016d555 --- /dev/null +++ b/bfxapi/models/FundingCredit.py @@ -0,0 +1,86 @@ + +class FundingCreditModel: + ID = 0 + SYMBOL = 1 + SIDE = 2 + MTS_CREATE = 3 + MTS_UPDATE = 4 + AMOUNT = 5 + FLAGS = 6 + STATUS = 7 + RATE = 11 + PERIOD = 12 + MTS_OPENING = 13 + MTS_LAST_PAYOUT = 14 + NOTIFY = 15 + HIDDEN = 16 + RENEW = 18 + NO_CLOSE = 20 + POSITION_PAIR = 21 + +class FundingLoan: + """ + ID integer Offer ID + SYMBOL string The currency of the offer (fUSD, etc) + SIDE string "Lend" or "Loan" + MTS_CREATE int Millisecond Time Stamp when the offer was created + MSG_UPDATE int Millisecond Time Stamp when the offer was updated + AMOUNT float Amount the offer is for + FLAGS object future params object (stay tuned) + STATUS string Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED + RATE float Rate of the offer + PERIOD int Period of the offer + MTS_OPENING int Millisecond Time Stamp when funding opened + MTS_LAST_PAYOUT int Millisecond Time Stamp when last payout received + NOTIFY int 0 if false, 1 if true + HIDDEN int 0 if false, 1 if true + RENEW int 0 if false, 1 if true + NO_CLOSE int 0 if false, 1 if true Whether the funding will be closed when the position is closed + POSITION_PAIR string Pair of the position that the funding was used for + """ + + def __init__(self, id, symbol, side, mts_create, mts_update, amount, flags, status, rate, + period, mts_opening, mts_last_payout, notify, hidden, renew, no_close, position_pair): + self.id = id + self.symbol = symbol + self.side = side + self.mts_create = mts_create + self.mts_update = mts_update + self.amount = amount + self.flags = flags + self.status = status + self.rate = rate + self.period = period + self.mts_opening = mts_opening + self.mts_last_payout = mts_last_payout + self.notify = notify + self.hidden = hidden + self.renew = renew + self.no_close = no_close + self.position_pair = position_pair + + @staticmethod + def from_raw_credit(raw_credit): + id = raw_credit[FundingCreditModel.ID] + symbol = raw_credit[FundingCreditModel.SYMBOL] + side = raw_credit[FundingCreditModel.SIDE] + mts_create = raw_credit[FundingCreditModel.MTS_CREATE] + mts_update = raw_credit[FundingCreditModel.MTS_UPDATE] + amount = raw_credit[FundingCreditModel.AMOUNT] + flags = raw_credit[FundingCreditModel.FLAGS] + status = raw_credit[FundingCreditModel.STATUS] + rate = raw_credit[FundingCreditModel.RATE] + period = raw_credit[FundingCreditModel.PERIOD] + mts_opening = raw_credit[FundingCreditModel.MTS_OPENING] + mts_last_payout = raw_credit[FundingCreditModel.MTS_LAST_PAYOUT] + notify = raw_credit[FundingCreditModel.NOTIFY] + hidden = raw_credit[FundingCreditModel.HIDDEN] + renew = raw_credit[FundingCreditModel.RENEW] + no_close = raw_credit[FundingCreditModel.NO_CLOSE] + position_pair = raw_credit[FundingCreditModel.POSITION_PAIR] + return FundingLoan(id, symbol, side, mts_create, mts_update, amount, flags, status, rate, + period, mts_opening, mts_last_payout, notify, hidden, renew, no_close, position_pair) + + def __str__(self): + return "FundingCredit '{}' ".format( + self.symbol, self.id, self.rate, self.amount, self.period, self.status) From a49c1b608d05012910f466c7d11efc3dffdf02bf Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:52:10 +0000 Subject: [PATCH 14/31] Create FundingLoan model --- bfxapi/models/FundingLoan.py | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 bfxapi/models/FundingLoan.py diff --git a/bfxapi/models/FundingLoan.py b/bfxapi/models/FundingLoan.py new file mode 100644 index 0000000..211712a --- /dev/null +++ b/bfxapi/models/FundingLoan.py @@ -0,0 +1,82 @@ + +class FundingLoanModel: + ID = 0 + SYMBOL = 1 + SIDE = 2 + MTS_CREATE = 3 + MTS_UPDATE = 4 + AMOUNT = 5 + FLAGS = 6 + STATUS = 7 + RATE = 11 + PERIOD = 12 + MTS_OPENING = 13 + MTS_LAST_PAYOUT = 14 + NOTIFY = 15 + HIDDEN = 16 + RENEW = 18 + NO_CLOSE = 20 + +class FundingLoan: + """ + ID integer Offer ID + SYMBOL string The currency of the offer (fUSD, etc) + SIDE string "Lend" or "Loan" + MTS_CREATE int Millisecond Time Stamp when the offer was created + MTS_UPDATE int Millisecond Time Stamp when the offer was created + AMOUNT float Amount the offer is for + FLAGS object future params object (stay tuned) + STATUS string Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED + RATE float Rate of the offer + PERIOD int Period of the offer + MTS_OPENING int Millisecond Time Stamp for when the loan was opened + MTS_LAST_PAYOUT int Millisecond Time Stamp for when the last payout was made + NOTIFY int 0 if false, 1 if true + HIDDEN int 0 if false, 1 if true + RENEW int 0 if false, 1 if true + NO_CLOSE int If funding will be returned when position is closed. 0 if false, 1 if true + """ + + def __init__(self, id, symbol, side, mts_create, mts_update, amount, flags, status, rate, + period, mts_opening, mts_last_payout, notify, hidden, renew, no_close): + self.id = id + self.symbol = symbol + self.side = side + self.mts_create = mts_create + self.mts_update = mts_update + self.amount = amount + self.flags = flags + self.status = status + self.rate = rate + self.period = period + self.mts_opening = mts_opening + self.mts_last_payout = mts_last_payout + self.notify = notify + self.hidden = hidden + self.renew = renew + self.no_close = no_close + + @staticmethod + def from_raw_loan(raw_loan): + id = raw_loan[FundingLoanModel.ID] + symbol = raw_loan[FundingLoanModel.SYMBOL] + side = raw_loan[FundingLoanModel.SIDE] + mts_create = raw_loan[FundingLoanModel.MTS_CREATE] + mts_update = raw_loan[FundingLoanModel.MTS_UPDATE] + amount = raw_loan[FundingLoanModel.AMOUNT] + flags = raw_loan[FundingLoanModel.FLAGS] + status = raw_loan[FundingLoanModel.STATUS] + rate = raw_loan[FundingLoanModel.RATE] + period = raw_loan[FundingLoanModel.PERIOD] + mts_opening = raw_loan[FundingLoanModel.MTS_OPENING] + mts_last_payout = raw_loan[FundingLoanModel.MTS_LAST_PAYOUT] + notify = raw_loan[FundingLoanModel.NOTIFY] + hidden = raw_loan[FundingLoanModel.HIDDEN] + renew = raw_loan[FundingLoanModel.RENEW] + no_close = raw_loan[FundingLoanModel.NO_CLOSE] + return FundingLoan(id, symbol, side, mts_create, mts_update, amount, flags, status, rate, + period, mts_opening, mts_last_payout, notify, hidden, renew, no_close) + + def __str__(self): + return "FundingLoan '{}' ".format( + self.symbol, self.id, self.rate, self.amount, self.period, self.status) From 4ed8fe17e07191d06229d416ed983fa9569b5fc9 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:52:29 +0000 Subject: [PATCH 15/31] Create FundingOffer model --- bfxapi/models/FundingOffer.py | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 bfxapi/models/FundingOffer.py diff --git a/bfxapi/models/FundingOffer.py b/bfxapi/models/FundingOffer.py new file mode 100644 index 0000000..39f9aa7 --- /dev/null +++ b/bfxapi/models/FundingOffer.py @@ -0,0 +1,74 @@ + +class FundingOfferModel: + ID = 0 + SYMBOL = 1 + MTS_CREATE = 2 + MTS_UPDATED = 3 + AMOUNT = 4 + AMOUNT_ORIG = 5 + TYPE = 6 + FLAGS = 9 + STATUS = 10 + RATE = 14 + PERIOD = 15 + NOTFIY = 16 + HIDDEN = 17 + RENEW = 19 + +class FundingOffer: + """ + ID integer Offer ID + SYMBOL string The currency of the offer (fUSD, etc) + MTS_CREATED int Millisecond Time Stamp when the offer was created + MSG_UPDATED int Millisecond Time Stamp when the offer was created + AMOUNT float Amount the offer is for + AMOUNT_ORIG float Amount the offer was entered with originally + TYPE string "lend" or "loan" + FLAGS object future params object (stay tuned) + STATUS string Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED + RATE float Rate of the offer + PERIOD int Period of the offer + NOTIFY int 0 if false, 1 if true + HIDDEN int 0 if false, 1 if true + RENEW int 0 if false, 1 if true + """ + + def __init__(self, id, symbol, mts_create, mts_updated, amount, amount_orig, f_type, + flags, status, rate, period, notify, hidden, renew): + self.id = id + self.symbol = symbol + self.mts_create = mts_create + self.mts_updated = mts_updated + self.amount = amount + self.amount_orig = amount_orig + self.f_type = f_type + self.flags = flags + self.status = status + self.rate = rate + self.period = period + self.notify = notify + self.hidden = hidden + self.renew = renew + + @staticmethod + def from_raw_offer(raw_offer): + id = raw_offer[FundingOfferModel.ID] + symbol = raw_offer[FundingOfferModel.SYMBOL] + mts_create = raw_offer[FundingOfferModel.MTS_CREATE] + mts_updated = raw_offer[FundingOfferModel.MTS_UPDATED] + amount = raw_offer[FundingOfferModel.AMOUNT] + amount_orig = raw_offer[FundingOfferModel.AMOUNT_ORIG] + f_type = raw_offer[FundingOfferModel.TYPE] + flags = raw_offer[FundingOfferModel.FLAGS] + status = raw_offer[FundingOfferModel.STATUS] + rate = raw_offer[FundingOfferModel.RATE] + period = raw_offer[FundingOfferModel.PERIOD] + notify = raw_offer[FundingOfferModel.NOTFIY] + hidden = raw_offer[FundingOfferModel.HIDDEN] + renew = raw_offer[FundingOfferModel.RENEW] + return FundingOffer(id, symbol, mts_create, mts_updated, amount, + amount_orig, f_type,flags, status, rate, period, notify, hidden, renew) + + def __str__(self): + return "FundingOffer '{}' ".format( + self.symbol, self.id, self.rate, self.period, self.status) From 3760efcc16c9512b9ec1878e85fc44c0f81ea6b1 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:52:51 +0000 Subject: [PATCH 16/31] Expose create modles via __init__ --- bfxapi/models/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bfxapi/models/__init__.py b/bfxapi/models/__init__.py index dfb89d9..40cc181 100644 --- a/bfxapi/models/__init__.py +++ b/bfxapi/models/__init__.py @@ -6,3 +6,6 @@ from .OrderBook import * from .Subscription import * from .Wallet import * from .Position import * +from .FundingLoan import * +from .FundingOffer import * +from .FundingCredit import * From 93096923440b0edf72574522130aefdc878d464c Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:55:20 +0000 Subject: [PATCH 17/31] Add doc strings to model --- bfxapi/models/Order.py | 40 +++++++++++++++++++++++++++++++++++++++ bfxapi/models/Position.py | 13 ++++++++++++- bfxapi/models/Trade.py | 14 ++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/bfxapi/models/Order.py b/bfxapi/models/Order.py index e2a1532..28bf8f9 100644 --- a/bfxapi/models/Order.py +++ b/bfxapi/models/Order.py @@ -1,6 +1,22 @@ import time import datetime +class OrderType: + MARKET = 'market' + LIMIT = 'limit' + STOP = 'stop' + TRAILING_STOP = 'trailing-stop' + FILL_OR_KILL = 'fill-or-kill' + EXCHANGE_MARKET = 'exchange market' + EXCHANGE_LIMIT = 'exchange limit' + EXCHANGE_STOP = 'exchange stop' + EXCHANGE_TRAILING_STOP = 'exchange trailing-stop' + EXCHANGE_FILL_OR_KILL = 'exchange fill-or-kill' + +class OrderSide: + BUY = 'buy' + SELL = 'sell' + class OrderClosedModel: ID = 0 GID = 1 @@ -25,6 +41,30 @@ def now_in_mills(): return int(round(time.time() * 1000)) class Order: + """ + ID int64 Order ID + GID int Group ID + CID int Client Order ID + SYMBOL string Pair (tBTCUSD, …) + MTS_CREATE int Millisecond timestamp of creation + MTS_UPDATE int Millisecond timestamp of update + AMOUNT float Positive means buy, negative means sell. + AMOUNT_ORIG float Original amount + TYPE string The type of the order: LIMIT, MARKET, STOP, TRAILING STOP, EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK. + TYPE_PREV string Previous order type + FLAGS int Upcoming Params Object (stay tuned) + ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED + PRICE float Price + PRICE_AVG float Average price + PRICE_TRAILING float The trailing price + PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT) + HIDDEN int 1 if Hidden, 0 if not hidden + PLACED_ID int If another order caused this order to be placed (OCO) this will be that other order's ID + """ + + Type = OrderType() + Side = OrderSide() + def __init__(self, id, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId): self.id = id diff --git a/bfxapi/models/Position.py b/bfxapi/models/Position.py index e8bcdf0..f012993 100644 --- a/bfxapi/models/Position.py +++ b/bfxapi/models/Position.py @@ -1,9 +1,20 @@ class Position: + """ + SYMBOL string Pair (tBTCUSD, …). + STATUS string Status (ACTIVE, CLOSED). + ±AMOUNT float Size of the position. Positive values means a long position, negative values means a short position. + BASE_PRICE float The price at which you entered your position. + MARGIN_FUNDING float The amount of funding being used for this position. + MARGIN_FUNDING_TYPE int 0 for daily, 1 for term. + PL float Profit & Loss + PL_PERC float Profit & Loss Percentage + PRICE_LIQ float Liquidation price + LEVERAGE float Beta value + """ def __init__(self, symbol, status, amount, bPrice, mFunding, mFundingType, profit_loss, profit_loss_perc, lPrice, lev): - # [['tBTCUSD', 'ACTIVE', -2.37709587, 20720, -0.00066105, 0, 13115.99728968, 26.6296139, 104156.986252, -1.2332]] self.symbol = symbol self.status = status self.amount = amount diff --git a/bfxapi/models/Trade.py b/bfxapi/models/Trade.py index bcb0e83..3ce8d3d 100644 --- a/bfxapi/models/Trade.py +++ b/bfxapi/models/Trade.py @@ -1,6 +1,20 @@ import datetime class Trade: + """ + ID integer Trade database id + PAIR string Pair (BTCUSD, …) + MTS_CREATE integer Execution timestamp + ORDER_ID integer Order id + EXEC_AMOUNT float Positive means buy, negative means sell + EXEC_PRICE float Execution price + ORDER_TYPE string Order type + ORDER_PRICE float Order price + MAKER int 1 if true, 0 if false + FEE float Fee + FEE_CURRENCY string Fee currency + """ + SHORT = 'SHORT' LONG = 'LONG' From 43ad3d29c75292b2f5071d67402fe1361d142754 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 14:57:33 +0000 Subject: [PATCH 18/31] Adds more authenticated endpoints --- bfxapi/rest/BfxRest.py | 83 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/bfxapi/rest/BfxRest.py b/bfxapi/rest/BfxRest.py index 941bcbf..aca6bf3 100644 --- a/bfxapi/rest/BfxRest.py +++ b/bfxapi/rest/BfxRest.py @@ -5,7 +5,8 @@ import json from ..utils.CustomLogger import CustomLogger from ..utils.auth import generate_auth_headers -from ..models import Wallet, Order, Position, Trade +from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer +from ..models import FundingCredit class BfxRest: @@ -42,7 +43,7 @@ class BfxRest: return await resp.json() ################################################## - # Public # + # Public Data # ################################################## async def get_seed_candles(self, symbol): @@ -91,7 +92,7 @@ class BfxRest: return ticker ################################################## - # Authenticated # + # Authenticated Data # ################################################## async def get_wallets(self): @@ -120,3 +121,79 @@ class BfxRest: params = "?start={}&end={}&limit={}".format(start, end, limit) raw_trades = await self.post(endpoint, params=params) return [ Trade.from_raw_rest_trade(rt) for rt in raw_trades ] + + async def get_funding_offers(self, symbol): + endpoint = "auth/r/funding/offers/{}".format(symbol) + offers = await self.post(endpoint) + return [ FundingOffer.from_raw_offer(o) for o in offers ] + + async def get_funding_offer_history(self, symbol, start, end, limit=25): + endpoint = "auth/r/funding/offers/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}".format(start, end, limit) + offers = await self.post(endpoint, params=params) + return [ FundingOffer.from_raw_offer(o) for o in offers ] + + async def get_funding_loans(self, symbol): + endpoint = "auth/r/funding/loans/{}".format(symbol) + loans = await self.post(endpoint) + return [ FundingLoan.from_raw_loan(o) for o in loans ] + + async def get_funding_loan_history(self, symbol, start, end, limit=25): + endpoint = "auth/r/funding/loans/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}".format(start, end, limit) + loans = await self.post(endpoint, params=params) + return [ FundingLoan.from_raw_loan(o) for o in loans ] + + async def get_funding_credits(self, symbol): + endpoint = "auth/r/funding/credits/{}".format(symbol) + credits = await self.post(endpoint) + return [ FundingCredit.from_raw_credit(c) for c in credits] + + async def get_funding_credit_history(self, symbol, start, end, limit=25): + endpoint = "auth/r/funding/credits/{}/hist".format(symbol) + params = "?start={}&end={}&limit={}".format(start, end, limit) + credits = await self.post(endpoint, params=params) + return [ FundingCredit.from_raw_credit(c) for c in credits] + + ################################################## + # 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): + """ + Submit a new order + + @param symbol: the name of the symbol i.e 'tBTCUSD + @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) + """ + 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) From cecf9edcceee994e6ded5a5a99f64e947bbcd3ab Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 15:43:10 +0000 Subject: [PATCH 19/31] Add docstrings to functions --- bfxapi/models/Order.py | 4 +- bfxapi/rest/BfxRest.py | 139 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/bfxapi/models/Order.py b/bfxapi/models/Order.py index 28bf8f9..3c58cc1 100644 --- a/bfxapi/models/Order.py +++ b/bfxapi/models/Order.py @@ -137,5 +137,5 @@ class Order: def __str__(self): ''' Allow us to print the Order object in a pretty format ''' - return "Order <'{0}' mtsCreate={1} {2}>".format(self.symbol, self.mtsCreate, - self.status) + return "Order <'{}' mtsCreate={} status='{}' id={}>".format(self.symbol, self.mtsCreate, + self.status, self.id) diff --git a/bfxapi/rest/BfxRest.py b/bfxapi/rest/BfxRest.py index aca6bf3..499b7a0 100644 --- a/bfxapi/rest/BfxRest.py +++ b/bfxapi/rest/BfxRest.py @@ -69,76 +69,205 @@ class BfxRest: async def get_public_candles(self, symbol, start, end, section='hist', tf='1m', limit="100", sort=-1): + """ + Get all of the public candles between the start and end period. + + @param symbol symbol string: pair symbol i.e tBTCUSD + @param secton string: available values: "last", "hist" + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @param tf int: timeframe inbetween candles i.e 1m (min), ..., 1D (day), ... 1M (month) + @param sort int: if = 1 it sorts results returned with old > new + @return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ] + """ endpoint = "candles/trade:{}:{}/{}".format(tf, symbol, section) params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) candles = await self.fetch(endpoint, params=params) return candles async def get_public_trades(self, symbol, start, end, limit="100", sort=-1): + """ + Get all of the public trades between the start and end period. + + @param symbol symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array [ ID, MTS, AMOUNT, RATE, PERIOD? ] + """ endpoint = "trades/{}/hist".format(symbol) params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) trades = await self.fetch(endpoint, params=params) return trades async def get_public_books(self, symbol, precision="P0", length=25): + """ + Get the public orderbook for a given symbol. + + @param symbol symbol string: pair symbol i.e tBTCUSD + @param precision string: level of price aggregation (P0, P1, P2, P3, P4, R0) + @param length int: number of price points ("25", "100") + @return Array [ PRICE, COUNT, AMOUNT ] + """ endpoint = "book/{}/{}".format(symbol, precision) params = "?len={}".format(length) books = await self.fetch(endpoint, params) return books async def get_public_ticker(self, symbol): + """ + Get tickers for the given symbol. Tickers shows you the current best bid and ask, + as well as the last trade price. + + @parms symbols symbol string: pair symbol i.e tBTCUSD + @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, + LAST_PRICE, VOLUME, HIGH, LOW ] + """ endpoint = "ticker/{}".format(symbol) ticker = await self.fetch(endpoint) return ticker + async def get_public_tickers(self, symbols): + """ + Get tickers for the given symbols. Tickers shows you the current best bid and ask, + as well as the last trade price. + + @parms symbols Array: array of symbols i.e [tBTCUSD, tETHUSD] + @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, + LAST_PRICE, VOLUME, HIGH, LOW ] + """ + endpoint = "tickers/?symbols={}".format(','.join(symbols)) + ticker = await self.fetch(endpoint) + return ticker + ################################################## # Authenticated Data # ################################################## async def get_wallets(self): + """ + Get all wallets on account associated with API_KEY - Requires authentication. + + @return Array + """ endpoint = "auth/r/wallets" raw_wallets = await self.post(endpoint) return [ Wallet(*rw[:4]) for rw in raw_wallets ] async def get_active_orders(self, symbol): + """ + Get all of the active orders associated with API_KEY - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @return Array + """ endpoint = "auth/r/orders/{}".format(symbol) raw_orders = await self.post(endpoint) return [ Order.from_raw_order(ro) for ro in raw_orders ] async def get_order_history(self, symbol, start, end, limit=25, sort=-1): + """ + Get all of the orders between the start and end period associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array + """ endpoint = "auth/r/orders/{}/hist".format(symbol) params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) raw_orders = await self.post(endpoint, params=params) return [ Order.from_raw_order(ro) for ro in raw_orders ] async def get_active_position(self): + """ + Get all of the active position associated with API_KEY - Requires authentication. + + @return Array + """ endpoint = "auth/r/positions" raw_positions = await self.post(endpoint) return [ Position.from_raw_rest_position(rp) for rp in raw_positions ] + async def get_order_trades(self, symbol, order_id): + """ + Get all of the trades that have been generated by the given order associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param order_id string: id of the order + @return Array + """ + endpoint = "auth/r/order/{}:{}/trades".format(symbol, order_id) + raw_trades = await self.post(endpoint) + return [ Trade.from_raw_rest_trade(rt) for rt in raw_trades ] + async def get_trades(self, symbol, start, end, limit=25): + """ + Get all of the trades between the start and end period associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array + """ endpoint = "auth/r/trades/{}/hist".format(symbol) params = "?start={}&end={}&limit={}".format(start, end, limit) raw_trades = await self.post(endpoint, params=params) return [ Trade.from_raw_rest_trade(rt) for rt in raw_trades ] async def get_funding_offers(self, symbol): + """ + Get all of the funding offers associated with API_KEY - Requires authentication. + + @return Array + """ endpoint = "auth/r/funding/offers/{}".format(symbol) offers = await self.post(endpoint) return [ FundingOffer.from_raw_offer(o) for o in offers ] async def get_funding_offer_history(self, symbol, start, end, limit=25): + """ + Get all of the funding offers between the start and end period associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array + """ endpoint = "auth/r/funding/offers/{}/hist".format(symbol) params = "?start={}&end={}&limit={}".format(start, end, limit) offers = await self.post(endpoint, params=params) return [ FundingOffer.from_raw_offer(o) for o in offers ] async def get_funding_loans(self, symbol): + """ + Get all of the funding loans associated with API_KEY - Requires authentication. + + @return Array + """ endpoint = "auth/r/funding/loans/{}".format(symbol) loans = await self.post(endpoint) return [ FundingLoan.from_raw_loan(o) for o in loans ] async def get_funding_loan_history(self, symbol, start, end, limit=25): + """ + Get all of the funding loans between the start and end period associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array + """ endpoint = "auth/r/funding/loans/{}/hist".format(symbol) params = "?start={}&end={}&limit={}".format(start, end, limit) loans = await self.post(endpoint, params=params) @@ -150,6 +279,16 @@ class BfxRest: return [ FundingCredit.from_raw_credit(c) for c in credits] async def get_funding_credit_history(self, symbol, start, end, limit=25): + """ + Get all of the funding credits between the start and end period associated with API_KEY + - Requires authentication. + + @param symbol string: pair symbol i.e tBTCUSD + @param start int: millisecond start time + @param end int: millisecond end time + @param limit int: max number of items in response + @return Array + """ endpoint = "auth/r/funding/credits/{}/hist".format(symbol) params = "?start={}&end={}&limit={}".format(start, end, limit) credits = await self.post(endpoint, params=params) From d4c2942f22158b9a749afc4116a7a1af51c426c8 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 15:44:11 +0000 Subject: [PATCH 20/31] examples: add public get_tickers --- bfxapi/examples/rest/get_public_data.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bfxapi/examples/rest/get_public_data.py b/bfxapi/examples/rest/get_public_data.py index a3555f0..0509a9a 100644 --- a/bfxapi/examples/rest/get_public_data.py +++ b/bfxapi/examples/rest/get_public_data.py @@ -33,11 +33,17 @@ async def log_ticker(): print ("Ticker:") print (ticker) +async def log_mul_tickers(): + tickers = await bfx.rest.get_public_tickers(['tBTCUSD', 'tETHBTC']) + print ("Tickers:") + print (tickers) + async def run(): await log_historical_candles() await log_historical_trades() await log_books() await log_ticker() + await log_mul_tickers() t = asyncio.ensure_future(run()) asyncio.get_event_loop().run_until_complete(t) From 5a7ebb387434ac04c6453a2f81584210a4ea36f9 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Wed, 5 Dec 2018 15:44:33 +0000 Subject: [PATCH 21/31] examples: adds atuh funding functions --- .../examples/rest/get_authenticated_data.py | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/bfxapi/examples/rest/get_authenticated_data.py b/bfxapi/examples/rest/get_authenticated_data.py index 297b776..1fbd5ba 100644 --- a/bfxapi/examples/rest/get_authenticated_data.py +++ b/bfxapi/examples/rest/get_authenticated_data.py @@ -16,6 +16,9 @@ bfx = Client( rest_host='https://test.bitfinex.com/v2' ) +now = int(round(time.time() * 1000)) +then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago + async def log_wallets(): wallets = await bfx.rest.get_wallets() print ("Wallets:") @@ -27,9 +30,6 @@ async def log_active_orders(): [ print (o) for o in orders ] async def log_orders_history(): - now = int(round(time.time() * 1000)) - then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago - orders = await bfx.rest.get_order_history('tBTCUSD', 0, then) print ("Orders:") [ print (o) for o in orders ] @@ -40,19 +40,57 @@ async def log_active_positions(): [ print (p) for p in positions ] async def log_trades(): - now = int(round(time.time() * 1000)) - then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago - trades = await bfx.rest.get_trades('tBTCUSD', 0, then) - print ("Trades") + print ("Trades:") [ print (t) for t in trades] +async def log_order_trades(): + order_id = 1151353463 + trades = await bfx.rest.get_order_trades('tBTCUSD', order_id) + print ("Trade orders:") + [ print (t) for t in trades] + +async def log_funding_offers(): + offers = await bfx.rest.get_funding_offers('tBTCUSD') + print ("Offers:") + [ print (o) for o in offers] + +async def log_funding_offer_history(): + offers = await bfx.rest.get_funding_offer_history('tBTCUSD', 0, then) + print ("Offers history:") + [ print (o) for o in offers] + +async def log_funding_loans(): + loans = await bfx.rest.get_funding_loans('tBTCUSD') + print ("Funding loans:") + [ print (l) for l in loans ] + +async def log_funding_loans_history(): + loans = await bfx.rest.get_funding_loan_history('tBTCUSD', 0, then) + print ("Funding loan history:") + [ print (l) for l in loans ] + +async def log_funding_credits(): + credits = await bfx.rest.get_funding_credits('tBTCUSD') + print ("Funding credits:") + [ print (c) for c in credits ] + +async def log_funding_credits_history(): + credit = await bfx.rest.get_funding_credit_history('tBTCUSD', 0, then) + print ("Funding credit history:") + [ print (c) for c in credit ] + async def run(): await log_wallets() await log_active_orders() await log_orders_history() await log_active_positions() await log_trades() + await log_order_trades() + await log_funding_offers() + await log_funding_offer_history() + await log_funding_credits() + await log_funding_credits_history() t = asyncio.ensure_future(run()) From 7777989bc5a7009fc0296bf3f140c7a7b0a33433 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 11:30:23 +0000 Subject: [PATCH 22/31] ws.OrderManager: improve submit order --- bfxapi/websockets/OrderManager.py | 58 ++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index c7cecf0..226f1f5 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -95,17 +95,58 @@ class OrderManager: def _gen_unqiue_cid(self): return int(round(time.time() * 1000)) - async def submit_order(self, symbol, price, amount, market_type, - hidden=False, onConfirm=None, onClose=None, *args, **kwargs): + 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, + onConfirm=None, onClose=None, *args, **kwargs): + """ + Submit a new order + + @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 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 + @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 + was closed due to being filled or cancelled + """ cId = self._gen_unqiue_cid() - # send order over websocket + # create base payload with required data payload = { "cid": cId, "type": str(market_type), "symbol": symbol, "amount": str(amount), - "price": str(price) + "price": str(price), } + # caclulate and add flags + flags = self._calculate_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'] = oco_stop_price + if (time_in_force): + payload['tif'] = time_in_force + # submit the order self.pending_orders[cId] = payload self.pending_callbacks[cId] = (onConfirm, onClose) await self.bfxapi._send_auth_command('on', payload) @@ -161,3 +202,12 @@ class OrderManager: if time_in_force is not None: payload['time_in_force'] = str(time_in_force) await self.bfxapi._send_auth_command('ou', payload) + + 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 From 80efce2a6507106797aecb1fb97824983cc3bfa9 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 11:30:38 +0000 Subject: [PATCH 23/31] ws.OrderManager: improve update order --- bfxapi/websockets/OrderManager.py | 44 ++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index 226f1f5..0bbb8e2 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -153,12 +153,48 @@ class OrderManager: self.logger.info("Order cid={} ({} {} @ {}) dispatched".format( cId, symbol, amount, price)) - async def update_order(self, orderId, *args, onConfirm=None, onClose=None, **kwargs): - if orderId not in self.open_orders: - raise Exception("Order id={} is not open".format(orderId)) + 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, onClose=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 + @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 + was closed due to being filled or cancelled + """ order = self.open_orders[orderId] self.pending_callbacks[order.cId] = (onConfirm, onClose) - await order._update(orderId, *args, **kwargs) + 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 = self._calculate_flags(hidden, close, reduce_only, post_only, False) + payload['flags'] = flags + await self.bfxapi._send_auth_command('ou', payload) self.logger.info("Update Order order_id={} dispatched".format(orderId)) async def close_order(self, orderId, onConfirm=None, onClose=None): From 065873ced8cf433afce850de300874f5d7c66f9b Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 11:33:32 +0000 Subject: [PATCH 24/31] Rename close_order > cancel_order --- bfxapi/examples/ws/cancel_order.py | 2 +- bfxapi/examples/ws/send_order.py | 8 ++-- bfxapi/websockets/BfxWebsocket.py | 64 ++++-------------------------- bfxapi/websockets/OrderManager.py | 12 +++--- 4 files changed, 19 insertions(+), 67 deletions(-) diff --git a/bfxapi/examples/ws/cancel_order.py b/bfxapi/examples/ws/cancel_order.py index 2da2a44..b13b3af 100644 --- a/bfxapi/examples/ws/cancel_order.py +++ b/bfxapi/examples/ws/cancel_order.py @@ -24,7 +24,7 @@ async def trade_completed(order, trade): print ("Order confirmed.") print (order) print (trade) - await bfx.ws.close_order(order.id) + await bfx.ws.cancel_order(order.id) @bfx.ws.on('error') def log_error(msg): diff --git a/bfxapi/examples/ws/send_order.py b/bfxapi/examples/ws/send_order.py index e61442c..2af03c4 100644 --- a/bfxapi/examples/ws/send_order.py +++ b/bfxapi/examples/ws/send_order.py @@ -14,8 +14,8 @@ bfx = Client( ) @bfx.ws.on('order_snapshot') -async def close_all(data): - await bfx.ws.close_all_orders() +async def cancel_all(data): + await bfx.ws.cancel_all_orders() @bfx.ws.on('order_confirmed') async def trade_completed(order): @@ -24,9 +24,9 @@ async def trade_completed(order): ## close the order # await order.close() # or - # await bfx.ws.close_order(order.id) + # await bfx.ws.cancel_order(order.id) # or - # await bfx.ws.close_all_orders() + # await bfx.ws.cancel_all_orders() @bfx.ws.on('error') def log_error(msg): diff --git a/bfxapi/websockets/BfxWebsocket.py b/bfxapi/websockets/BfxWebsocket.py index 138b906..278a3c3 100644 --- a/bfxapi/websockets/BfxWebsocket.py +++ b/bfxapi/websockets/BfxWebsocket.py @@ -55,59 +55,11 @@ def _parse_trade(tData, symbol): } class BfxWebsocket(GenericWebsocket): - ''' + """ More complex websocket that heavily relies on the btfxwss module. This websocket requires authentication and is capable of handling orders. https://github.com/Crypto-toolbox/btfxwss - - Translation names: - - translation table for channel names: - Data Channels - os - Orders - hos - Historical Orders - ps - Positions - hts - Trades (snapshot) - te - Trade Executed - tu - Trade Execution update - ws - Wallets - bu - Balance Info - miu - Margin Info - fiu - Funding Info - fos - Offers - hfos - Historical Offers - fcs - Credits - hfcs - Historical Credits - fls - Loans - hfls - Historical Loans - htfs - Funding Trades - n - Notifications (WIP) - - Events: - - all: listen for all messages coming through - - connected: called when a connection is made - - authenticated: called when the websocket passes authentication - - notification (array): incoming account notification - - error (string): 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 - - order_confirmed (Order, Trade): when an order has been submitted and received - - wallet_snapshot (string): Initial wallet balances (Fired once) - - order_snapshot (string): Initial open orders (Fired once) - - positions_snapshot (string): Initial open positions (Fired once) - - wallet_update (string): changes to the balance of wallets - - seed_candle (candleArray): initial past candle to prime strategy - - seed_trade (tradeArray): initial past trade to prime strategy - - funding_offer_snapshot: - - funding_loan_snapshot: - - funding_credit_snapshot: - - balance_update when the state of a balance is changed - - new_trade: a new trade on the market has been executed - - new_candle: a new candle has been produced - - margin_info_update: new margin information has been broadcasted - - funding_info_update: new funding information has been broadcasted - ''' + """ ERRORS = { 10000: 'Unknown event', @@ -436,11 +388,11 @@ class BfxWebsocket(GenericWebsocket): async def update_order(self, *args, **kwargs): return await self.orderManager.update_order(*args, **kwargs) - async def close_order(self, *args, **kwargs): - return await self.orderManager.close_order(*args, **kwargs) + async def cancel_order(self, *args, **kwargs): + return await self.orderManager.cancel_order(*args, **kwargs) - async def close_all_orders(self, *args, **kwargs): - return await self.orderManager.close_all_orders(*args, **kwargs) + async def cancel_all_orders(self, *args, **kwargs): + return await self.orderManager.cancel_all_orders(*args, **kwargs) - async def close_order_multi(self, *args, **kwargs): - return await self.close_order_multi(*args, **kwargs) + async def cancel_order_multi(self, *args, **kwargs): + return await self.cancel_order_multi(*args, **kwargs) diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index 0bbb8e2..ada2622 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -197,19 +197,19 @@ class OrderManager: await self.bfxapi._send_auth_command('ou', payload) self.logger.info("Update Order order_id={} dispatched".format(orderId)) - async def close_order(self, orderId, onConfirm=None, onClose=None): + async def cancel_order(self, orderId, onConfirm=None, onClose=None): if orderId not in self.open_orders: raise Exception("Order id={} is not open".format(orderId)) order = self.open_orders[orderId] self.pending_callbacks[order.cId] = (onConfirm, onClose) - await self._close_order(orderId) + await self._cancel_order(orderId) self.logger.info("Order cancel order_id={} dispatched".format(orderId)) - async def close_all_orders(self): + async def cancel_all_orders(self): ids = [self.open_orders[x].id for x in self.open_orders] - await self.close_order_multi(ids) + await self.cancel_order_multi(ids) - async def close_order_multi(self, orderIds): + async def cancel_order_multi(self, orderIds): task_batch = [] for oid in orderIds: task_batch += [ @@ -217,7 +217,7 @@ class OrderManager: ] await asyncio.wait(*[ task_batch ]) - async def _close_order(self, orderId): + async def _cancel_order(self, orderId): await self.bfxapi._send_auth_command('oc', { 'id': orderId }) async def _update(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, From 2f10d4f356ce5cab41d92ca0381deeaa3711a105 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 12:09:50 +0000 Subject: [PATCH 25/31] SubscriptionManager: add docstrings --- bfxapi/websockets/SubscriptionManager.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bfxapi/websockets/SubscriptionManager.py b/bfxapi/websockets/SubscriptionManager.py index af15bf9..92b1c8b 100644 --- a/bfxapi/websockets/SubscriptionManager.py +++ b/bfxapi/websockets/SubscriptionManager.py @@ -16,6 +16,14 @@ class SubscriptionManager: self.logger = CustomLogger('BfxSubscriptionManager', logLevel=logLevel) async def subscribe(self, channel_name, symbol, timeframe=None, **kwargs): + """ + Subscribe to a new channel + + @param channel_name: the name of the channel i.e 'books', 'candles' + @param symbol: the trading symbol i.e 'tBTCUSD' + @param timeframe: sepecifies the data timeframe between each candle (only required + for the candles channel) + """ # create a new subscription subscription = Subscription(self.bfxapi.ws, channel_name, symbol, timeframe, **kwargs) self.logger.info("Subscribing to channel {}".format(channel_name)) @@ -61,6 +69,12 @@ class SubscriptionManager: return self.subscriptions_chanid[chanId] async def unsubscribe(self, chanId, onComplete=None): + """ + Unsubscribe from the channel with the given chanId + + @param onComplete: function called when the bitfinex websocket resoponds with + a signal that confirms the subscription has been unsubscribed to + """ sub = self.subscriptions_chanid[chanId] if onComplete: self.unsubscribe_callbacks[sub.sub_id] = onComplete @@ -68,6 +82,11 @@ class SubscriptionManager: await self.subscriptions_chanid[chanId].unsubscribe() async def resubscribe(self, chanId): + """ + Unsubscribes and then subscribes to the channel with the given Id + + This function is mostly used to force the channel to produce a fresh snapshot. + """ sub = self.subscriptions_chanid[chanId] async def re_sub(): await sub.subscribe() @@ -79,11 +98,17 @@ class SubscriptionManager: await sub.subscribe() def is_subscribed(self, chanId): + """ + Returns True if the channel with the given chanId is currenly subscribed to + """ if chanId not in self.subscriptions_chanid: return False return self.subscriptions_chanid[chanId].is_subscribed() async def unsubscribe_all(self): + """ + Unsubscribe from all channels. + """ task_batch = [] for chanId in self.subscriptions_chanid: sub = self.get(chanId) @@ -94,6 +119,9 @@ class SubscriptionManager: await asyncio.wait(*[ task_batch ]) async def resubscribe_all(self): + """ + Unsubscribe and then subscribe to all channels + """ task_batch = [] for chanId in self.subscriptions_chanid: task_batch += [ From bfb8a62fe2f1d60b1cedb908e8f81494685cf0e0 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 12:10:24 +0000 Subject: [PATCH 26/31] Order: add order flags enum --- bfxapi/models/Order.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bfxapi/models/Order.py b/bfxapi/models/Order.py index 3c58cc1..05daa9a 100644 --- a/bfxapi/models/Order.py +++ b/bfxapi/models/Order.py @@ -37,6 +37,13 @@ class OrderClosedModel: NOTIFY = 23 PLACE_ID = 25 +class OrderFlags: + HIDDEN = 64 + CLOSE = 12 + REDUCE_ONLY = 1024 + POST_ONLY = 4096 + OCO = 16384 + def now_in_mills(): return int(round(time.time() * 1000)) @@ -64,6 +71,7 @@ class Order: Type = OrderType() Side = OrderSide() + Flags = OrderFlags() def __init__(self, id, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId): From d0f2e81ecd131b9ea04390e8cd4849851f7aab5c Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 12:10:46 +0000 Subject: [PATCH 27/31] OrderManager: add docstrings to cancel functions --- bfxapi/websockets/OrderManager.py | 53 ++++++++++++++----------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/bfxapi/websockets/OrderManager.py b/bfxapi/websockets/OrderManager.py index ada2622..359d4aa 100644 --- a/bfxapi/websockets/OrderManager.py +++ b/bfxapi/websockets/OrderManager.py @@ -24,10 +24,10 @@ class OrderManager: return list(self.pending_orders.values()) async def _confirm_order(self, order, isClosed=False): - ''' - Called once when we first recieve infomation back from the bitfinex api - that the order has been accepted. - ''' + """ + Called every time an order signal has been received. This function + manages the local list of open orders. + """ if order.cId in self.pending_orders: if self.pending_callbacks[order.cId][0]: # call onConfirm callback @@ -198,18 +198,35 @@ class OrderManager: self.logger.info("Update Order order_id={} dispatched".format(orderId)) async def cancel_order(self, orderId, onConfirm=None, onClose=None): - if orderId not in self.open_orders: - raise Exception("Order id={} is not open".format(orderId)) + """ + Cancel an existing open order + + @param orderId: the id of the order that you want to update + @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 + was closed due to being filled or cancelled + """ order = self.open_orders[orderId] self.pending_callbacks[order.cId] = (onConfirm, onClose) - await self._cancel_order(orderId) + await self.bfxapi._send_auth_command('oc', { 'id': orderId }) self.logger.info("Order cancel order_id={} dispatched".format(orderId)) async def cancel_all_orders(self): + """ + Cancel all existing open orders + + This function closes orders that have been tracked locally by the OrderManager. + """ ids = [self.open_orders[x].id for x in self.open_orders] await self.cancel_order_multi(ids) async def cancel_order_multi(self, orderIds): + """ + Cancel existing open orders as a batch + + @param orderIds: an array of order ids + """ task_batch = [] for oid in orderIds: task_batch += [ @@ -217,28 +234,6 @@ class OrderManager: ] await asyncio.wait(*[ task_batch ]) - async def _cancel_order(self, orderId): - await self.bfxapi._send_auth_command('oc', { 'id': orderId }) - - async def _update(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, - price_trailing=None, flags=None, time_in_force=None): - 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 flags is not None: - payload['flags'] = str(flags) - if time_in_force is not None: - payload['time_in_force'] = str(time_in_force) - await self.bfxapi._send_auth_command('ou', payload) - def _calculate_flags(self, hidden, close, reduce_only, post_only, oco): flags = 0 flags = flags + Order.Flags.HIDDEN if hidden else flags From 1e6f9b2356f874fb0dc71d24bcf9a1d619aab48a Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 13:12:02 +0000 Subject: [PATCH 28/31] examples: use Order.Type enum in order submit --- bfxapi/examples/ws/cancel_order.py | 4 ++-- bfxapi/examples/ws/send_order.py | 4 ++-- bfxapi/examples/ws/update_order.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bfxapi/examples/ws/cancel_order.py b/bfxapi/examples/ws/cancel_order.py index b13b3af..18d0703 100644 --- a/bfxapi/examples/ws/cancel_order.py +++ b/bfxapi/examples/ws/cancel_order.py @@ -2,7 +2,7 @@ import os import sys sys.path.append('../') -from bfxapi import Client +from bfxapi import Client, Order API_KEY=os.getenv("BFX_KEY") API_SECRET=os.getenv("BFX_SECRET") @@ -33,6 +33,6 @@ 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 - await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') + await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT) bfx.ws.run() diff --git a/bfxapi/examples/ws/send_order.py b/bfxapi/examples/ws/send_order.py index 2af03c4..50ae345 100644 --- a/bfxapi/examples/ws/send_order.py +++ b/bfxapi/examples/ws/send_order.py @@ -2,7 +2,7 @@ import os import sys sys.path.append('../') -from bfxapi import Client +from bfxapi import Client, Order API_KEY=os.getenv("BFX_KEY") API_SECRET=os.getenv("BFX_SECRET") @@ -34,7 +34,7 @@ def log_error(msg): @bfx.ws.on('authenticated') async def submit_order(auth_message): - await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT') + await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, Order.Type.EXCHANGE_MARKET) # If you dont want to use a decorator # ws.on('authenticated', submit_order) diff --git a/bfxapi/examples/ws/update_order.py b/bfxapi/examples/ws/update_order.py index 59abee5..f925b06 100644 --- a/bfxapi/examples/ws/update_order.py +++ b/bfxapi/examples/ws/update_order.py @@ -2,7 +2,7 @@ import os import sys sys.path.append('../') -from bfxapi import Client +from bfxapi import Client, Order API_KEY=os.getenv("BFX_KEY") API_SECRET=os.getenv("BFX_SECRET") @@ -36,6 +36,6 @@ 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 - await bfx.ws.submit_order('tBTCUSD', 10, 1, 'EXCHANGE LIMIT') + await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT) bfx.ws.run() From c27b9c132e228be0aaa2ee1e551a185d19c070e6 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 13:12:19 +0000 Subject: [PATCH 29/31] bfxpi: expose models in __init__ --- bfxapi/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bfxapi/__init__.py b/bfxapi/__init__.py index 71fb092..9c22fe5 100644 --- a/bfxapi/__init__.py +++ b/bfxapi/__init__.py @@ -2,3 +2,4 @@ name = 'bfxapi' from bfxapi.Client import Client from bfxapi.websockets.GenericWebsocket import GenericWebsocket +from bfxapi.models import * From 597e51ee1b17698c127c6521016a5e800be91b98 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 13:12:52 +0000 Subject: [PATCH 30/31] models.Order: fix type enum --- bfxapi/models/Order.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bfxapi/models/Order.py b/bfxapi/models/Order.py index 05daa9a..19bded4 100644 --- a/bfxapi/models/Order.py +++ b/bfxapi/models/Order.py @@ -2,16 +2,16 @@ import time import datetime class OrderType: - MARKET = 'market' - LIMIT = 'limit' - STOP = 'stop' - TRAILING_STOP = 'trailing-stop' - FILL_OR_KILL = 'fill-or-kill' - EXCHANGE_MARKET = 'exchange market' - EXCHANGE_LIMIT = 'exchange limit' - EXCHANGE_STOP = 'exchange stop' - EXCHANGE_TRAILING_STOP = 'exchange trailing-stop' - EXCHANGE_FILL_OR_KILL = 'exchange fill-or-kill' + MARKET = 'MARKET' + LIMIT = 'LIMIT' + STOP = 'STOP' + TRAILING_STOP = 'TRAILING STOP' + FILL_OR_KILL = 'FOK' + EXCHANGE_MARKET = 'EXCHANGE MARKET' + EXCHANGE_LIMIT = 'EXCHANGE LIMIT' + EXCHANGE_STOP = 'EXCHANGE STOP' + EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP' + EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK' class OrderSide: BUY = 'buy' From f38879cfdaea5d3dcfd31392ae313256d5197fa7 Mon Sep 17 00:00:00 2001 From: Jacob Plaster Date: Thu, 6 Dec 2018 13:13:23 +0000 Subject: [PATCH 31/31] README: add rest interface to documentation --- README.md | 129 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 4d38965..52b5e33 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ bfx.ws.run() ``` -# Official Python `bfxapi` +# Bitfinex Trading API for Python. Bitcoin, Ether and Litecoin trading This is an official python library that is used to connect interact with the Bitfinex api. Currently it only has support for websockets but will soon have Rest functionality as well. Install dependencies @@ -62,7 +62,8 @@ bfx.ws.run() ### Submit limit order ```python -await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT') +from bfxapi import Order +await bfx.ws.submit_order('tBTCUSD', 100, 0.01, Order.Type.EXCHANGE_MARKET) ``` ### Listen for completion @@ -74,11 +75,22 @@ async def order_completed(order, trade): ### Get wallets ```python -wallets = bfxapi.wallets.get_wallets() +wallets = bfxapi.ws.wallets.get_wallets() # [ Wallet <'exchange_BTC' balance='41.25809589' unsettled='0'>, # Wallet <'exchange_USD' balance='62761.86070104' unsettled='0'> ] ``` +### Order manipulation +All order function support onConfirm and onClose async callbacks. onConfirm is fired when we receive a signal from the websocket that the order has been confirmed. onClose is fired when we receive a signal that the order has either been filled or canceled. + +```python + +async def on_confirm(order): + await bfx.ws.update_order(order.id, price=1000) + +await bfx.ws.submit_order('tBTCUSD', 800, 0.1, onConfirm=on_confirm) +``` + ### Close all orders ```python await bfx.ws.close_all_orders() @@ -118,7 +130,7 @@ The websocket exposes a collection of events that are triggered when certain dat -# `bfxapi.ws` interface +# bfxapi ws interface #### `on(event, function)` @@ -168,35 +180,13 @@ The websocket exposes a collection of events that are triggered when certain dat Takes an array of orderIds and closes them all. -# `bfxapi.wallets` +## bfxapi.ws.wallets ### `get_wallets()` Returns an array of wallets that represent the users balance in the different currencies -# `Order obj` - -### `close()` - - Signals Bitfinex to close the order - -### `update(self, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None)` - - Signals Bitfinex to update the order with the given values - -### `isOpen()` - - Returns true if the order has not been closed - -### `isPending()` - - Returns true if Bitfinex has not responded to confirm the order has been received - -### `isConfirmed()` - - Returns true if Bitfinex has responded to confirm the order - -# `Subscription obj` +## `bfx.ws.Subscription obj` ### `subscribe()` @@ -210,7 +200,88 @@ The websocket exposes a collection of events that are triggered when certain dat Returns true if the subscription is open and receiving data +# bfxapi rest interface + +### `get_public_candles(symbol, start, end, section='hist', tf='1m', limit="100", sort=-1 ` + + Get All of the public candles between the given start and end period + +### `get_public_trades(symbol, start, end, limit="100", sort=-1)` + +Get all of the public trades between the start and end period + +### `get_public_books(symbol, precision="P0", length=25)` + +Get the public orderbook of a given symbol + +### `get_public_ticker(symbol)` + + Get tickers for the given symbol. Tickers shows you the current best bid and ask, + as well as the last trade price. + +### `get_public_tickers(symbols)` + + Get tickers for the given symbols. Tickers shows you the current best bid and ask, + as well as the last trade price. + +### `get_wallets()` + + Get all wallets on account associated with API_KEY - Requires authentication. + +### `get_active_orders(symbol)` + + Get all of the active orders associated with API_KEY - Requires authentication. + +### `get_order_history(symbol, start, end, limit=25, sort=-1)` + + Get all of the orders between the start and end period associated with API_KEY + - Requires authentication. + +### `get_active_positions()` + + Get all of the active position associated with API_KEY - Requires authentication. + +### `get_order_trades(symbol, order_id)` + + Get all of the trades that have been generated by the given order associated with API_KEY - Requires authentication. + +### `get_trades(symbol, start, end, limit=25)` + + Get all of the trades between the start and end period associated with API_KEY + - Requires authentication. + +### `get_funding_offers(symbol) + + Get all of the funding offers associated with API_KEY - Requires authentication. + +### `get_funding_offer_history(symbol, start, end, limit=25)` + + Get all of the funding offers between the start and end period associated with API_KEY - Requires authentication. + +### `get_funding_loans(symbol)` + + Get all of the funding loans associated with API_KEY - Requires authentication. + +### `get_funding_loan_history(symbol, start, end, limit=25)` + + Get all of the funding loans between the start and end period associated with API_KEY - Requires authentication. + +### `get_funding_credits(symbol)` + + Get all of the funding credits associated with API_KEY - Requires authentication. + +### `get_funding_credit_history(symbol, start, end, limit=25)` + + Get all of the funding credits between the start and end period associated with API_KEY - Requires authentication. # Examples -For more info on how to use this library please see the example scripts in the `bfxapi/examples` directory. +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. + +## Contributing + +1. Fork it ( https://github.com/[my-github-username]/bitfinex/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request