Merge branch 'master' into readme

This commit is contained in:
Jacob Plaster
2019-09-24 11:49:58 +01:00
committed by GitHub
20 changed files with 736 additions and 85 deletions

View File

@@ -1,3 +1,16 @@
1.1.0
- Adds rest.submit_funding_offer
- Adds rest.submit_cancel_funding_offer
- Adds rest.submit_wallet_transfer
- Adds rest.get_wallet_deposit_address
- Adds rest.create_wallet_deposit_address
- Adds rest.submit_wallet_withdraw
- Adds rest.submit_order
- Adds rest.submit_cancel_order
- Adds rest.submit_update_order
- Updates websocket notification event to use Notfication model object
1.0.1
- Added ws event `status_update`

View File

@@ -45,13 +45,12 @@ python3 subscribe_trades_candles.py
```
# Features
- Fast websocket connection
- Event-based routing
- Subscribe to trade, candles and orderbook channels
- Authenticate with API key/secret
- Orderbook checksum validation
- Wbsocket support with multiplexing
- Real-time Trade, Orderbook, Account data feeds and more
- Authenticate with api key/secret
- Data checksum verifications
- Create, update and close orders
- Track wallet updates
- Wallet withdraws/transfers
# Quickstart
@@ -116,7 +115,7 @@ The websocket exposes a collection of events that are triggered when certain dat
- `disconnected`: () called when a connection is ended (A reconnect attempt may follow)
- `stopped`: () called when max amount of connection retries is met and the socket is closed
- `authenticated` (): called when the websocket passes authentication
- `notification` (array): incoming account notification
- `notification` (Notification): incoming account notification
- `error` (array): error from the websocket
- `order_closed` (Order, Trade): when an order has been closed
- `order_new` (Order, Trade): when an order has been created but not closed. Note: will not be called if order is executed and filled instantly
@@ -177,7 +176,7 @@ The websocket exposes a collection of events that are triggered when certain dat
#### `submit_order(symbol, price, amount, market_type, hidden=False, onConfirm=None, onClose=None, *args, **kwargs)`
Submits an order to the Bitfinex api. When it has been verified that bitfine xhas received the order then the `onConfirm` callback will be called followed by the `order_confirmed` event. Once it has been verified that the order has completely closed due to either being filled or canceled then the `onClose` function will be called, followed by the `order_closed` event.
Submits an order to the Bitfinex api. When it has been verified that bitfinex has received the order then the `onConfirm` callback will be called followed by the `order_confirmed` event. Once it has been verified that the order has completely closed due to either being filled or canceled then the `onClose` function will be called, followed by the `order_closed` event.
#### `update_order(orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None, onConfirm=None, onClose=None)`
@@ -301,6 +300,42 @@ Get the public orderbook of a given symbol
Set the amount of collateral used to back a derivative position.
#### `submit_order(symbol, price, amount, market_type, hidden=False, *args, **kwargs)`
Submits an order to the Bitfinex api.
#### `update_order(orderId, price=None, amount=None, delta=None, price_aux_limit=None, price_trailing=None, flags=None, time_in_force=None)`
Attempts to update an order with the given values. If the order is no longer open then the update will be ignored.
#### `close_order(self, orderId):`
Close the order with the given orderId if it still open.
#### `submit_wallet_withdraw(wallet, method, amount, address):`
Withdraw funds from the given wallet to the provided address
#### `create_wallet_deposit_address(wallet, method):`
Create a new deposit address for the given wallet and currency protocol.
#### `get_wallet_deposit_address(wallet, method):`
Get the deposit address for the given wallet and currency protocol
#### `submit_wallet_transfer(from_wallet, to_wallet, from_currency, to_currency, amount):`
Transfer funds from one internal wallet to another
#### `submit_cancel_funding_offer(fundingId):`
Cancel a funding offer
### `submit_funding_offer(self, symbol, amount, rate, period, funding_type, hidden=False):`
Submit a new fund offer
# Examples
For more info on how to use this library please see the example scripts in the `bfxapi/examples` directory. Here you will find usage of all interface exposed functions for both the rest and websocket.

View File

@@ -0,0 +1,35 @@
import os
import sys
import asyncio
import time
sys.path.append('../')
from bfxapi import Client
API_KEY=os.getenv("BFX_KEY")
API_SECRET=os.getenv("BFX_SECRET")
bfx = Client(
API_KEY=API_KEY,
API_SECRET=API_SECRET,
logLevel='DEBUG'
)
async def create_funding():
response = await bfx.rest.submit_funding_offer("fUSD", 1000, 0.012, 7)
# response is in the form of a Notification object
# notify_info is in the form of a FundingOffer
print ("Offer: ", response.notify_info)
async def cancel_funding():
response = await bfx.rest.submit_cancel_funding_offer(41235958)
# response is in the form of a Notification object
# notify_info is in the form of a FundingOffer
print ("Offer: ", response.notify_info)
async def run():
await create_funding()
await cancel_funding()
t = asyncio.ensure_future(run())
asyncio.get_event_loop().run_until_complete(t)

View File

@@ -0,0 +1,43 @@
import os
import sys
import asyncio
import time
sys.path.append('../')
from bfxapi import Client
API_KEY=os.getenv("BFX_KEY")
API_SECRET=os.getenv("BFX_SECRET")
bfx = Client(
API_KEY=API_KEY,
API_SECRET=API_SECRET,
logLevel='DEBUG'
)
async def create_order():
response = await bfx.rest.submit_order("tBTCUSD", 10, 0.1)
# response is in the form of a Notification object
for o in response.notify_info:
# each item is in the form of an Order object
print ("Order: ", o)
async def cancel_order():
response = await bfx.rest.submit_cancel_order(1185510865)
# response is in the form of a Notification object
# notify_info is in the form of an order object
print ("Order: ", response.notify_info)
async def update_order():
response = await bfx.rest.submit_update_order(1185510771, price=15, amount=0.055)
# response is in the form of a Notification object
# notify_info is in the form of an order object
print ("Order: ", response.notify_info)
async def run():
await create_order()
await cancel_order()
await update_order()
t = asyncio.ensure_future(run())
asyncio.get_event_loop().run_until_complete(t)

View File

@@ -0,0 +1,49 @@
import os
import sys
import asyncio
import time
sys.path.append('../')
from bfxapi import Client
API_KEY=os.getenv("BFX_KEY")
API_SECRET=os.getenv("BFX_SECRET")
bfx = Client(
API_KEY=API_KEY,
API_SECRET=API_SECRET,
logLevel='DEBUG'
)
async def transfer_wallet():
response = await bfx.rest.submit_wallet_transfer("exchange", "margin", "BTC", "BTC", 0.1)
# response is in the form of a Notification object
# notify_info is in the form of a Transfer object
print ("Transfer: ", response.notify_info)
async def deposit_address():
response = await bfx.rest.get_wallet_deposit_address("exchange", "bitcoin")
# response is in the form of a Notification object
# notify_info is in the form of a DepositAddress object
print ("Address: ", response.notify_info)
async def create_new_address():
response = await bfx.rest.create_wallet_deposit_address("exchange", "bitcoin")
# response is in the form of a Notification object
# notify_info is in the form of a DepositAddress object
print ("Address: ", response.notify_info)
async def withdraw():
# tetheruse = Tether (ERC20)
response = await bfx.rest.submit_wallet_withdraw("exchange", "tetheruse", 5, "0xc5bbb852f82c24327693937d4012f496cff7eddf")
# response is in the form of a Notification object
# notify_info is in the form of a DepositAddress object
print ("Address: ", response.notify_info)
async def run():
await transfer_wallet()
await deposit_address()
await withdraw()
t = asyncio.ensure_future(run())
asyncio.get_event_loop().run_until_complete(t)

View File

@@ -10,20 +10,18 @@ API_SECRET=os.getenv("BFX_SECRET")
bfx = Client(
API_KEY=API_KEY,
API_SECRET=API_SECRET,
logLevel='INFO'
logLevel='DEBUG'
)
@bfx.ws.on('order_closed')
def order_cancelled(order, trade):
def order_cancelled(order):
print ("Order cancelled.")
print (order)
print (trade)
@bfx.ws.on('order_confirmed')
async def trade_completed(order, trade):
async def trade_completed(order):
print ("Order confirmed.")
print (order)
print (trade)
await bfx.ws.cancel_order(order.id)
@bfx.ws.on('error')
@@ -32,7 +30,7 @@ def log_error(msg):
@bfx.ws.once('authenticated')
async def submit_order(auth_message):
# create an inital order a really low price so it stays open
# create an initial order at a really low price so it stays open
await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT)
bfx.ws.run()

View File

@@ -12,5 +12,9 @@ from .position import Position
from .funding_loan import FundingLoan
from .funding_offer import FundingOffer
from .funding_credit import FundingCredit
from .notification import Notification
from .transfer import Transfer
from .deposit_address import DepositAddress
from .withdraw import Withdraw
NAME = 'models'

View File

@@ -0,0 +1,42 @@
"""
Module used to describe a DepositAddress object
"""
class DepositModel:
"""
Enum used to index the location of each value in a raw array
"""
METHOD = 1
CURRENCY = 2
ADDRESS = 4
class DepositAddress:
"""
[None, 'BITCOIN', 'BTC', None, '38zsUkv8q2aiXK9qsZVwepXjWeh3jKvvZw']
METHOD string Protocol used for funds transfer
SYMBOL string Currency symbol
ADDRESS string Deposit address for funds transfer
"""
def __init__(self, method, currency, address):
self.method = method
self.currency = currency
self.address = address
@staticmethod
def from_raw_deposit_address(raw_add):
"""
Parse a raw deposit object into a DepositAddress object
@return DepositAddress
"""
method = raw_add[DepositModel.METHOD]
currency = raw_add[DepositModel.CURRENCY]
address = raw_add[DepositModel.ADDRESS]
return DepositAddress(method, currency, address)
def __str__(self):
''' Allow us to print the Transfer object in a pretty format '''
text = "DepositAddress <{} method={} currency={}>"
return text.format(self.address, self.method, self.currency)

View File

@@ -2,6 +2,12 @@
Module used to describe all of the different data types
"""
class FundingOfferTypes:
"""
Enum used to define the different funding offer types
"""
LIMIT = 'LIMIT'
FRR_DELTA = 'FRRDELTAVAR'
class FundingOfferModel:
"""
@@ -41,6 +47,8 @@ class FundingOffer:
RENEW int 0 if false, 1 if true
"""
Type = FundingOfferTypes()
def __init__(self, fid, symbol, mts_create, mts_updated, amount, amount_orig, f_type,
flags, status, rate, period, notify, hidden, renew):
# pylint: disable=invalid-name

View File

@@ -0,0 +1,119 @@
"""
Module used to describe all of the different notification data types
"""
from .order import Order
from .funding_offer import FundingOffer
from .transfer import Transfer
from .deposit_address import DepositAddress
from .withdraw import Withdraw
class NotificationModal:
"""
Enum used index the different values in a raw order array
"""
MTS = 0
TYPE = 1
MESSAGE_ID = 2
NOTIFY_INFO = 4
CODE = 5
STATUS = 6
TEXT = 7
class NotificationError:
"""
Enum used to hold the error response statuses
"""
SUCCESS = "SUCCESS"
ERROR = "ERROR"
FAILURE = "FAILURE"
class NotificationTypes:
"""
Enum used to hold the different notification types
"""
ORDER_NEW_REQ = "on-req"
ORDER_CANCELED_REQ = "oc-req"
ORDER_UPDATED_REQ = "ou-req"
FUNDING_OFFER_NEW = "fon-req"
FUNDING_OFFER_CANCEL = "foc-req"
ACCOUNT_TRANSFER = "acc_tf"
ACCOUNT_DEPOSIT = "acc_dep"
ACCOUNT_WITHDRAW_REQ = "acc_wd-req"
# uca ?
# pm-req ?
class Notification:
"""
MTS int Millisecond Time Stamp of the update
TYPE string Purpose of notification ('on-req', 'oc-req', 'uca', 'fon-req', 'foc-req')
MESSAGE_ID int unique ID of the message
NOTIFY_INFO array/object A message containing information regarding the notification
CODE null or integer Work in progress
STATUS string Status of the notification; it may vary over time (SUCCESS, ERROR, FAILURE, ...)
TEXT string Text of the notification
"""
def __init__(self, mts, notify_type, message_id, notify_info, code, status, text):
self.mts = mts
self.notify_type = notify_type
self.message_id = message_id
self.notify_info = notify_info
self.code = code
self.status = status
self.text = text
def is_success(self):
"""
Check if the notification status was a success.
@return bool: True if is success else False
"""
if self.status == NotificationError.SUCCESS:
return True
return False
@staticmethod
def from_raw_notification(raw_notification):
"""
Parse a raw notification object into an Order object
@return Notification
"""
mts = raw_notification[NotificationModal.MTS]
notify_type = raw_notification[NotificationModal.TYPE]
message_id = raw_notification[NotificationModal.MESSAGE_ID]
notify_info = raw_notification[NotificationModal.NOTIFY_INFO]
code = raw_notification[NotificationModal.CODE]
status = raw_notification[NotificationModal.STATUS]
text = raw_notification[NotificationModal.TEXT]
basic = Notification(mts, notify_type, message_id, notify_info, code,
status, text)
# if failure notification then just return as is
if not basic.is_success():
return basic
# parse additional notification data
if basic.notify_type == NotificationTypes.ORDER_NEW_REQ:
basic.notify_info = Order.from_raw_order_snapshot(basic.notify_info)
elif basic.notify_type == NotificationTypes.ORDER_CANCELED_REQ:
basic.notify_info = Order.from_raw_order(basic.notify_info)
elif basic.notify_type == NotificationTypes.ORDER_UPDATED_REQ:
basic.notify_info = Order.from_raw_order(basic.notify_info)
elif basic.notify_type == NotificationTypes.FUNDING_OFFER_NEW:
basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info)
elif basic.notify_type == NotificationTypes.FUNDING_OFFER_CANCEL:
basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info)
elif basic.notify_type == NotificationTypes.ACCOUNT_TRANSFER:
basic.notify_info = Transfer.from_raw_transfer(basic.notify_info)
elif basic.notify_type == NotificationTypes.ACCOUNT_DEPOSIT:
basic.notify_info = DepositAddress.from_raw_deposit_address(basic.notify_info)
elif basic.notify_type == NotificationTypes.ACCOUNT_WITHDRAW_REQ:
basic.notify_info = Withdraw.from_raw_withdraw(basic.notify_info)
return basic
def __str__(self):
''' Allow us to print the Notification object in a pretty format '''
text = "Notification <'{}' ({}) - {} notify_info={}>"
return text.format(self.notify_type, self.status, self.text, self.notify_info)

View File

@@ -1,5 +1,5 @@
"""
Module used to describe all of the different data types
Module used to describe all of the different order data types
"""
import time
@@ -38,8 +38,7 @@ class OrderSide:
class OrderClosedModel:
"""
Enum used ad an index match to locate the different values in a
raw order array
Enum used index the different values in a raw order array
"""
ID = 0
GID = 1
@@ -151,7 +150,7 @@ class Order:
@staticmethod
def from_raw_order(raw_order):
"""
Parse a raw order object into an Order oject
Parse a raw order object into an Order object
@return Order
"""
@@ -178,6 +177,18 @@ class Order:
amount_orig, o_type, type_prev, flags, status, price, price_avg,
price_trailing, price_aux_limit, notfiy, place_id)
@staticmethod
def from_raw_order_snapshot(raw_order_snapshot):
"""
Parse a raw order snapshot array into an array of order objects
@return Orders: array of order objects
"""
parsed_orders = []
for raw_order in raw_order_snapshot:
parsed_orders += [Order.from_raw_order(raw_order)]
return parsed_orders
def set_confirmed(self):
"""
Set the state of the order to be confirmed

53
bfxapi/models/transfer.py Normal file
View File

@@ -0,0 +1,53 @@
"""
Module used to describe a transfer object
"""
class TransferModel:
"""
Enum used to index the location of each value in a raw array
"""
MTS = 0
W_FROM = 1
W_TO = 2
C_FROM = 4
C_TO = 5
AMOUNT = 7
class Transfer:
"""
MTS int Millisecond Time Stamp of the update
WALLET_FROM string Wallet name (exchange, margin, funding)
WALLET_TO string Wallet name (exchange, margin, funding)
CURRENCY_FROM string Currency (BTC, etc)
CURRENCY_TO string Currency (BTC, etc)
AMOUNT string Amount of funds to transfer
"""
def __init__(self, mts, wallet_from, wallet_to, currency_from, currency_to, amount):
self.mts = mts
self.wallet_from = wallet_from
self.wallet_to = wallet_to
self.currency_from = currency_from
self.currency_to = currency_to
self.amount = amount
@staticmethod
def from_raw_transfer(raw_transfer):
"""
Parse a raw transfer object into a Transfer object
@return Transfer
"""
mts = raw_transfer[TransferModel.MTS]
wallet_from = raw_transfer[TransferModel.W_FROM]
wallet_to = raw_transfer[TransferModel.W_TO]
currency_from = raw_transfer[TransferModel.C_FROM]
currency_to = raw_transfer[TransferModel.C_TO]
amount = raw_transfer[TransferModel.AMOUNT]
return Transfer(mts, wallet_from, wallet_to, currency_from, currency_to, amount)
def __str__(self):
''' Allow us to print the Transfer object in a pretty format '''
text = "Transfer <{} from {} ({}) to {} ({}) mts={}>"
return text.format(self.amount, self.wallet_from, self.currency_from,
self.wallet_to, self.currency_to, self.mts)

51
bfxapi/models/withdraw.py Normal file
View File

@@ -0,0 +1,51 @@
"""
Module used to describe a withdraw object
"""
class WithdrawModel:
"""
Enum used to index the location of each value in a raw array
"""
ID = 0
METHOD = 2
WALLET = 4
AMOUNT = 5
FEE = 7
class Withdraw:
"""
[13063236, None, 'tetheruse', None, 'exchange', 5, None, None, 0.00135]
MTS int Millisecond Time Stamp of the update
WALLET_FROM string Wallet name (exchange, margin, funding)
WALLET_TO string Wallet name (exchange, margin, funding)
CURRENCY_FROM string Currency (BTC, etc)
CURRENCY_TO string Currency (BTC, etc)
AMOUNT string Amount of funds to transfer
"""
def __init__(self, w_id, method, wallet, amount, fee=0):
self.id = w_id
self.method = method
self.wallet = wallet
self.amount = amount
self.fee = fee
@staticmethod
def from_raw_withdraw(raw_withdraw):
"""
Parse a raw withdraw object into a Withdraw object
@return Withdraw
"""
w_id = raw_withdraw[WithdrawModel.ID]
method = raw_withdraw[WithdrawModel.METHOD]
wallet = raw_withdraw[WithdrawModel.WALLET]
amount = raw_withdraw[WithdrawModel.AMOUNT]
return Withdraw(w_id, method, wallet, amount)
def __str__(self):
''' Allow us to print the Transfer object in a pretty format '''
text = "Withdraw <id={} from {} ({}) fee={}>"
return text.format(self.id, self.wallet, self.method, self.amount,
self.fee)

View File

@@ -8,9 +8,9 @@ import time
import json
from ..utils.custom_logger import CustomLogger
from ..utils.auth import generate_auth_headers
from ..utils.auth import generate_auth_headers, calculate_order_flags, gen_unique_cid
from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer
from ..models import FundingCredit
from ..models import FundingCredit, Notification
class BfxRest:
@@ -61,7 +61,7 @@ class BfxRest:
async with aiohttp.ClientSession() as session:
async with session.post(url + params, headers=headers, data=sData) as resp:
text = await resp.text()
if resp.status is not 200:
if resp.status < 200 or resp.status > 299:
raise Exception('POST {} failed with status {} - {}'
.format(url, resp.status, text))
parsed = json.loads(text, parse_float=self.parse_float)
@@ -351,48 +351,227 @@ class BfxRest:
credits = await self.post(endpoint, params=params)
return [FundingCredit.from_raw_credit(c) for c in credits]
async def submit_funding_offer(self, symbol, amount, rate, period,
funding_type=FundingOffer.Type.LIMIT, hidden=False):
"""
Submits a new funding offer
@param symbol string: pair symbol i.e fUSD
@param amount float: funding size
@param rate float: percentage rate to charge per a day
@param period int: number of days for funding to remain active once accepted
"""
payload = {
"type": funding_type,
"symbol": symbol,
"amount": str(amount),
"rate": str(rate),
"period": period,
}
# calculate and add flags
flags = calculate_order_flags(hidden, None, None, None, None)
payload['flags'] = flags
endpoint = "auth/w/funding/offer/submit"
raw_notification = await self.post(endpoint, payload)
return Notification.from_raw_notification(raw_notification)
async def submit_cancel_funding_offer(self, fundingId):
"""
Cancel a funding offer
@param fundingId int: the id of the funding offer
"""
endpoint = "auth/w/funding/offer/cancel"
raw_notification = await self.post(endpoint, { 'id': fundingId })
return Notification.from_raw_notification(raw_notification)
async def submit_wallet_transfer(self, from_wallet, to_wallet, from_currency, to_currency, amount):
"""
Transfer funds between wallets
@param from_wallet string: wallet name to transfer from i.e margin, exchange
@param to_wallet string: wallet name to transfer to i.e margin, exchange
@param from_currency string: currency symbol to tranfer from i.e BTC, USD
@param to_currency string: currency symbol to transfer to i.e BTC, USD
@param amount float: amount of funds to transfer
"""
endpoint = "auth/w/transfer"
payload = {
"from": from_wallet,
"to": to_wallet,
"currency": from_currency,
"currency_to": to_currency,
"amount": str(amount),
}
raw_transfer = await self.post(endpoint, payload)
return Notification.from_raw_notification(raw_transfer)
async def get_wallet_deposit_address(self, wallet, method, renew=0):
"""
Get the deposit address for the given wallet and protocol
@param wallet string: wallet name i.e margin, exchange
@param method string: transfer protocol i.e bitcoin
"""
endpoint = "auth/w/deposit/address"
payload = {
"wallet": wallet,
"method": method,
"op_renew": renew,
}
raw_deposit = await self.post(endpoint, payload)
return Notification.from_raw_notification(raw_deposit)
async def create_wallet_deposit_address(self, wallet, method):
"""
Creates a new deposit address for the given wallet and protocol.
Previously generated addresses remain linked.
@param wallet string: wallet name i.e margin, exchange
@param method string: transfer protocol i.e bitcoin
"""
return await self.get_wallet_deposit_address(wallet, method, renew=1)
async def submit_wallet_withdraw(self, wallet, method, amount, address):
"""
`/v2/auth/w/withdraw` (params: `wallet`, `method`, `amount`, `address
"""
endpoint = "auth/w/withdraw"
payload = {
"wallet": wallet,
"method": method,
"amount": str(amount),
"address": str(address)
}
raw_deposit = await self.post(endpoint, payload)
return Notification.from_raw_notification(raw_deposit)
# async def submit_close_funding(self, id, type):
# """
# `/v2/auth/w/funding/close` (params: `id`, `type` (credit|loan))
# """
# pass
# async def submit_auto_funding(self, ):
# """
# `/v2/auth/w/funding/auto` (params: `status` (1|0), `currency`, `amount`, `rate`, `period`)
# (`rate === 0` means `FRR`)
# """
# pass
##################################################
# Orders #
##################################################
async def __submit_order(self, symbol, amount, price, oType=Order.Type.LIMIT,
is_hidden=False, is_postonly=False, use_all_available=False,
stop_order=False, stop_buy_price=0, stop_sell_price=0):
async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
hidden=False, price_trailing=None, price_aux_limit=None,
oco_stop_price=None, close=False, reduce_only=False,
post_only=False, oco=False, time_in_force=None, leverage=None,
gid=None):
"""
Submit a new order
@param gid: assign the order to a group identifier
@param symbol: the name of the symbol i.e 'tBTCUSD
@param price: the price you want to buy/sell at (must be positive)
@param amount: order size: how much you want to buy/sell,
a negative amount indicates a sell order and positive a buy order
@param price: the price you want to buy/sell at (must be positive)
@param oType: order type, see Order.Type enum
@param is_hidden: True if order should be hidden from orderbooks
@param is_postonly: True if should be post only. Only relevant for limit
@param use_all_available: True if order should use entire balance
@param stop_order: True to set an additional STOP OCO order linked to the
current order
@param stop_buy_price: set the OCO stop buy price (requires stop_order True)
@param stop_sell_price: set the OCO stop sell price (requires stop_order True)
@param market_type Order.Type: please see Order.Type enum
amount decimal string Positive for buy, Negative for sell
@param hidden: if True, order should be hidden from orderbooks
@param price_trailing: decimal trailing price
@param price_aux_limit: decimal auxiliary Limit price (only for STOP LIMIT)
@param oco_stop_price: set the oco stop price (requires oco = True)
@param close: if True, close position if position present
@param reduce_only: if True, ensures that the executed order does not flip the opened position
@param post_only: if True, ensures the limit order will be added to the order book and not
match with a pre-existing order
@param oco: cancels other order option allows you to place a pair of orders stipulating
that if one order is executed fully or partially, then the other is automatically canceled
@param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
@param leverage: the amount of leverage to apply to the order as an integer
"""
raise NotImplementedError(
"V2 submit order has not yet been added to the bfx api. Please use bfxapi.ws")
side = Order.Side.SELL if amount < 0 else Order.Side.BUY
use_all_balance = 1 if use_all_available else 0
payload = {}
payload['symbol'] = symbol
payload['amount'] = abs(amount)
payload['price'] = price
payload['side'] = side
payload['type'] = oType
payload['is_hidden'] = is_hidden
payload['is_postonly'] = is_postonly
payload['use_all_available'] = use_all_balance
payload['ocoorder'] = stop_order
if stop_order:
payload['buy_price_oco'] = stop_buy_price
payload['sell_price_oco'] = stop_sell_price
endpoint = 'order/new'
return await self.post(endpoint, data=payload)
cid = gen_unique_cid()
payload = {
"cid": cid,
"type": str(market_type),
"symbol": symbol,
"amount": str(amount),
"price": str(price),
}
# calculate and add flags
flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco)
payload['flags'] = flags
# add extra parameters
if price_trailing is not None:
payload['price_trailing'] = price_trailing
if price_aux_limit is not None:
payload['price_aux_limit'] = price_aux_limit
if oco_stop_price is not None:
payload['price_oco_stop'] = str(oco_stop_price)
if time_in_force is not None:
payload['tif'] = time_in_force
if gid is not None:
payload['gid'] = gid
if leverage is not None:
payload['lev'] = str(leverage)
endpoint = "auth/w/order/submit"
raw_notification = await self.post(endpoint, payload)
return Notification.from_raw_order(raw_notification)
async def submit_cancel_order(self, orderId):
"""
Cancel an existing open order
@param orderId: the id of the order that you want to update
"""
endpoint = "auth/w/order/cancel"
raw_notification = await self.post(endpoint, { 'id': orderId })
return Notification.from_raw_order(raw_notification)
async def submit_update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None,
price_trailing=None, hidden=False, close=False, reduce_only=False,
post_only=False, time_in_force=None, leverage=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 leverage: the amount of leverage to apply to the order as an integer
"""
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)
if leverage is not None:
payload["lev"] = str(leverage)
flags = calculate_order_flags(
hidden, close, reduce_only, post_only, False)
payload['flags'] = flags
endpoint = "auth/w/order/update"
raw_notification = await self.post(endpoint, payload)
return Notification.from_raw_order(raw_notification)
##################################################
# Derivatives #

View File

@@ -122,7 +122,7 @@ async def test_closed_callback_on_submit_order_closed():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onClose=c)
await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()
@@ -139,7 +139,7 @@ async def test_confirmed_callback_on_submit_order_closed():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c)
await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()
@@ -155,7 +155,7 @@ async def test_confirmed_callback_on_submit_new_order():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c)
await client.ws.publish([0,"on",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262833410,-1,-1,"EXCHANGE LIMIT",None,None,None,0,"ACTIVE",None,None,15980,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()
@@ -171,7 +171,7 @@ async def test_confirmed_callback_on_submit_order_update():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.update_order(123, price=100, onConfirm=c)
await client.ws.publish([0,"ou",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262846964,-0.5,-1,"EXCHANGE LIMIT",None,None,None,0,"PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()
@@ -187,7 +187,7 @@ async def test_confirmed_callback_on_submit_cancel_order():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.cancel_order(123, onConfirm=c)
await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()
@@ -203,7 +203,7 @@ async def test_confirmed_callback_on_submit_cancel_group_order():
client.ws._emit('c1', order)
callback_wait = EventWatcher.watch(client.ws, 'c1')
# override cid generation
client.ws.orderManager._gen_unqiue_cid = lambda: 123
client.ws.orderManager._gen_unique_cid = lambda: 123
await client.ws.cancel_order_group(123, onConfirm=c)
await client.ws.publish([0,"oc",[1548262833910,123,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
callback_wait.wait_until_complete()

View File

@@ -6,6 +6,7 @@ to handle the http authentication of the client
import hashlib
import hmac
import time
from ..models import Order
def generate_auth_payload(API_KEY, API_SECRET):
"""
@@ -48,3 +49,15 @@ def _gen_signature(API_KEY, API_SECRET, nonce):
def _gen_nonce():
return int(round(time.time() * 1000000))
def gen_unique_cid():
return int(round(time.time() * 1000))
def calculate_order_flags(hidden, close, reduce_only, post_only, oco):
flags = 0
flags = flags + Order.Flags.HIDDEN if hidden else flags
flags = flags + Order.Flags.CLOSE if close else flags
flags = flags + Order.Flags.REDUCE_ONLY if reduce_only else flags
flags = flags + Order.Flags.POST_ONLY if post_only else flags
flags = flags + Order.Flags.OCO if oco else flags
return flags

