mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-19 14:54:21 +01:00
Merge pull request #2 from JacobPlaster/add-rest-services
Add rest interface to bfxapi
This commit is contained in:
153
README.md
153
README.md
@@ -1,5 +1,5 @@
|
||||
|
||||
```
|
||||
```python
|
||||
from bfxapi import Client
|
||||
|
||||
bfx = Client(
|
||||
@@ -20,15 +20,15 @@ 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
|
||||
```
|
||||
```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='<YOUR_API_KEY>'
|
||||
API_SECRET='<YOUR_API_SECRET>'
|
||||
@@ -61,26 +61,38 @@ bfx.ws.run()
|
||||
|
||||
### Submit limit order
|
||||
|
||||
```
|
||||
await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE LIMIT')
|
||||
```python
|
||||
from bfxapi import Order
|
||||
await bfx.ws.submit_order('tBTCUSD', 100, 0.01, Order.Type.EXCHANGE_MARKET)
|
||||
```
|
||||
|
||||
### Listen for completion
|
||||
```
|
||||
```python
|
||||
@bfx.ws.on('order_confirmed')
|
||||
async def order_completed(order, trade):
|
||||
print ("Order has been confrmed")
|
||||
```
|
||||
|
||||
### Get wallets
|
||||
```
|
||||
wallets = bfxapi.wallets.get_wallets()
|
||||
```python
|
||||
wallets = bfxapi.ws.wallets.get_wallets()
|
||||
# [ Wallet <'exchange_BTC' balance='41.25809589' unsettled='0'>,
|
||||
# Wallet <'exchange_USD' balance='62761.86070104' unsettled='0'> ]
|
||||
```
|
||||
|
||||
### Close all orders
|
||||
### 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)`
|
||||
|
||||
@@ -148,15 +160,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,3 +2,4 @@ name = 'bfxapi'
|
||||
|
||||
from bfxapi.Client import Client
|
||||
from bfxapi.websockets.GenericWebsocket import GenericWebsocket
|
||||
from bfxapi.models import *
|
||||
|
||||
97
bfxapi/examples/rest/get_authenticated_data.py
Normal file
97
bfxapi/examples/rest/get_authenticated_data.py
Normal file
@@ -0,0 +1,97 @@
|
||||
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'
|
||||
)
|
||||
|
||||
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:")
|
||||
[ 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():
|
||||
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():
|
||||
trades = await bfx.rest.get_trades('tBTCUSD', 0, then)
|
||||
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())
|
||||
asyncio.get_event_loop().run_until_complete(t)
|
||||
49
bfxapi/examples/rest/get_public_data.py
Normal file
49
bfxapi/examples/rest/get_public_data.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import time
|
||||
sys.path.append('../')
|
||||
|
||||
from bfxapi import Client
|
||||
|
||||
bfx = Client(
|
||||
logLevel='DEBUG',
|
||||
)
|
||||
|
||||
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 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)
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -14,20 +14,19 @@ 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, trade):
|
||||
async def trade_completed(order):
|
||||
print ("Order confirmed.")
|
||||
print (order)
|
||||
print (trade)
|
||||
## 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):
|
||||
@@ -35,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)
|
||||
@@ -43,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()
|
||||
@@ -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")
|
||||
@@ -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')
|
||||
@@ -38,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()
|
||||
86
bfxapi/models/FundingCredit.py
Normal file
86
bfxapi/models/FundingCredit.py
Normal file
@@ -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 '{}' <id={} rate={} amount={} period={} status='{}'>".format(
|
||||
self.symbol, self.id, self.rate, self.amount, self.period, self.status)
|
||||
82
bfxapi/models/FundingLoan.py
Normal file
82
bfxapi/models/FundingLoan.py
Normal file
@@ -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 '{}' <id={} rate={} amount={} period={} status='{}'>".format(
|
||||
self.symbol, self.id, self.rate, self.amount, self.period, self.status)
|
||||
74
bfxapi/models/FundingOffer.py
Normal file
74
bfxapi/models/FundingOffer.py
Normal file
@@ -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 '{}' <id={} rate={} period={} status='{}'>".format(
|
||||
self.symbol, self.id, self.rate, self.period, self.status)
|
||||
@@ -1,4 +1,21 @@
|
||||
import time
|
||||
import datetime
|
||||
|
||||
class OrderType:
|
||||
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'
|
||||
SELL = 'sell'
|
||||
|
||||
class OrderClosedModel:
|
||||
ID = 0
|
||||
@@ -20,55 +37,95 @@ 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))
|
||||
|
||||
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]
|
||||
"""
|
||||
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()
|
||||
Flags = OrderFlags()
|
||||
|
||||
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
|
||||
@@ -88,5 +145,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)
|
||||
|
||||
36
bfxapi/models/Position.py
Normal file
36
bfxapi/models/Position.py
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
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):
|
||||
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 {} <Sstatus='{}' p&l={}>".format(
|
||||
self.symbol, self.base_price, self.amount, self.status, self.profit_loss)
|
||||
@@ -1,20 +1,44 @@
|
||||
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'
|
||||
|
||||
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={} <order='{}'>".format(
|
||||
self.amount, self.price, self.fee, self.order)
|
||||
return "Trade '{}' x {} @ {} <direction='{}' fee={}>".format(
|
||||
self.pair, self.amount, self.price, self.direction, self.fee)
|
||||
|
||||
@@ -5,3 +5,7 @@ from .Trade import *
|
||||
from .OrderBook import *
|
||||
from .Subscription import *
|
||||
from .Wallet import *
|
||||
from .Position import *
|
||||
from .FundingLoan import *
|
||||
from .FundingOffer import *
|
||||
from .FundingCredit import *
|
||||
|
||||
@@ -4,24 +4,47 @@ import time
|
||||
import json
|
||||
|
||||
from ..utils.CustomLogger import CustomLogger
|
||||
from ..utils.auth import generate_auth_headers
|
||||
from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer
|
||||
from ..models import FundingCredit
|
||||
|
||||
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)
|
||||
|
||||
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('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()
|
||||
|
||||
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 + 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 Data #
|
||||
##################################################
|
||||
|
||||
async def get_seed_candles(self, symbol):
|
||||
endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol)
|
||||
@@ -43,3 +66,273 @@ class BfxRest:
|
||||
candles.sort(key=lambda x: x[0], reverse=True)
|
||||
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):
|
||||
"""
|
||||
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<string>: 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 <models.Wallet>
|
||||
"""
|
||||
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 <models.Order>
|
||||
"""
|
||||
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 <models.Order>
|
||||
"""
|
||||
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 <models.Position>
|
||||
"""
|
||||
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 <models.Trade>
|
||||
"""
|
||||
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 <models.Trade>
|
||||
"""
|
||||
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 <models.FundingOffer>
|
||||
"""
|
||||
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 <models.FundingOffer>
|
||||
"""
|
||||
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 <models.FundingLoan>
|
||||
"""
|
||||
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 <models.FundingLoan>
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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 <models.FundingCredit>
|
||||
"""
|
||||
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)
|
||||
|
||||
37
bfxapi/utils/auth.py
Normal file
37
bfxapi/utils/auth.py
Normal file
@@ -0,0 +1,37 @@
|
||||
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)
|
||||
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))
|
||||
@@ -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:
|
||||
@@ -56,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',
|
||||
@@ -142,7 +93,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()
|
||||
@@ -392,18 +343,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):
|
||||
@@ -448,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)
|
||||
|
||||
@@ -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,20 +23,22 @@ class OrderManager:
|
||||
def get_pending_orders(self):
|
||||
return list(self.pending_orders.values())
|
||||
|
||||
async def _confirm_order(self, order, trade):
|
||||
'''
|
||||
Called once when we first recieve infomation back from the bitfinex api
|
||||
that the order has been accepted.
|
||||
'''
|
||||
async def _confirm_order(self, order, isClosed=False):
|
||||
"""
|
||||
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 onComplete callback
|
||||
await self.pending_callbacks[order.cId][0](order, trade)
|
||||
# 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, trade)
|
||||
self.bfxapi._emit('order_confirmed', order)
|
||||
|
||||
async def confirm_order_closed(self, raw_ws_data):
|
||||
# order created and executed
|
||||
@@ -44,14 +46,13 @@ 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])
|
||||
trade = Trade(order)
|
||||
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]
|
||||
await self._confirm_order(order, trade)
|
||||
await self._confirm_order(order, isClosed=True)
|
||||
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):
|
||||
'''
|
||||
@@ -60,11 +61,9 @@ class OrderManager:
|
||||
osData = raw_ws_data[2]
|
||||
self.open_orders = {}
|
||||
for raw_order in osData:
|
||||
order = Order(self.bfxapi, raw_order)
|
||||
trade = Trade(order)
|
||||
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, trade)
|
||||
self.bfxapi._emit('order_snapshot', self.get_open_orders())
|
||||
|
||||
async def confirm_order_update(self, raw_ws_data):
|
||||
@@ -73,13 +72,12 @@ 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)
|
||||
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
|
||||
@@ -87,58 +85,160 @@ 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)
|
||||
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))
|
||||
|
||||
async def submit_order(self, symbol, price, amount, market_type,
|
||||
hidden=False, onComplete=None, onError=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] = (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):
|
||||
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] = (onComplete, onError)
|
||||
await order.update(*args, **kwargs)
|
||||
self.pending_callbacks[order.cId] = (onConfirm, onClose)
|
||||
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, onComplete=None, onError=None):
|
||||
if orderId not in self.open_orders:
|
||||
raise Exception("Order id={} is not open".format(orderId))
|
||||
async def cancel_order(self, orderId, onConfirm=None, onClose=None):
|
||||
"""
|
||||
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] = (onComplete, onError)
|
||||
await order.cancel()
|
||||
self.pending_callbacks[order.cId] = (onConfirm, onClose)
|
||||
await self.bfxapi._send_auth_command('oc', { 'id': orderId })
|
||||
self.logger.info("Order cancel order_id={} dispatched".format(orderId))
|
||||
|
||||
async def close_all_orders(self):
|
||||
ids = [self.open_orders[x].id for x in self.open_orders]
|
||||
await self.close_order_multi(ids)
|
||||
async def cancel_all_orders(self):
|
||||
"""
|
||||
Cancel all existing open orders
|
||||
|
||||
async def close_order_multi(self, orderIds):
|
||||
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 += [
|
||||
asyncio.ensure_future(self.open_orders[oid].close())
|
||||
]
|
||||
await asyncio.wait(*[ task_batch ])
|
||||
|
||||
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
|
||||
|
||||
@@ -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 += [
|
||||
|
||||
Reference in New Issue
Block a user