View File

@@ -2,4 +2,4 @@
This module contains the current version of the bfxapi lib
"""
__version__ = '1.0.1'
__version__ = '1.1.0'

View File

@@ -7,6 +7,7 @@ import asyncio
from ..utils.custom_logger import CustomLogger
from ..models import Order
from ..utils.auth import calculate_order_flags, gen_unique_cid
class OrderManager:
@@ -83,18 +84,15 @@ class OrderManager:
self.logger.info("Order new: {}".format(order))
self.bfxapi._emit('order_new', order)
def _gen_unqiue_cid(self):
return int(round(time.time() * 1000))
async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
hidden=False, price_trailing=None, price_aux_limit=None,
oco_stop_price=None, close=False, reduce_only=False,
post_only=False, oco=False, time_in_force=None,
post_only=False, oco=False, time_in_force=None, leverage=None,
onConfirm=None, onClose=None, gid=None, *args, **kwargs):
"""
Submit a new order
@param gid: assign the order to a group identitfier
@param gid: assign the order to a group identifier
@param symbol: the name of the symbol i.e 'tBTCUSD
@param price: the price you want to buy/sell at (must be positive)
@param amount: order size: how much you want to buy/sell,
@@ -113,12 +111,13 @@ class OrderManager:
that if one order is executed fully or partially, then the other is automatically canceled
@param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
@param leverage: the amount of leverage to apply to the order as an integer
@param onConfirm: function called when the bitfinex websocket receives signal that the order
was confirmed
@param onClose: function called when the bitfinex websocket receives signal that the order
was closed due to being filled or cancelled
"""
cid = self._gen_unqiue_cid()
cid = self._gen_unique_cid()
# create base payload with required data
payload = {
"cid": cid,
@@ -127,20 +126,22 @@ class OrderManager:
"amount": str(amount),
"price": str(price),
}
# caclulate and add flags
flags = self._calculate_flags(hidden, close, reduce_only, post_only, oco)
# calculate and add flags
flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco)
payload['flags'] = flags
# add extra parameters
if (price_trailing):
if price_trailing is not None:
payload['price_trailing'] = price_trailing
if (price_aux_limit):
if price_aux_limit is not None:
payload['price_aux_limit'] = price_aux_limit
if (oco_stop_price):
if oco_stop_price is not None:
payload['price_oco_stop'] = str(oco_stop_price)
if (time_in_force):
if time_in_force is not None:
payload['tif'] = time_in_force
if (gid):
if gid is not None:
payload['gid'] = gid
if leverage is not None:
payload['lev'] = str(leverage)
# submit the order
self.pending_orders[cid] = payload
self._create_callback(cid, onConfirm, self.pending_order_confirm_callbacks)
@@ -151,7 +152,7 @@ class OrderManager:
async def update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None,
price_trailing=None, hidden=False, close=False, reduce_only=False,
post_only=False, time_in_force=None, onConfirm=None):
post_only=False, time_in_force=None, leverage=None, onConfirm=None):
"""
Update an existing order
@@ -167,7 +168,8 @@ class OrderManager:
@param reduce_only: if True, ensures that the executed order does not flip the opened position
@param post_only: if True, ensures the limit order will be added to the order book and not
match with a pre-existing order
@param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
@param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
@param leverage: the amount of leverage to apply to the order as an integer
@param onConfirm: function called when the bitfinex websocket receives signal that the order
was confirmed
@param onClose: function called when the bitfinex websocket receives signal that the order
@@ -187,7 +189,9 @@ class OrderManager:
payload['price_trailing'] = str(price_trailing)
if time_in_force is not None:
payload['time_in_force'] = str(time_in_force)
flags = self._calculate_flags(
if leverage is not None:
payload['lev'] = str(leverage)
flags = calculate_order_flags(
hidden, close, reduce_only, post_only, False)
payload['flags'] = flags
await self.bfxapi._send_auth_command('ou', payload)
@@ -261,11 +265,5 @@ class OrderManager:
del callback_storage[key]
await asyncio.gather(*tasks)
def _calculate_flags(self, hidden, close, reduce_only, post_only, oco):
flags = 0
flags = flags + Order.Flags.HIDDEN if hidden else flags
flags = flags + Order.Flags.CLOSE if close else flags
flags = flags + Order.Flags.REDUUCE_ONLY if reduce_only else flags
flags = flags + Order.Flags.POST_ONLY if post_only else flags
flags = flags + Order.Flags.OCO if oco else flags
return flags
def _gen_unique_cid(self):
return gen_unique_cid()

View File

@@ -2,7 +2,7 @@ eventemitter==0.2.0
asyncio==3.4.3
websockets==7.0
pylint==2.3.0
pytest-asyncio==0.9.0
pytest-asyncio==0.10.0
six==1.12.0
pyee==5.0.0
aiohttp==3.4.4

View File

@@ -20,7 +20,7 @@ here = path.abspath(path.dirname(__file__))
setup(
name='bitfinex-api-py',
version='1.0.1', # Required
version='1.1.0', # Required
description='Official Bitfinex API', # Optional
long_description='This is an official python library that is used to connect interact with the Bitfinex api.', # Optional
long_description_content_type='text/markdown', # Optional