fix all linting for pylint3

This commit is contained in:
Jacob Plaster
2018-12-18 11:28:04 +00:00
parent 4ef8be144b
commit 0c9a0fadbc
33 changed files with 2505 additions and 1687 deletions

View File

@@ -1,12 +1,12 @@
language: python language: python
python: python:
- "3.4" - "3.4"
- "3.5" - "3.5"
- "3.6" - "3.6"
# PyPy versions # PyPy versions
- "pypy3.5" - "pypy3.5"
# command to install dependencies # command to install dependencies
install: install:
- pip install -r requirements.txt - pip install -r requirements.txt
# command to run tests # command to run tests
script: pylint bfxapi script: pylint --rcfile=pylint.rc bfxapi

View File

@@ -1,13 +1,29 @@
"""
This module exposes the core bitfinex clients which includes both
a websocket client and a rest interface client
"""
# pylint: disable-all
import asyncio import asyncio
from .websockets.BfxWebsocket import BfxWebsocket from .websockets.BfxWebsocket import BfxWebsocket
from .rest.BfxRest import BfxRest from .rest.BfxRest import BfxRest
REST_HOST = 'https://api.bitfinex.com/v2'
WS_HOST = 'wss://api.bitfinex.com/ws/2'
class Client: class Client:
def __init__(self, API_KEY=None, API_SECRET=None, rest_host='https://api.bitfinex.com/v2', """
ws_host='wss://api.bitfinex.com/ws/2', loop=None, logLevel='INFO', dead_man_switch=False, *args, **kwargs): The bfx client exposes rest and websocket objects
self.loop = loop or asyncio.get_event_loop() """
self.ws = BfxWebsocket(API_KEY=API_KEY, API_SECRET=API_SECRET, host=ws_host,
loop=self.loop, logLevel=logLevel, dead_man_switch=dead_man_switch, *args, **kwargs) def __init__(self, API_KEY=None, API_SECRET=None, rest_host=REST_HOST,
self.rest = BfxRest(API_KEY=API_KEY, API_SECRET=API_SECRET, host=rest_host, ws_host=WS_HOST, loop=None, logLevel='INFO', dead_man_switch=False,
loop=self.loop, logLevel=logLevel, *args, **kwargs) *args, **kwargs):
self.loop = loop or asyncio.get_event_loop()
self.ws = BfxWebsocket(API_KEY=API_KEY, API_SECRET=API_SECRET, host=ws_host,
loop=self.loop, logLevel=logLevel, dead_man_switch=dead_man_switch,
*args, **kwargs)
self.rest = BfxRest(API_KEY=API_KEY, API_SECRET=API_SECRET, host=rest_host,
loop=self.loop, logLevel=logLevel, *args, **kwargs)

View File

@@ -1,5 +1,10 @@
name = 'bfxapi' """
This module is used to interact with the bitfinex api
"""
from bfxapi.Client import Client from .client import Client
from bfxapi.websockets.GenericWebsocket import GenericWebsocket from .models import (Order, Trade, OrderBook, Subscription, Wallet,
from bfxapi.models import * Position, FundingLoan, FundingOffer, FundingCredit)
from .websockets.GenericWebsocket import GenericWebsocket
NAME = 'bfxapi'

29
bfxapi/client.py Normal file
View File

@@ -0,0 +1,29 @@
"""
This module exposes the core bitfinex clients which includes both
a websocket client and a rest interface client
"""
# pylint: disable-all
import asyncio
from .websockets.BfxWebsocket import BfxWebsocket
from .rest.BfxRest import BfxRest
REST_HOST = 'https://api.bitfinex.com/v2'
WS_HOST = 'wss://api.bitfinex.com/ws/2'
class Client:
"""
The bfx client exposes rest and websocket objects
"""
def __init__(self, API_KEY=None, API_SECRET=None, rest_host=REST_HOST,
ws_host=WS_HOST, loop=None, logLevel='INFO', dead_man_switch=False,
*args, **kwargs):
self.loop = loop or asyncio.get_event_loop()
self.ws = BfxWebsocket(API_KEY=API_KEY, API_SECRET=API_SECRET, host=ws_host,
loop=self.loop, logLevel=logLevel, dead_man_switch=dead_man_switch,
*args, **kwargs)
self.rest = BfxRest(API_KEY=API_KEY, API_SECRET=API_SECRET, host=rest_host,
loop=self.loop, logLevel=logLevel, *args, **kwargs)

View File

@@ -27,7 +27,7 @@ async def on_subscribe(subscription):
@bfx.ws.once('subscribed') @bfx.ws.once('subscribed')
async def on_once_subscribe(subscription): async def on_once_subscribe(subscription):
print ("Performig resubscribe") print ("Performig resubscribe")
await bfx.ws.resubscribe(subscription.chanId) await bfx.ws.resubscribe(subscription.chan_id)
async def start(): async def start():

View File

@@ -1,86 +0,0 @@
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)

View File

@@ -1,82 +0,0 @@
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)

View File

@@ -1,74 +0,0 @@
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)

View File

@@ -1,161 +1,221 @@
"""
Module used to describe all of the different data types
"""
import time import time
import datetime import datetime
class OrderType: class OrderType:
MARKET = 'MARKET' """
LIMIT = 'LIMIT' Enum used to describe all of the different order types available for use
STOP = 'STOP' """
STOP_LIMIT = 'STOP LIMIT' MARKET = 'MARKET'
TRAILING_STOP = 'TRAILING STOP' LIMIT = 'LIMIT'
FILL_OR_KILL = 'FOK' STOP = 'STOP'
EXCHANGE_MARKET = 'EXCHANGE MARKET' STOP_LIMIT = 'STOP LIMIT'
EXCHANGE_LIMIT = 'EXCHANGE LIMIT' TRAILING_STOP = 'TRAILING STOP'
EXCHANGE_STOP = 'EXCHANGE STOP' FILL_OR_KILL = 'FOK'
EXCHANGE_STOP_LIMIT = 'EXCHANGE STOP LIMIT' EXCHANGE_MARKET = 'EXCHANGE MARKET'
EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP' EXCHANGE_LIMIT = 'EXCHANGE LIMIT'
EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK' EXCHANGE_STOP = 'EXCHANGE STOP'
EXCHANGE_STOP_LIMIT = 'EXCHANGE STOP LIMIT'
EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP'
EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK'
LIMIT_ORDERS = [OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.EXCHANGE_LIMIT, LIMIT_ORDERS = [OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.EXCHANGE_LIMIT,
OrderType.EXCHANGE_STOP_LIMIT, OrderType.FILL_OR_KILL, OrderType.EXCHANGE_FILL_OR_KILL] OrderType.EXCHANGE_STOP_LIMIT, OrderType.FILL_OR_KILL,
OrderType.EXCHANGE_FILL_OR_KILL]
class OrderSide: class OrderSide:
BUY = 'buy' """
SELL = 'sell' Enum used to describe the different directions of an order
"""
BUY = 'buy'
SELL = 'sell'
class OrderClosedModel: class OrderClosedModel:
ID = 0 """
GID = 1 Enum used ad an index match to locate the different values in a
CID = 2 raw order array
SYMBOL = 3 """
MTS_CREATE = 4 ID = 0
MTS_UPDATE = 5 GID = 1
AMOUNT = 6 CID = 2
AMOUNT_ORIG = 7 SYMBOL = 3
TYPE = 8 MTS_CREATE = 4
TYPE_PREV = 9 MTS_UPDATE = 5
FLAGS = 12 AMOUNT = 6
STATUS = 13 AMOUNT_ORIG = 7
PRICE = 16 TYPE = 8
PRIVE_AVG = 17 TYPE_PREV = 9
PRICE_TRAILING = 18 FLAGS = 12
PRICE_AUX_LIMIT = 19 STATUS = 13
NOTIFY = 23 PRICE = 16
PLACE_ID = 25 PRIVE_AVG = 17
PRICE_TRAILING = 18
PRICE_AUX_LIMIT = 19
NOTIFY = 23
PLACE_ID = 25
class OrderFlags: class OrderFlags:
HIDDEN = 64 """
CLOSE = 12 Enum used to explain the different values that can be passed in
REDUCE_ONLY = 1024 as flags
POST_ONLY = 4096 """
OCO = 16384 HIDDEN = 64
CLOSE = 12
REDUCE_ONLY = 1024
POST_ONLY = 4096
OCO = 16384
def now_in_mills(): def now_in_mills():
return int(round(time.time() * 1000)) """
Gets the current time in milliseconds
"""
return int(round(time.time() * 1000))
class Order: class Order:
""" """
ID int64 Order ID ID int64 Order ID
GID int Group ID GID int Group ID
CID int Client Order ID CID int Client Order ID
SYMBOL string Pair (tBTCUSD, ) SYMBOL string Pair (tBTCUSD, ...)
MTS_CREATE int Millisecond timestamp of creation MTS_CREATE int Millisecond timestamp of creation
MTS_UPDATE int Millisecond timestamp of update MTS_UPDATE int Millisecond timestamp of update
AMOUNT float Positive means buy, negative means sell. AMOUNT float Positive means buy, negative means sell.
AMOUNT_ORIG float Original amount 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 string The type of the order: LIMIT, MARKET, STOP, TRAILING STOP,
TYPE_PREV string Previous order type EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK.
FLAGS int Upcoming Params Object (stay tuned) TYPE_PREV string Previous order type
ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED FLAGS int Upcoming Params Object (stay tuned)
PRICE float Price ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
PRICE_AVG float Average price PRICE float Price
PRICE_TRAILING float The trailing price PRICE_AVG float Average price
PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT) PRICE_TRAILING float The trailing price
HIDDEN int 1 if Hidden, 0 if not hidden PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT)
PLACED_ID int If another order caused this order to be placed (OCO) this will be that other order's ID 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() Type = OrderType()
Side = OrderSide() Side = OrderSide()
Flags = OrderFlags() Flags = OrderFlags()
def __init__(self, id, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, def __init__(self, oid, gid, cid, symbol, mts_create, mts_update, amount,
typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId): amount_orig, o_type, typePrev, flags, status, price, price_avg,
self.id = id price_trailing, price_aux_limit, notfiy, place_id):
self.gId = gId # pylint: disable=invalid-name
self.cId = cId self.id = oid
self.symbol = symbol self.gid = gid
self.mtsCreate = mtsCreate self.cid = cid
self.mtsUpdate = mtsUpdate self.symbol = symbol
# self.amount = amount self.mts_create = mts_create
self.amount = amount self.mts_update = mts_update
self.amountOrig = amountOrig # self.amount = amount
self.type = oType self.amount = amount
self.typePrev = typePrev self.amount_orig = amount_orig
self.flags = flags self.type = o_type
self.status = status self.type_prev = typePrev
self.price = price self.flags = flags
self.priceAvg = priceAvg self.status = status
self.priceTrailing = priceTrailing self.price = price
self.priceAuxLimit = priceAuxLimit self.price_avg = price_avg
self.notfiy = notfiy self.price_trailing = price_trailing
self.placeId = placeId self.price_aux_limit = price_aux_limit
self.tag = "" self.notfiy = notfiy
self.fee = 0 self.place_id = place_id
self.tag = ""
self.fee = 0
self.is_pending_bool = True self.is_pending_bool = True
self.is_confirmed_bool = False self.is_confirmed_bool = False
self.is_open_bool = False self.is_open_bool = False
self.date = datetime.datetime.fromtimestamp(mtsCreate/1000.0) self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
## if cancelled then priceAvg wont exist # if cancelled then priceAvg wont exist
if priceAvg: if price_avg:
## check if order is taker or maker # check if order is taker or maker
if self.type in LIMIT_ORDERS: if self.type in LIMIT_ORDERS:
self.fee = (priceAvg * abs(amount)) * 0.001 self.fee = (price_avg * abs(amount)) * 0.001
else: else:
self.fee = (priceAvg * abs(amount)) * 0.002 self.fee = (price_avg * abs(amount)) * 0.002
@staticmethod @staticmethod
def from_raw_order(raw_order): def from_raw_order(raw_order):
oid = raw_order[OrderClosedModel.ID] """
gId = raw_order[OrderClosedModel.GID] Parse a raw order object into an Order oject
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(oid, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, @return Order
typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId) """
oid = raw_order[OrderClosedModel.ID]
gid = raw_order[OrderClosedModel.GID]
cid = raw_order[OrderClosedModel.CID]
symbol = raw_order[OrderClosedModel.SYMBOL]
mts_create = raw_order[OrderClosedModel.MTS_CREATE]
mts_update = raw_order[OrderClosedModel.MTS_UPDATE]
amount = raw_order[OrderClosedModel.AMOUNT]
amount_orig = raw_order[OrderClosedModel.AMOUNT_ORIG]
o_type = raw_order[OrderClosedModel.TYPE]
type_prev = raw_order[OrderClosedModel.TYPE_PREV]
flags = raw_order[OrderClosedModel.FLAGS]
status = raw_order[OrderClosedModel.STATUS]
price = raw_order[OrderClosedModel.PRICE]
price_avg = raw_order[OrderClosedModel.PRIVE_AVG]
price_trailing = raw_order[OrderClosedModel.PRICE_TRAILING]
price_aux_limit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT]
notfiy = raw_order[OrderClosedModel.NOTIFY]
place_id = raw_order[OrderClosedModel.PLACE_ID]
def set_confirmed(self): return Order(oid, gid, cid, symbol, mts_create, mts_update, amount,
self.is_pending_bool = False amount_orig, o_type, type_prev, flags, status, price, price_avg,
self.is_confirmed_bool = True price_trailing, price_aux_limit, notfiy, place_id)
def set_open_state(self, isOpen): def set_confirmed(self):
self.is_open_bool = isOpen """
Set the state of the order to be confirmed
"""
self.is_pending_bool = False
self.is_confirmed_bool = True
def isOpen(self): def set_open_state(self, is_open):
return self.is_open_bool """
Set the is_open state of the order
"""
self.is_open_bool = is_open
def isPending(self): def is_open(self):
return self.is_pending_bool """
Check if the order is still open
def isConfirmed(self): @return bool: Ture if order open else False
return self.is_confirmed_bool """
return self.is_open_bool
def __str__(self):
''' Allow us to print the Order object in a pretty format ''' def is_pending(self):
return "Order <'{}' mtsCreate={} status='{}' id={}>".format(self.symbol, self.mtsCreate, """
self.status, self.id) Check if the state of the order is still pending
@return bool: True if is pending else False
"""
return self.is_pending_bool
def is_confirmed(self):
"""
Check if the order has been confirmed by the bitfinex api
@return bool: True if has been confirmed else False
"""
return self.is_confirmed_bool
def __str__(self):
''' Allow us to print the Order object in a pretty format '''
text = "Order <'{}' mts_create={} status='{}' id={}>"
return text.format(self.symbol, self.mts_create, self.status, self.id)

View File

@@ -1,90 +0,0 @@
import zlib
def preparePrice(price):
# convert to 4 significant figures
prepPrice = '{0:.4f}'.format(price)
# remove decimal place if zero float
return '{0:g}'.format(float(prepPrice))
class OrderBook:
def __init__(self):
self.asks = []
self.bids = []
def get_bids(self):
return self.bids
def get_asks(self):
return self.asks
def updateFromSnapshot(self, data):
# [[4642.3, 1, 4.192], [4641.5, 1, 1]]
for order in data:
if len(order) is 4:
if order[3] < 0:
self.bids += [order]
else:
self.asks += [order]
else:
if order[2] < 0:
self.asks += [order]
else:
self.bids += [order]
def updateWith(self, order):
if len(order) is 4:
amount = order[3]
count = order[2]
side = self.bids if amount < 0 else self.asks
else:
amount = order[2]
side = self.asks if amount < 0 else self.bids
count = order[1]
price = order[0]
# if first item in ordebook
if len(side) is 0:
side += [order]
return
# match price level
for index, sOrder in enumerate(side):
sPrice = sOrder[0]
if sPrice == price:
if count is 0:
del side[index]
return
else:
# remove but add as new below
del side[index]
# if ob is initialised w/o all price levels
if count is 0:
return
# add to book and sort lowest to highest
side += [order]
side.sort(key=lambda x: x[0], reverse=not amount < 0)
return
def checksum(self):
data = []
# take set of top 25 bids/asks
for index in range(0, 25):
if index < len(self.bids):
bid = self.bids[index]
price = bid[0]
amount = bid[3] if len(bid) is 4 else bid[2]
data += [preparePrice(price)]
data += [str(amount)]
if index < len(self.asks):
ask = self.asks[index]
price = ask[0]
amount = ask[3] if len(ask) is 4 else ask[2]
data += [preparePrice(price)]
data += [str(amount)]
checksumStr = ':'.join(data)
# calculate checksum and force signed integer
checksum = zlib.crc32(checksumStr.encode('utf8')) & 0xffffffff
return checksum

View File

@@ -1,36 +1,47 @@
"""
Module used to describe all of the different data types
"""
class Position: class Position:
""" """
SYMBOL string Pair (tBTCUSD, ). SYMBOL string Pair (tBTCUSD, ...).
STATUS string Status (ACTIVE, CLOSED). STATUS string Status (ACTIVE, CLOSED).
±AMOUNT float Size of the position. Positive values means a long position, negative values means a short position. AMOUNT float Size of the position. Positive values means a long position,
BASE_PRICE float The price at which you entered your position. negative values means a short position.
MARGIN_FUNDING float The amount of funding being used for this position. BASE_PRICE float The price at which you entered your position.
MARGIN_FUNDING_TYPE int 0 for daily, 1 for term. MARGIN_FUNDING float The amount of funding being used for this position.
PL float Profit & Loss MARGIN_FUNDING_TYPE int 0 for daily, 1 for term.
PL_PERC float Profit & Loss Percentage PL float Profit & Loss
PRICE_LIQ float Liquidation price PL_PERC float Profit & Loss Percentage
LEVERAGE float Beta value PRICE_LIQ float Liquidation price
""" LEVERAGE float Beta value
"""
def __init__(self, symbol, status, amount, bPrice, mFunding, mFundingType, def __init__(self, symbol, status, amount, b_price, m_funding, m_funding_type,
profit_loss, profit_loss_perc, lPrice, lev): profit_loss, profit_loss_perc, l_price, lev):
self.symbol = symbol self.symbol = symbol
self.status = status self.status = status
self.amount = amount self.amount = amount
self.base_price = bPrice self.base_price = b_price
self.margin_funding = mFunding self.margin_funding = m_funding
self.margin_funding_type = mFundingType self.margin_funding_type = m_funding_type
self.profit_loss = profit_loss self.profit_loss = profit_loss
self.profit_loss_percentage = profit_loss_perc self.profit_loss_percentage = profit_loss_perc
self.liquidation_price = lPrice self.liquidation_price = l_price
self.leverage = lev self.leverage = lev
@staticmethod @staticmethod
def from_raw_rest_position(raw_position): def from_raw_rest_position(raw_position):
return Position(*raw_position) """
Generate a Position object from a raw position array
def __str__(self):
''' Allow us to print the Trade object in a pretty format ''' @return Position
return "Position '{}' {} x {} <Sstatus='{}' p&l={}>".format( """
self.symbol, self.base_price, self.amount, self.status, self.profit_loss) return Position(*raw_position)
def __str__(self):
''' Allow us to print the Trade object in a pretty format '''
text = "Position '{}' {} x {} <status='{}' pl={}>"
return text.format(self.symbol, self.base_price, self.amount,
self.status, self.profit_loss)

View File

@@ -1,45 +1,74 @@
"""
Module used to describe all of the different data types
"""
import time import time
import json import json
class Subscription: class Subscription:
"""
Object used to represent an individual subscription to the websocket.
This class also exposes certain functions which helps to manage the subscription
such as unsibscribe and subscribe.
"""
def __init__(self, ws, channel_name, symbol, timeframe=None, **kwargs): def __init__(self, ws, channel_name, symbol, timeframe=None, **kwargs):
self.ws = ws self._ws = ws
self.channel_name = channel_name self.channel_name = channel_name
self.symbol = symbol self.symbol = symbol
self.timeframe = timeframe self.timeframe = timeframe
self.is_subscribed_bool = False self.is_subscribed_bool = False
self.key = None self.key = None
if timeframe: self.chan_id = None
self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol) if timeframe:
self.sub_id = int(round(time.time() * 1000)) self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol)
self.send_payload = self._generate_payload(**kwargs) self.sub_id = int(round(time.time() * 1000))
self.send_payload = self._generate_payload(**kwargs)
async def subscribe(self): def confirm_subscription(self, chan_id):
await self.ws.send(json.dumps(self.get_send_payload())) """
Update the subscription to confirmed state
"""
self.is_subscribed_bool = True
self.chan_id = chan_id
async def unsubscribe(self): async def unsubscribe(self):
if not self.is_subscribed(): """
raise Exception("Subscription is not subscribed to websocket") Send an unsubscription request to the bitfinex socket
payload = { 'event': 'unsubscribe', 'chanId': self.chanId } """
await self.ws.send(json.dumps(payload)) if not self.is_subscribed():
raise Exception("Subscription is not subscribed to websocket")
payload = {'event': 'unsubscribe', 'chan_id': self.chan_id}
await self._ws.send(json.dumps(payload))
def confirm_subscription(self, chanId): async def subscribe(self):
self.is_subscribed_bool = True """
self.chanId = chanId Send a subscription request to the bitfinex socket
"""
await self._ws.send(json.dumps(self._get_send_payload()))
def confirm_unsubscribe(self): def confirm_unsubscribe(self):
self.is_subscribed_bool = False """
Update the subscription to unsubscribed state
"""
self.is_subscribed_bool = False
def is_subscribed(self): def is_subscribed(self):
return self.is_subscribed_bool """
Check if the subscription is currently subscribed
def _generate_payload(self, **kwargs): @return bool: True if subscribed else False
payload = { 'event': 'subscribe', 'channel': self.channel_name, 'symbol': self.symbol } """
if self.timeframe: return self.is_subscribed_bool
payload['key'] = self.key
payload.update(**kwargs)
return payload
def get_send_payload(self): def _generate_payload(self, **kwargs):
return self.send_payload payload = {'event': 'subscribe',
'channel': self.channel_name, 'symbol': self.symbol}
if self.timeframe:
payload['key'] = self.key
payload.update(**kwargs)
return payload
def _get_send_payload(self):
return self.send_payload

View File

@@ -1,44 +1,54 @@
"""
Module used to describe all of the different data types
"""
import datetime import datetime
class Trade: class Trade:
""" """
ID integer Trade database id ID integer Trade database id
PAIR string Pair (BTCUSD, ) PAIR string Pair (BTCUSD, ...)
MTS_CREATE integer Execution timestamp MTS_CREATE integer Execution timestamp
ORDER_ID integer Order id ORDER_ID integer Order id
EXEC_AMOUNT float Positive means buy, negative means sell EXEC_AMOUNT float Positive means buy, negative means sell
EXEC_PRICE float Execution price EXEC_PRICE float Execution price
ORDER_TYPE string Order type ORDER_TYPE string Order type
ORDER_PRICE float Order price ORDER_PRICE float Order price
MAKER int 1 if true, 0 if false MAKER int 1 if true, 0 if false
FEE float Fee FEE float Fee
FEE_CURRENCY string Fee currency FEE_CURRENCY string Fee currency
""" """
SHORT = 'SHORT' SHORT = 'SHORT'
LONG = 'LONG' LONG = 'LONG'
def __init__(self, id, pair, mts_create, order_id, amount, price, order_type, def __init__(self, tid, pair, mts_create, order_id, amount, price, order_type,
order_price, maker, fee, fee_currency): order_price, maker, fee, fee_currency):
self.id = id # pylint: disable=invalid-name
self.pair = pair self.id = tid
self.mts_create = mts_create self.pair = pair
self.date = datetime.datetime.fromtimestamp(mts_create/1000.0) self.mts_create = mts_create
self.order_id = order_id self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
self.amount = amount self.order_id = order_id
self.direction = Trade.SHORT if amount < 0 else Trade.LONG self.amount = amount
self.price = price self.direction = Trade.SHORT if amount < 0 else Trade.LONG
self.order_type = order_type self.price = price
self.order_price = order_price self.order_type = order_type
self.maker = maker self.order_price = order_price
self.fee = fee self.maker = maker
self.fee_currency = fee_currency self.fee = fee
self.fee_currency = fee_currency
@staticmethod @staticmethod
def from_raw_rest_trade(raw_trade): def from_raw_rest_trade(raw_trade):
# [24224048, 'tBTCUSD', 1542800024000, 1151353484, 0.09399997, 19963, None, None, -1, -0.000188, 'BTC'] """
return Trade(*raw_trade) Generate a Trade object from a raw trade array
"""
def __str__(self): # [24224048, 'tBTCUSD', 1542800024000, 1151353484, 0.09399997, 19963, None, None,
return "Trade '{}' x {} @ {} <direction='{}' fee={}>".format( # -1, -0.000188, 'BTC']
self.pair, self.amount, self.price, self.direction, self.fee) return Trade(*raw_trade)
def __str__(self):
return "Trade '{}' x {} @ {} <direction='{}' fee={}>".format(
self.pair, self.amount, self.price, self.direction, self.fee)

View File

@@ -1,19 +1,33 @@
"""
Module used to describe all of the different data types
"""
class Wallet: class Wallet:
"""
Stores data relevant to a users wallet such as balance and
currency
"""
def __init__(self, wType, currency, balance, unsettled_interest): def __init__(self, wType, currency, balance, unsettled_interest):
self.type = wType self.type = wType
self.currency = currency self.currency = currency
self.balance = balance self.balance = balance
self.unsettled_interest = unsettled_interest self.unsettled_interest = unsettled_interest
self.key = "{}_{}".format(wType, currency) self.key = "{}_{}".format(wType, currency)
def set_balance(self, data): def set_balance(self, data):
self.balance = data """
Set the balance of the wallet
"""
self.balance = data
def set_unsettled_interest(self, data): def set_unsettled_interest(self, data):
self.unsettled_interest = data """
Set the unsettled interest of the wallet
"""
self.unsettled_interest = data
def __str__(self): def __str__(self):
return "Wallet <'{}_{}' balance='{}' unsettled='{}'>".format( return "Wallet <'{}_{}' balance='{}' unsettled='{}'>".format(
self.type, self.currency, self.balance, self.unsettled_interest) self.type, self.currency, self.balance, self.unsettled_interest)

View File

@@ -1,11 +1,16 @@
name = 'models' """
This module contains a group of different models which
are used to define data types
"""
from .Order import * from .order import Order
from .Trade import * from .trade import Trade
from .OrderBook import * from .order_book import OrderBook
from .Subscription import * from .subscription import Subscription
from .Wallet import * from .wallet import Wallet
from .Position import * from .position import Position
from .FundingLoan import * from .funding_loan import FundingLoan
from .FundingOffer import * from .funding_offer import FundingOffer
from .FundingCredit import * from .funding_credit import FundingCredit
NAME = 'models'

View File

@@ -0,0 +1,104 @@
"""
Module used to describe all of the different data types
"""
class FundingCreditModel:
"""
Enum used to index the location of each value in a raw array
"""
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 FundingCredit:
"""
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, fid, symbol, side, mts_create, mts_update, amount, flags, status, rate,
period, mts_opening, mts_last_payout, notify, hidden, renew, no_close,
position_pair):
# pylint: disable=invalid-name
self.id = fid
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):
"""
Parse a raw credit object into a FundingCredit object
@return FundingCredit
"""
fid = 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 FundingCredit(fid, 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):
string = "FundingCredit '{}' <id={} rate={} amount={} period={} status='{}'>"
return string.format(self.symbol, self.id, self.rate, self.amount,
self.period, self.status)

View File

@@ -0,0 +1,96 @@
"""
Module used to describe all of the different data types
"""
class FundingLoanModel:
"""
Enum used to index the location of each value in a raw array
"""
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, fid, symbol, side, mts_create, mts_update, amount, flags, status, rate,
period, mts_opening, mts_last_payout, notify, hidden, renew, no_close):
# pylint: disable=invalid-name
self.id = fid
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):
"""
Parse a raw funding load into a FundingLoan object
@return FundingLoan
"""
fid = 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(fid, 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)

View File

@@ -0,0 +1,88 @@
"""
Module used to describe all of the different data types
"""
class FundingOfferModel:
"""
Enum used to index the location of each value in a raw array
"""
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, fid, symbol, mts_create, mts_updated, amount, amount_orig, f_type,
flags, status, rate, period, notify, hidden, renew):
# pylint: disable=invalid-name
self.id = fid
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):
"""
Parse a raw funding offer into a RawFunding object
@return FundingOffer
"""
oid = 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(oid, 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)

221
bfxapi/models/order.py Normal file
View File

@@ -0,0 +1,221 @@
"""
Module used to describe all of the different data types
"""
import time
import datetime
class OrderType:
"""
Enum used to describe all of the different order types available for use
"""
MARKET = 'MARKET'
LIMIT = 'LIMIT'
STOP = 'STOP'
STOP_LIMIT = 'STOP LIMIT'
TRAILING_STOP = 'TRAILING STOP'
FILL_OR_KILL = 'FOK'
EXCHANGE_MARKET = 'EXCHANGE MARKET'
EXCHANGE_LIMIT = 'EXCHANGE LIMIT'
EXCHANGE_STOP = 'EXCHANGE STOP'
EXCHANGE_STOP_LIMIT = 'EXCHANGE STOP LIMIT'
EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP'
EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK'
LIMIT_ORDERS = [OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.EXCHANGE_LIMIT,
OrderType.EXCHANGE_STOP_LIMIT, OrderType.FILL_OR_KILL,
OrderType.EXCHANGE_FILL_OR_KILL]
class OrderSide:
"""
Enum used to describe the different directions of an order
"""
BUY = 'buy'
SELL = 'sell'
class OrderClosedModel:
"""
Enum used ad an index match to locate the different values in a
raw order array
"""
ID = 0
GID = 1
CID = 2
SYMBOL = 3
MTS_CREATE = 4
MTS_UPDATE = 5
AMOUNT = 6
AMOUNT_ORIG = 7
TYPE = 8
TYPE_PREV = 9
FLAGS = 12
STATUS = 13
PRICE = 16
PRIVE_AVG = 17
PRICE_TRAILING = 18
PRICE_AUX_LIMIT = 19
NOTIFY = 23
PLACE_ID = 25
class OrderFlags:
"""
Enum used to explain the different values that can be passed in
as flags
"""
HIDDEN = 64
CLOSE = 12
REDUCE_ONLY = 1024
POST_ONLY = 4096
OCO = 16384
def now_in_mills():
"""
Gets the current time in milliseconds
"""
return int(round(time.time() * 1000))
class Order:
"""
ID int64 Order ID
GID int Group ID
CID int Client Order ID
SYMBOL string Pair (tBTCUSD, ...)
MTS_CREATE int Millisecond timestamp of creation
MTS_UPDATE int Millisecond timestamp of update
AMOUNT float Positive means buy, negative means sell.
AMOUNT_ORIG float Original amount
TYPE string The type of the order: LIMIT, MARKET, STOP, TRAILING STOP,
EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK.
TYPE_PREV string Previous order type
FLAGS int Upcoming Params Object (stay tuned)
ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
PRICE float Price
PRICE_AVG float Average price
PRICE_TRAILING float The trailing price
PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT)
HIDDEN int 1 if Hidden, 0 if not hidden
PLACED_ID int If another order caused this order to be placed (OCO) this will be that other
order's ID
"""
Type = OrderType()
Side = OrderSide()
Flags = OrderFlags()
def __init__(self, oid, gid, cid, symbol, mts_create, mts_update, amount,
amount_orig, o_type, typePrev, flags, status, price, price_avg,
price_trailing, price_aux_limit, notfiy, place_id):
# pylint: disable=invalid-name
self.id = oid
self.gid = gid
self.cid = cid
self.symbol = symbol
self.mts_create = mts_create
self.mts_update = mts_update
# self.amount = amount
self.amount = amount
self.amount_orig = amount_orig
self.type = o_type
self.type_prev = typePrev
self.flags = flags
self.status = status
self.price = price
self.price_avg = price_avg
self.price_trailing = price_trailing
self.price_aux_limit = price_aux_limit
self.notfiy = notfiy
self.place_id = place_id
self.tag = ""
self.fee = 0
self.is_pending_bool = True
self.is_confirmed_bool = False
self.is_open_bool = False
self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
# if cancelled then priceAvg wont exist
if price_avg:
# check if order is taker or maker
if self.type in LIMIT_ORDERS:
self.fee = (price_avg * abs(amount)) * 0.001
else:
self.fee = (price_avg * abs(amount)) * 0.002
@staticmethod
def from_raw_order(raw_order):
"""
Parse a raw order object into an Order oject
@return Order
"""
oid = raw_order[OrderClosedModel.ID]
gid = raw_order[OrderClosedModel.GID]
cid = raw_order[OrderClosedModel.CID]
symbol = raw_order[OrderClosedModel.SYMBOL]
mts_create = raw_order[OrderClosedModel.MTS_CREATE]
mts_update = raw_order[OrderClosedModel.MTS_UPDATE]
amount = raw_order[OrderClosedModel.AMOUNT]
amount_orig = raw_order[OrderClosedModel.AMOUNT_ORIG]
o_type = raw_order[OrderClosedModel.TYPE]
type_prev = raw_order[OrderClosedModel.TYPE_PREV]
flags = raw_order[OrderClosedModel.FLAGS]
status = raw_order[OrderClosedModel.STATUS]
price = raw_order[OrderClosedModel.PRICE]
price_avg = raw_order[OrderClosedModel.PRIVE_AVG]
price_trailing = raw_order[OrderClosedModel.PRICE_TRAILING]
price_aux_limit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT]
notfiy = raw_order[OrderClosedModel.NOTIFY]
place_id = raw_order[OrderClosedModel.PLACE_ID]
return Order(oid, gid, cid, symbol, mts_create, mts_update, amount,
amount_orig, o_type, type_prev, flags, status, price, price_avg,
price_trailing, price_aux_limit, notfiy, place_id)
def set_confirmed(self):
"""
Set the state of the order to be confirmed
"""
self.is_pending_bool = False
self.is_confirmed_bool = True
def set_open_state(self, is_open):
"""
Set the is_open state of the order
"""
self.is_open_bool = is_open
def is_open(self):
"""
Check if the order is still open
@return bool: Ture if order open else False
"""
return self.is_open_bool
def is_pending(self):
"""
Check if the state of the order is still pending
@return bool: True if is pending else False
"""
return self.is_pending_bool
def is_confirmed(self):
"""
Check if the order has been confirmed by the bitfinex api
@return bool: True if has been confirmed else False
"""
return self.is_confirmed_bool
def __str__(self):
''' Allow us to print the Order object in a pretty format '''
text = "Order <'{}' mts_create={} status='{}' id={}>"
return text.format(self.symbol, self.mts_create, self.status, self.id)

121
bfxapi/models/order_book.py Normal file
View File

@@ -0,0 +1,121 @@
"""
Module used to describe all of the different data types
"""
import zlib
def prepare_price(price):
"""
Convert the price to an acceptable format
"""
# convert to 4 significant figures
prep_price = '{0:.4f}'.format(price)
# remove decimal place if zero float
return '{0:g}'.format(float(prep_price))
class OrderBook:
"""
Object used to store the state of the orderbook. This can then be used
in one of two ways. To get the checksum of the book or so get the bids/asks
of the book
"""
def __init__(self):
self.asks = []
self.bids = []
def get_bids(self):
"""
Get all of the bids from the orderbook
@return bids Array
"""
return self.bids
def get_asks(self):
"""
Get all of the asks from the orderbook
@return asks Array
"""
return self.asks
def update_from_snapshot(self, data):
"""
Update the orderbook with a raw orderbook snapshot
"""
for order in data:
if len(order) == 4:
if order[3] < 0:
self.bids += [order]
else:
self.asks += [order]
else:
if order[2] < 0:
self.asks += [order]
else:
self.bids += [order]
def update_with(self, order):
"""
Update the orderbook with a single update
"""
if len(order) == 4:
amount = order[3]
count = order[2]
side = self.bids if amount < 0 else self.asks
else:
amount = order[2]
side = self.asks if amount < 0 else self.bids
count = order[1]
price = order[0]
# if first item in ordebook
if len(side) == 0:
side += [order]
return
# match price level
for index, s_order in enumerate(side):
s_price = s_order[0]
if s_price == price:
if count == 0:
del side[index]
return
# remove but add as new below
del side[index]
# if ob is initialised w/o all price levels
if count == 0:
return
# add to book and sort lowest to highest
side += [order]
side.sort(key=lambda x: x[0], reverse=not amount < 0)
return
def checksum(self):
"""
Generate a CRC32 checksum of the orderbook
"""
data = []
# take set of top 25 bids/asks
for index in range(0, 25):
if index < len(self.bids):
bid = self.bids[index]
price = bid[0]
amount = bid[3] if len(bid) == 4 else bid[2]
data += [prepare_price(price)]
data += [str(amount)]
if index < len(self.asks):
ask = self.asks[index]
price = ask[0]
amount = ask[3] if len(ask) == 4 else ask[2]
data += [prepare_price(price)]
data += [str(amount)]
checksum_str = ':'.join(data)
# calculate checksum and force signed integer
checksum = zlib.crc32(checksum_str.encode('utf8')) & 0xffffffff
return checksum

47
bfxapi/models/position.py Normal file
View File

@@ -0,0 +1,47 @@
"""
Module used to describe all of the different data types
"""
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, b_price, m_funding, m_funding_type,
profit_loss, profit_loss_perc, l_price, lev):
self.symbol = symbol
self.status = status
self.amount = amount
self.base_price = b_price
self.margin_funding = m_funding
self.margin_funding_type = m_funding_type
self.profit_loss = profit_loss
self.profit_loss_percentage = profit_loss_perc
self.liquidation_price = l_price
self.leverage = lev
@staticmethod
def from_raw_rest_position(raw_position):
"""
Generate a Position object from a raw position array
@return Position
"""
return Position(*raw_position)
def __str__(self):
''' Allow us to print the Trade object in a pretty format '''
text = "Position '{}' {} x {} <status='{}' pl={}>"
return text.format(self.symbol, self.base_price, self.amount,
self.status, self.profit_loss)

View File

@@ -0,0 +1,74 @@
"""
Module used to describe all of the different data types
"""
import time
import json
class Subscription:
"""
Object used to represent an individual subscription to the websocket.
This class also exposes certain functions which helps to manage the subscription
such as unsibscribe and subscribe.
"""
def __init__(self, ws, channel_name, symbol, timeframe=None, **kwargs):
self._ws = ws
self.channel_name = channel_name
self.symbol = symbol
self.timeframe = timeframe
self.is_subscribed_bool = False
self.key = None
self.chan_id = None
if timeframe:
self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol)
self.sub_id = int(round(time.time() * 1000))
self.send_payload = self._generate_payload(**kwargs)
def confirm_subscription(self, chan_id):
"""
Update the subscription to confirmed state
"""
self.is_subscribed_bool = True
self.chan_id = chan_id
async def unsubscribe(self):
"""
Send an unsubscription request to the bitfinex socket
"""
if not self.is_subscribed():
raise Exception("Subscription is not subscribed to websocket")
payload = {'event': 'unsubscribe', 'chan_id': self.chan_id}
await self._ws.send(json.dumps(payload))
async def subscribe(self):
"""
Send a subscription request to the bitfinex socket
"""
await self._ws.send(json.dumps(self._get_send_payload()))
def confirm_unsubscribe(self):
"""
Update the subscription to unsubscribed state
"""
self.is_subscribed_bool = False
def is_subscribed(self):
"""
Check if the subscription is currently subscribed
@return bool: True if subscribed else False
"""
return self.is_subscribed_bool
def _generate_payload(self, **kwargs):
payload = {'event': 'subscribe',
'channel': self.channel_name, 'symbol': self.symbol}
if self.timeframe:
payload['key'] = self.key
payload.update(**kwargs)
return payload
def _get_send_payload(self):
return self.send_payload

54
bfxapi/models/trade.py Normal file
View File

@@ -0,0 +1,54 @@
"""
Module used to describe all of the different data types
"""
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, tid, pair, mts_create, order_id, amount, price, order_type,
order_price, maker, fee, fee_currency):
# pylint: disable=invalid-name
self.id = tid
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):
"""
Generate a Trade object from a raw trade array
"""
# [24224048, 'tBTCUSD', 1542800024000, 1151353484, 0.09399997, 19963, None, None,
# -1, -0.000188, 'BTC']
return Trade(*raw_trade)
def __str__(self):
return "Trade '{}' x {} @ {} <direction='{}' fee={}>".format(
self.pair, self.amount, self.price, self.direction, self.fee)

33
bfxapi/models/wallet.py Normal file
View File

@@ -0,0 +1,33 @@
"""
Module used to describe all of the different data types
"""
class Wallet:
"""
Stores data relevant to a users wallet such as balance and
currency
"""
def __init__(self, wType, currency, balance, unsettled_interest):
self.type = wType
self.currency = currency
self.balance = balance
self.unsettled_interest = unsettled_interest
self.key = "{}_{}".format(wType, currency)
def set_balance(self, data):
"""
Set the balance of the wallet
"""
self.balance = data
def set_unsettled_interest(self, data):
"""
Set the unsettled interest of the wallet
"""
self.unsettled_interest = data
def __str__(self):
return "Wallet <'{}_{}' balance='{}' unsettled='{}'>".format(
self.type, self.currency, self.balance, self.unsettled_interest)

View File

@@ -1,3 +1,7 @@
"""
This module contains the BFX rest client data types
"""
import asyncio import asyncio
import aiohttp import aiohttp
import time import time
@@ -8,331 +12,355 @@ from ..utils.auth import generate_auth_headers
from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer
from ..models import FundingCredit from ..models import FundingCredit
class BfxRest: 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, params=""):
url = '{}/{}{}'.format(self.host, endpoint, params)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
text = await resp.text()
if resp.status is not 200:
raise Exception('GET {} failed with status {} - {}'
.format(url, resp.status, text))
return await resp.json()
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)
time_difference = (1000 * 60) * 5000
# get now to the nearest min
now = int(round((time.time() // 60 * 60) * 1000))
task_batch = []
for x in range(0, 10):
start = x * time_difference
end = now - (x * time_difference) - time_difference
e2 = endpoint + '&start={}&end={}'.format(start, end)
task_batch += [asyncio.ensure_future(self.fetch(e2))]
self.logger.info("Downloading seed candles from Bitfinex...")
# call all fetch requests async
done, _ = await asyncio.wait(*[ task_batch ])
candles = []
for task in done:
candles += task.result()
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. BFX rest interface contains functions which are used to interact with both the public
and private Bitfinex http api's.
@param symbol symbol string: pair symbol i.e tBTCUSD To use the private api you have to set the API_KEY and API_SECRET variables to your
@param secton string: available values: "last", "hist" api key.
@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): def __init__(self, API_KEY, API_SECRET, host='https://api.bitfinex.com/v2', loop=None,
""" logLevel='INFO', *args, **kwargs):
Get all of the public trades between the start and end period. 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)
@param symbol symbol string: pair symbol i.e tBTCUSD async def fetch(self, endpoint, params=""):
@param start int: millisecond start time """
@param end int: millisecond end time Fetch a GET request from the bitfinex host
@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): @return reponse
""" """
Get the public orderbook for a given symbol. url = '{}/{}{}'.format(self.host, endpoint, params)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
text = await resp.text()
if resp.status is not 200:
raise Exception('GET {} failed with status {} - {}'
.format(url, resp.status, text))
return await resp.json()
@param symbol symbol string: pair symbol i.e tBTCUSD async def post(self, endpoint, data={}, params=""):
@param precision string: level of price aggregation (P0, P1, P2, P3, P4, R0) """
@param length int: number of price points ("25", "100") Request a POST to the bitfinex host
@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): @return response
""" """
Get tickers for the given symbol. Tickers shows you the current best bid and ask, url = '{}/{}'.format(self.host, endpoint)
as well as the last trade price. 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()
@parms symbols symbol string: pair symbol i.e tBTCUSD ##################################################
@return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, # Public Data #
LAST_PRICE, VOLUME, HIGH, LOW ] ##################################################
"""
endpoint = "ticker/{}".format(symbol)
ticker = await self.fetch(endpoint)
return ticker
async def get_public_tickers(self, symbols): async def get_seed_candles(self, symbol):
""" """
Get tickers for the given symbols. Tickers shows you the current best bid and ask, Used by the honey framework, this function gets the last 4k candles.
as well as the last trade price. """
endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol)
time_difference = (1000 * 60) * 5000
# get now to the nearest min
now = int(round((time.time() // 60 * 60) * 1000))
task_batch = []
for x in range(0, 10):
start = x * time_difference
end = now - (x * time_difference) - time_difference
e2 = endpoint + '&start={}&end={}'.format(start, end)
task_batch += [asyncio.ensure_future(self.fetch(e2))]
self.logger.info("Downloading seed candles from Bitfinex...")
# call all fetch requests async
done, _ = await asyncio.wait(*[task_batch])
candles = []
for task in done:
candles += task.result()
candles.sort(key=lambda x: x[0], reverse=True)
self.logger.info("Downloaded {} candles.".format(len(candles)))
return candles
@parms symbols Array<string>: array of symbols i.e [tBTCUSD, tETHUSD] async def get_public_candles(self, symbol, start, end, section='hist',
@return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, tf='1m', limit="100", sort=-1):
LAST_PRICE, VOLUME, HIGH, LOW ] """
""" Get all of the public candles between the start and end period.
endpoint = "tickers/?symbols={}".format(','.join(symbols))
ticker = await self.fetch(endpoint)
return ticker
################################################## @param symbol symbol string: pair symbol i.e tBTCUSD
# Authenticated Data # @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_wallets(self): async def get_public_trades(self, symbol, start, end, limit="100", sort=-1):
""" """
Get all wallets on account associated with API_KEY - Requires authentication. Get all of the public trades between the start and end period.
@return Array <models.Wallet> @param symbol symbol string: pair symbol i.e tBTCUSD
""" @param start int: millisecond start time
endpoint = "auth/r/wallets" @param end int: millisecond end time
raw_wallets = await self.post(endpoint) @param limit int: max number of items in response
return [ Wallet(*rw[:4]) for rw in raw_wallets ] @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_active_orders(self, symbol): async def get_public_books(self, symbol, precision="P0", length=25):
""" """
Get all of the active orders associated with API_KEY - Requires authentication. Get the public orderbook for a given symbol.
@param symbol string: pair symbol i.e tBTCUSD @param symbol symbol string: pair symbol i.e tBTCUSD
@return Array <models.Order> @param precision string: level of price aggregation (P0, P1, P2, P3, P4, R0)
""" @param length int: number of price points ("25", "100")
endpoint = "auth/r/orders/{}".format(symbol) @return Array [ PRICE, COUNT, AMOUNT ]
raw_orders = await self.post(endpoint) """
return [ Order.from_raw_order(ro) for ro in raw_orders ] endpoint = "book/{}/{}".format(symbol, precision)
params = "?len={}".format(length)
books = await self.fetch(endpoint, params)
return books
async def get_order_history(self, symbol, start, end, limit=25, sort=-1): async def get_public_ticker(self, symbol):
""" """
Get all of the orders between the start and end period associated with API_KEY Get tickers for the given symbol. Tickers shows you the current best bid and ask,
- Requires authentication. as well as the last trade price.
@param symbol string: pair symbol i.e tBTCUSD @parms symbols symbol string: pair symbol i.e tBTCUSD
@param start int: millisecond start time @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,
@param end int: millisecond end time DAILY_CHANGE_PERC, LAST_PRICE, VOLUME, HIGH, LOW ]
@param limit int: max number of items in response """
@return Array <models.Order> endpoint = "ticker/{}".format(symbol)
""" ticker = await self.fetch(endpoint)
endpoint = "auth/r/orders/{}/hist".format(symbol) return ticker
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): async def get_public_tickers(self, symbols):
""" """
Get all of the active position associated with API_KEY - Requires authentication. Get tickers for the given symbols. Tickers shows you the current best bid and ask,
as well as the last trade price.
@return Array <models.Position> @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,
endpoint = "auth/r/positions" LAST_PRICE, VOLUME, HIGH, LOW ]
raw_positions = await self.post(endpoint) """
return [ Position.from_raw_rest_position(rp) for rp in raw_positions ] endpoint = "tickers/?symbols={}".format(','.join(symbols))
ticker = await self.fetch(endpoint)
return ticker
async def get_order_trades(self, symbol, order_id): ##################################################
""" # Authenticated Data #
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 async def get_wallets(self):
@param order_id string: id of the order """
@return Array <models.Trade> Get all wallets on account associated with API_KEY - Requires authentication.
"""
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): @return Array <models.Wallet>
""" """
Get all of the trades between the start and end period associated with API_KEY endpoint = "auth/r/wallets"
- Requires authentication. raw_wallets = await self.post(endpoint)
return [Wallet(*rw[:4]) for rw in raw_wallets]
@param symbol string: pair symbol i.e tBTCUSD async def get_active_orders(self, symbol):
@param start int: millisecond start time """
@param end int: millisecond end time Get all of the active orders associated with API_KEY - Requires authentication.
@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): @param symbol string: pair symbol i.e tBTCUSD
""" @return Array <models.Order>
Get all of the funding offers associated with API_KEY - Requires authentication. """
endpoint = "auth/r/orders/{}".format(symbol)
raw_orders = await self.post(endpoint)
return [Order.from_raw_order(ro) for ro in raw_orders]
@return Array <models.FundingOffer> async def get_order_history(self, symbol, start, end, limit=25, sort=-1):
""" """
endpoint = "auth/r/funding/offers/{}".format(symbol) Get all of the orders between the start and end period associated with API_KEY
offers = await self.post(endpoint) - Requires authentication.
return [ FundingOffer.from_raw_offer(o) for o in offers ]
async def get_funding_offer_history(self, symbol, start, end, limit=25): @param symbol string: pair symbol i.e tBTCUSD
""" @param start int: millisecond start time
Get all of the funding offers between the start and end period associated with API_KEY @param end int: millisecond end time
- Requires authentication. @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]
@param symbol string: pair symbol i.e tBTCUSD async def get_active_position(self):
@param start int: millisecond start time """
@param end int: millisecond end time Get all of the active position associated with API_KEY - Requires authentication.
@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): @return Array <models.Position>
""" """
Get all of the funding loans associated with API_KEY - Requires authentication. endpoint = "auth/r/positions"
raw_positions = await self.post(endpoint)
return [Position.from_raw_rest_position(rp) for rp in raw_positions]
@return Array <models.FundingLoan> async def get_order_trades(self, symbol, order_id):
""" """
endpoint = "auth/r/funding/loans/{}".format(symbol) Get all of the trades that have been generated by the given order associated with API_KEY
loans = await self.post(endpoint) - Requires authentication.
return [ FundingLoan.from_raw_loan(o) for o in loans ]
async def get_funding_loan_history(self, symbol, start, end, limit=25): @param symbol string: pair symbol i.e tBTCUSD
""" @param order_id string: id of the order
Get all of the funding loans between the start and end period associated with API_KEY @return Array <models.Trade>
- Requires authentication. """
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]
@param symbol string: pair symbol i.e tBTCUSD async def get_trades(self, symbol, start, end, limit=25):
@param start int: millisecond start time """
@param end int: millisecond end time Get all of the trades between the start and end period associated with API_KEY
@param limit int: max number of items in response - Requires authentication.
@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): @param symbol string: pair symbol i.e tBTCUSD
endpoint = "auth/r/funding/credits/{}".format(symbol) @param start int: millisecond start time
credits = await self.post(endpoint) @param end int: millisecond end time
return [ FundingCredit.from_raw_credit(c) for c in credits] @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_credit_history(self, symbol, start, end, limit=25): async def get_funding_offers(self, symbol):
""" """
Get all of the funding credits between the start and end period associated with API_KEY Get all of the funding offers associated with API_KEY - Requires authentication.
- Requires authentication.
@param symbol string: pair symbol i.e tBTCUSD @return Array <models.FundingOffer>
@param start int: millisecond start time """
@param end int: millisecond end time endpoint = "auth/r/funding/offers/{}".format(symbol)
@param limit int: max number of items in response offers = await self.post(endpoint)
@return Array <models.FundingCredit> return [FundingOffer.from_raw_offer(o) for o in offers]
"""
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]
################################################## async def get_funding_offer_history(self, symbol, start, end, limit=25):
# Orders # """
################################################## Get all of the funding offers between the start and end period associated with API_KEY
- Requires authentication.
async def __submit_order(self, symbol, amount, price, oType=Order.Type.LIMIT, @param symbol string: pair symbol i.e tBTCUSD
is_hidden=False, is_postonly=False, use_all_available=False, stop_order=False, @param start int: millisecond start time
stop_buy_price=0, stop_sell_price=0): @param end int: millisecond end time
""" @param limit int: max number of items in response
Submit a new order @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]
@param symbol: the name of the symbol i.e 'tBTCUSD async def get_funding_loans(self, symbol):
@param amount: order size: how much you want to buy/sell, """
a negative amount indicates a sell order and positive a buy order Get all of the funding loans associated with API_KEY - Requires authentication.
@param price: the price you want to buy/sell at (must be positive)
@param oType: order type, see Order.Type enum @return Array <models.FundingLoan>
@param is_hidden: True if order should be hidden from orderbooks """
@param is_postonly: True if should be post only. Only relevant for limit endpoint = "auth/r/funding/loans/{}".format(symbol)
@param use_all_available: True if order should use entire balance loans = await self.post(endpoint)
@param stop_order: True to set an additional STOP OCO order linked to the return [FundingLoan.from_raw_loan(o) for o in loans]
current order
@param stop_buy_price: set the OCO stop buy price (requires stop_order True) async def get_funding_loan_history(self, symbol, start, end, limit=25):
@param stop_sell_price: set the OCO stop sell price (requires stop_order True) """
""" Get all of the funding loans between the start and end period associated with API_KEY
raise NotImplementedError( - Requires authentication.
"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 @param symbol string: pair symbol i.e tBTCUSD
use_all_balance = 1 if use_all_available else 0 @param start int: millisecond start time
payload = {} @param end int: millisecond end time
payload['symbol'] = symbol @param limit int: max number of items in response
payload['amount'] = abs(amount) @return Array <models.FundingLoan>
payload['price'] = price """
payload['side'] = side endpoint = "auth/r/funding/loans/{}/hist".format(symbol)
payload['type'] = oType params = "?start={}&end={}&limit={}".format(start, end, limit)
payload['is_hidden'] = is_hidden loans = await self.post(endpoint, params=params)
payload['is_postonly'] = is_postonly return [FundingLoan.from_raw_loan(o) for o in loans]
payload['use_all_available'] = use_all_balance
payload['ocoorder'] = stop_order async def get_funding_credits(self, symbol):
if stop_order: endpoint = "auth/r/funding/credits/{}".format(symbol)
payload['buy_price_oco'] = stop_buy_price credits = await self.post(endpoint)
payload['sell_price_oco'] = stop_sell_price return [FundingCredit.from_raw_credit(c) for c in credits]
endpoint = 'order/new'
return await self.post(endpoint, data=payload) 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)

View File

@@ -1,3 +1,7 @@
"""
Module used to describe all of the different data types
"""
import logging import logging
RESET_SEQ = "\033[0m" RESET_SEQ = "\033[0m"
@@ -22,13 +26,19 @@ KEYWORD_COLORS = {
} }
def formatter_message(message, use_color = True): def formatter_message(message, use_color = True):
if use_color: """
message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ) Syntax highlight certain keywords
else: """
message = message.replace("$RESET", "").replace("$BOLD", "") if use_color:
return message message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
else:
message = message.replace("$RESET", "").replace("$BOLD", "")
return message
def format_word(message, word, color_seq, bold=False, underline=False): def format_word(message, word, color_seq, bold=False, underline=False):
"""
Surround the fiven word with a sequence
"""
replacer = color_seq + word + RESET_SEQ replacer = color_seq + word + RESET_SEQ
if underline: if underline:
replacer = UNDERLINE_SEQ + replacer replacer = UNDERLINE_SEQ + replacer
@@ -45,6 +55,9 @@ class Formatter(logging.Formatter):
self.use_color = use_color self.use_color = use_color
def format(self, record): def format(self, record):
"""
Format and highlight certain keywords
"""
levelname = record.levelname levelname = record.levelname
if self.use_color and levelname in KEYWORD_COLORS: if self.use_color and levelname in KEYWORD_COLORS:
levelname_color = KEYWORD_COLORS[levelname] + levelname + RESET_SEQ levelname_color = KEYWORD_COLORS[levelname] + levelname + RESET_SEQ
@@ -71,6 +84,9 @@ class CustomLogger(logging.Logger):
return return
def trade(self, message, *args, **kws): def trade(self, message, *args, **kws):
"""
Print a syntax highlighted trade signal
"""
if self.isEnabledFor(self.TRADE): if self.isEnabledFor(self.TRADE):
message = format_word(message, 'CLOSED ', YELLOW, bold=True) message = format_word(message, 'CLOSED ', YELLOW, bold=True)
message = format_word(message, 'OPENED ', LIGHT_BLUE, bold=True) message = format_word(message, 'OPENED ', LIGHT_BLUE, bold=True)

View File

@@ -1,8 +1,18 @@
"""
This module is used to house all of the functions which are used
to handle the http authentication of the client
"""
import hashlib import hashlib
import hmac import hmac
import time import time
def generate_auth_payload(API_KEY, API_SECRET): def generate_auth_payload(API_KEY, API_SECRET):
"""
Generate a signed payload
@return json Oject headers
"""
nonce = _gen_nonce() nonce = _gen_nonce()
authMsg, sig = _gen_signature(API_KEY, API_SECRET, nonce) authMsg, sig = _gen_signature(API_KEY, API_SECRET, nonce)
@@ -15,6 +25,9 @@ def generate_auth_payload(API_KEY, API_SECRET):
} }
def generate_auth_headers(API_KEY, API_SECRET, path, body): def generate_auth_headers(API_KEY, API_SECRET, path, body):
"""
Generate headers for a signed payload
"""
nonce = str(_gen_nonce()) nonce = str(_gen_nonce())
signature = "/api/v2/{}{}{}".format(path, nonce, body) signature = "/api/v2/{}{}{}".format(path, nonce, body)
h = hmac.new(API_SECRET.encode('utf8'), signature.encode('utf8'), hashlib.sha384) h = hmac.new(API_SECRET.encode('utf8'), signature.encode('utf8'), hashlib.sha384)

View File

@@ -1,3 +1,7 @@
"""
Module used to house the bitfine websocket client
"""
import asyncio import asyncio
import json import json
import time import time
@@ -10,397 +14,410 @@ from .OrderManager import OrderManager
from ..utils.auth import generate_auth_payload from ..utils.auth import generate_auth_payload
from ..models import Order, Trade, OrderBook from ..models import Order, Trade, OrderBook
class Flags:
DEC_S = 9
TIME_S = 32
TIMESTAMP = 32768
SEQ_ALL = 65536
CHECKSUM = 131072
strings = { class Flags:
9: 'DEC_S', """
32: 'TIME_S', Enum used to index the available flags used in the authentication
32768: 'TIMESTAMP', websocket packet
65536: 'SEQ_ALL', """
131072: 'CHECKSUM' DEC_S = 9
} TIME_S = 32
TIMESTAMP = 32768
SEQ_ALL = 65536
CHECKSUM = 131072
strings = {
9: 'DEC_S',
32: 'TIME_S',
32768: 'TIMESTAMP',
65536: 'SEQ_ALL',
131072: 'CHECKSUM'
}
def _parse_candle(cData, symbol, tf): def _parse_candle(cData, symbol, tf):
return { return {
'mts': cData[0], 'mts': cData[0],
'open': cData[1], 'open': cData[1],
'close': cData[2], 'close': cData[2],
'high': cData[3], 'high': cData[3],
'low': cData[4], 'low': cData[4],
'volume': cData[5], 'volume': cData[5],
'symbol': symbol, 'symbol': symbol,
'tf': tf 'tf': tf
} }
def _parse_trade_snapshot_item(tData, symbol): def _parse_trade_snapshot_item(tData, symbol):
return { return {
'mts': tData[3], 'mts': tData[3],
'price': tData[4], 'price': tData[4],
'amount': tData[5], 'amount': tData[5],
'symbol': symbol 'symbol': symbol
} }
def _parse_trade(tData, symbol): def _parse_trade(tData, symbol):
return { return {
'mts': tData[1], 'mts': tData[1],
'price': tData[3], 'price': tData[3],
'amount': tData[2], 'amount': tData[2],
'symbol': symbol 'symbol': symbol
} }
class BfxWebsocket(GenericWebsocket): class BfxWebsocket(GenericWebsocket):
""" """
More complex websocket that heavily relies on the btfxwss module. This websocket requires More complex websocket that heavily relies on the btfxwss module.
authentication and is capable of handling orders. This websocket requires authentication and is capable of handling orders.
https://github.com/Crypto-toolbox/btfxwss https://github.com/Crypto-toolbox/btfxwss
""" """
ERRORS = { ERRORS = {
10000: 'Unknown event', 10000: 'Unknown event',
10001: 'Generic error', 10001: 'Generic error',
10008: 'Concurrency error', 10008: 'Concurrency error',
10020: 'Request parameters error', 10020: 'Request parameters error',
10050: 'Configuration setup failed', 10050: 'Configuration setup failed',
10100: 'Failed authentication', 10100: 'Failed authentication',
10111: 'Error in authentication request payload', 10111: 'Error in authentication request payload',
10112: 'Error in authentication request signature', 10112: 'Error in authentication request signature',
10113: 'Error in authentication request encryption', 10113: 'Error in authentication request encryption',
10114: 'Error in authentication request nonce', 10114: 'Error in authentication request nonce',
10200: 'Error in un-authentication request', 10200: 'Error in un-authentication request',
10300: 'Subscription Failed (generic)', 10300: 'Subscription Failed (generic)',
10301: 'Already Subscribed', 10301: 'Already Subscribed',
10302: 'Unknown channel', 10302: 'Unknown channel',
10400: 'Subscription Failed (generic)', 10400: 'Subscription Failed (generic)',
10401: 'Not subscribed', 10401: 'Not subscribed',
11000: 'Not ready, try again later', 11000: 'Not ready, try again later',
20000: 'User is invalid!', 20000: 'User is invalid!',
20051: 'Websocket server stopping', 20051: 'Websocket server stopping',
20060: 'Websocket server resyncing', 20060: 'Websocket server resyncing',
20061: 'Websocket server resync complete' 20061: 'Websocket server resync complete'
}
def __init__(self, API_KEY=None, API_SECRET=None, host='wss://api.bitfinex.com/ws/2', manageOrderBooks=False,
dead_man_switch=False, logLevel='INFO', *args, **kwargs):
self.API_KEY=API_KEY
self.API_SECRET=API_SECRET
self.manageOrderBooks = manageOrderBooks
self.dead_man_switch = dead_man_switch
self.pendingOrders = {}
self.orderBooks = {}
super(BfxWebsocket, self).__init__(host, logLevel=logLevel, *args, **kwargs)
self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel)
self.orderManager = OrderManager(self, logLevel=logLevel)
self.wallets = WalletManager()
self._WS_DATA_HANDLERS = {
'tu': self._trade_update_handler,
'wu': self._wallet_update_handler,
'hb': self._heart_beat_handler,
'te': self._trade_executed_handler,
'oc': self._order_closed_handler,
'ou': self._order_update_handler,
'on': self._order_new_handler,
'os': self._order_snapshot_handler,
'ws': self._wallet_snapshot_handler,
'ps': self._position_snapshot_handler,
'fos': self._funding_offer_snapshot_handler,
'fcs': self._funding_credit_snapshot_handler,
'fls': self._funding_load_snapshot_handler,
'bu': self._balance_update_handler,
'n': self._notification_handler,
'miu': self._margin_info_update_handler,
'fiu': self._funding_info_update_handler
} }
self._WS_SYSTEM_HANDLERS = { def __init__(self, API_KEY=None, API_SECRET=None, host='wss://api.bitfinex.com/ws/2',
'info': self._system_info_handler, manageOrderBooks=False, dead_man_switch=False, logLevel='INFO', *args, **kwargs):
'subscribed': self._system_subscribed_handler, self.API_KEY = API_KEY
'unsubscribed': self._system_unsubscribe_handler, self.API_SECRET = API_SECRET
'error': self._system_error_handler, self.manageOrderBooks = manageOrderBooks
'auth': self._system_auth_handler, self.dead_man_switch = dead_man_switch
'conf': self._system_conf_handler self.pendingOrders = {}
} self.orderBooks = {}
async def _ws_system_handler(self, msg): super(BfxWebsocket, self).__init__(
eType = msg.get('event') host, logLevel=logLevel, *args, **kwargs)
if eType in self._WS_SYSTEM_HANDLERS: self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel)
await self._WS_SYSTEM_HANDLERS[eType](msg) self.orderManager = OrderManager(self, logLevel=logLevel)
else: self.wallets = WalletManager()
self.logger.warn("Unknown websocket event: '{}' {}".format(eType, msg))
async def _ws_data_handler(self, data): self._WS_DATA_HANDLERS = {
dataEvent = data[1] 'tu': self._trade_update_handler,
chanId = data[0] 'wu': self._wallet_update_handler,
'hb': self._heart_beat_handler,
if type(dataEvent) is str and dataEvent in self._WS_DATA_HANDLERS: 'te': self._trade_executed_handler,
return await self._WS_DATA_HANDLERS[dataEvent](data) 'oc': self._order_closed_handler,
elif self.subscriptionManager.is_subscribed(chanId): 'ou': self._order_update_handler,
subscription = self.subscriptionManager.get(chanId) 'on': self._order_new_handler,
# candles do not have an event 'os': self._order_snapshot_handler,
if subscription.channel_name == 'candles': 'ws': self._wallet_snapshot_handler,
await self._candle_handler(data) 'ps': self._position_snapshot_handler,
if subscription.channel_name == 'book': 'fos': self._funding_offer_snapshot_handler,
await self._order_book_handler(data) 'fcs': self._funding_credit_snapshot_handler,
if subscription.channel_name == 'trades': 'fls': self._funding_load_snapshot_handler,
await self._trade_handler(data) 'bu': self._balance_update_handler,
else: 'n': self._notification_handler,
self.logger.warn("Unknown data event: '{}' {}".format(dataEvent, data)) 'miu': self._margin_info_update_handler,
'fiu': self._funding_info_update_handler
async def _system_info_handler(self, data):
self.logger.info(data)
if data.get('serverId', None):
## connection has been established
await self.on_open()
async def _system_conf_handler(self, data):
flag = data.get('flags')
status = data.get('status')
if flag not in Flags.strings:
self.logger.warn("Unknown config value set {}".format(flag))
return
flagString = Flags.strings[flag]
if status == "OK":
self.logger.info("Enabled config flag {}".format(flagString))
else:
self.logger.error("Unable to enable config flag {}".format(flagString))
async def _system_subscribed_handler(self, data):
await self.subscriptionManager.confirm_subscription(data)
async def _system_unsubscribe_handler(self, data):
await self.subscriptionManager.confirm_unsubscribe(data)
async def _system_error_handler(self, data):
self._emit('error', data)
async def _system_auth_handler(self, data):
if data.get('status') == 'FAILED':
raise AuthError(self.ERRORS[data.get('code')])
else:
self._emit('authenticated', data)
self.logger.info("Authentication successful.")
async def _trade_update_handler(self, data):
tData = data[2]
# [209, 'tu', [312372989, 1542303108930, 0.35, 5688.61834032]]
if self.subscriptionManager.is_subscribed(data[0]):
symbol = self.subscriptionManager.get(data[0]).symbol
tradeObj = _parse_trade(tData, symbol)
self._emit('new_trade', tradeObj)
async def _trade_executed_handler(self, data):
tData = data[2]
# [209, 'te', [312372989, 1542303108930, 0.35, 5688.61834032]]
if self.subscriptionManager.is_subscribed(data[0]):
symbol = self.subscriptionManager.get(data[0]).symbol
tradeObj = _parse_trade(tData, symbol)
self._emit('new_trade', tradeObj)
async def _wallet_update_handler(self, data):
# [0,"wu",["exchange","USD",89134.66933283,0]]
uw = self.wallets._update_from_event(data)
self._emit('wallet_update', uw)
self.logger.info("Wallet update: {}".format(uw))
async def _heart_beat_handler(self, data):
self.logger.debug("Heartbeat - {}".format(self.host))
async def _margin_info_update_handler(self, data):
self._emit('margin_info_update', data)
self.logger.info("Margin info update: {}".format(data))
async def _funding_info_update_handler(self, data):
self._emit('funding_info_update', data)
self.logger.info("Funding info update: {}".format(data))
async def _notification_handler(self, data):
# [0, 'n', [1542289340429, 'on-req', None, None,
# [1151350600, None, 1542289341196, 'tBTCUSD', None, None, 0.01, None, 'EXCHANGE MARKET',
# None, None, None, None, None, None, None, 18970, None, 0, 0, None, None, None, 0, None,
# None, None, None, None, None, None, None], None, 'SUCCESS', 'Submitting exchange market buy order for 0.01 BTC.']]
nInfo = data[2]
self._emit('notification', nInfo)
notificationType = nInfo[6]
notificationText = nInfo[7]
if notificationType == 'ERROR':
# self._emit('error', notificationText)
self.logger.error("Notification ERROR: {}".format(notificationText))
else:
self.logger.info("Notification SUCCESS: {}".format(notificationText))
async def _balance_update_handler(self, data):
self.logger.info('Balance update: {}'.format(data[2]))
self._emit('balance_update', data[2])
async def _order_closed_handler(self, data):
await self.orderManager.confirm_order_closed(data)
async def _order_update_handler(self, data):
await self.orderManager.confirm_order_update(data)
async def _order_new_handler(self, data):
await self.orderManager.confirm_order_new(data)
async def _order_snapshot_handler(self, data):
await self.orderManager.build_from_order_snapshot(data)
async def _wallet_snapshot_handler(self, data):
wallets = self.wallets._update_from_snapshot(data)
self._emit('wallet_snapshot', wallets)
async def _position_snapshot_handler(self, data):
self._emit('position_snapshot', data)
self.logger.info("Position snapshot: {}".format(data))
async def _funding_offer_snapshot_handler(self, data):
self._emit('funding_offer_snapshot', data)
self.logger.info("Funding offer snapshot: {}".format(data))
async def _funding_load_snapshot_handler(self, data):
self._emit('funding_loan_snapshot', data[2])
self.logger.info("Funding loan snapshot: {}".format(data))
async def _funding_credit_snapshot_handler(self, data):
self._emit('funding_credit_snapshot', data[2])
self.logger.info("Funding credit snapshot: {}".format(data))
async def _trade_handler(self, data):
symbol = self.subscriptionManager.get(data[0]).symbol
if type(data[1]) is list:
data = data[1]
# Process the batch of seed trades on
# connection
data.reverse()
for t in data:
trade = {
'mts': t[1],
'amount': t[2],
'price': t[3],
'symbol': symbol
} }
self._emit('seed_trade', trade)
async def _candle_handler(self, data): self._WS_SYSTEM_HANDLERS = {
subscription = self.subscriptionManager.get(data[0]) 'info': self._system_info_handler,
if type(data[1][0]) is list: 'subscribed': self._system_subscribed_handler,
# Process the batch of seed candles on 'unsubscribed': self._system_unsubscribe_handler,
# websocket subscription 'error': self._system_error_handler,
candlesSnapshot = data[1] 'auth': self._system_auth_handler,
candlesSnapshot.reverse() 'conf': self._system_conf_handler
for c in candlesSnapshot: }
candle = _parse_candle(c, subscription.symbol, subscription.timeframe)
self._emit('seed_candle', candle)
else:
candle = _parse_candle(data[1], subscription.symbol, subscription.timeframe)
self._emit('new_candle', candle)
async def _order_book_handler(self, data):
obInfo = data[1]
chanId = data[0]
subscription = self.subscriptionManager.get(data[0])
symbol = subscription.symbol
if data[1] == "cs":
dChecksum = data[2] & 0xffffffff # force to signed int
checksum = self.orderBooks[symbol].checksum()
# force checksums to signed integers
isValid = (dChecksum) == (checksum)
if isValid:
self.logger.debug("Checksum orderbook validation for '{}' successful."
.format(symbol))
else:
self.logger.warn("Checksum orderbook invalid for '{}'. Resetting subscription."
.format(symbol))
# re-build orderbook with snapshot
await self.subscriptionManager.resubscribe(chanId)
return
if obInfo == []:
self.orderBooks[symbol] = OrderBook()
return
isSnapshot = type(obInfo[0]) is list
if isSnapshot:
self.orderBooks[symbol] = OrderBook()
self.orderBooks[symbol].updateFromSnapshot(obInfo)
self._emit('order_book_snapshot', { 'symbol': symbol, 'data': obInfo })
else:
self.orderBooks[symbol].updateWith(obInfo)
self._emit('order_book_update', { 'symbol': symbol, 'data': obInfo })
async def on_message(self, message): async def _ws_system_handler(self, msg):
self.logger.debug(message) eType = msg.get('event')
msg = json.loads(message) if eType in self._WS_SYSTEM_HANDLERS:
self._emit('all', msg) await self._WS_SYSTEM_HANDLERS[eType](msg)
if type(msg) is dict: else:
# System messages are received as json self.logger.warn(
await self._ws_system_handler(msg) "Unknown websocket event: '{}' {}".format(eType, msg))
elif type(msg) is list:
# All data messages are received as a list
await self._ws_data_handler(msg)
else:
self.logger.warn('Unknown websocket response: {}'.format(msg))
async def _ws_authenticate_socket(self): async def _ws_data_handler(self, data):
jdata = generate_auth_payload(self.API_KEY, self.API_SECRET) dataEvent = data[1]
if self.dead_man_switch: chan_id = data[0]
jdata['dms'] = 4
await self.ws.send(json.dumps(jdata))
async def on_open(self): if type(dataEvent) is str and dataEvent in self._WS_DATA_HANDLERS:
self.logger.info("Websocket opened.") return await self._WS_DATA_HANDLERS[dataEvent](data)
self._emit('connected') elif self.subscriptionManager.is_subscribed(chan_id):
# Orders are simulated in backtest mode subscription = self.subscriptionManager.get(chan_id)
if self.API_KEY and self.API_SECRET: # candles do not have an event
await self._ws_authenticate_socket() if subscription.channel_name == 'candles':
# enable order book checksums await self._candle_handler(data)
if self.manageOrderBooks: if subscription.channel_name == 'book':
await self.enable_flag(Flags.CHECKSUM) await self._order_book_handler(data)
if subscription.channel_name == 'trades':
await self._trade_handler(data)
else:
self.logger.warn(
"Unknown data event: '{}' {}".format(dataEvent, data))
async def _send_auth_command(self, channel_name, data): async def _system_info_handler(self, data):
payload = [0, channel_name, None, data] self.logger.info(data)
await self.ws.send(json.dumps(payload)) if data.get('serverId', None):
# connection has been established
await self.on_open()
async def enable_flag(self, flag): async def _system_conf_handler(self, data):
payload = { flag = data.get('flags')
"event": 'conf', status = data.get('status')
"flags": flag if flag not in Flags.strings:
} self.logger.warn("Unknown config value set {}".format(flag))
await self.ws.send(json.dumps(payload)) return
flagString = Flags.strings[flag]
if status == "OK":
self.logger.info("Enabled config flag {}".format(flagString))
else:
self.logger.error(
"Unable to enable config flag {}".format(flagString))
def get_orderbook(self, symbol): async def _system_subscribed_handler(self, data):
return self.orderBooks.get(symbol, None) await self.subscriptionManager.confirm_subscription(data)
async def subscribe(self, *args, **kwargs): async def _system_unsubscribe_handler(self, data):
return await self.subscriptionManager.subscribe(*args, **kwargs) await self.subscriptionManager.confirm_unsubscribe(data)
async def unsubscribe(self, *args, **kwargs): async def _system_error_handler(self, data):
return await self.subscriptionManager.unsubscribe(*args, **kwargs) self._emit('error', data)
async def resubscribe(self, *args, **kwargs): async def _system_auth_handler(self, data):
return await self.subscriptionManager.resubscribe(*args, **kwargs) if data.get('status') == 'FAILED':
raise AuthError(self.ERRORS[data.get('code')])
else:
self._emit('authenticated', data)
self.logger.info("Authentication successful.")
async def unsubscribe_all(self, *args, **kwargs): async def _trade_update_handler(self, data):
return await self.subscriptionManager.unsubscribe_all(*args, **kwargs) tData = data[2]
# [209, 'tu', [312372989, 1542303108930, 0.35, 5688.61834032]]
if self.subscriptionManager.is_subscribed(data[0]):
symbol = self.subscriptionManager.get(data[0]).symbol
tradeObj = _parse_trade(tData, symbol)
self._emit('new_trade', tradeObj)
async def resubscribe_all(self, *args, **kwargs): async def _trade_executed_handler(self, data):
return await self.subscriptionManager.resubscribe_all(*args, **kwargs) tData = data[2]
# [209, 'te', [312372989, 1542303108930, 0.35, 5688.61834032]]
if self.subscriptionManager.is_subscribed(data[0]):
symbol = self.subscriptionManager.get(data[0]).symbol
tradeObj = _parse_trade(tData, symbol)
self._emit('new_trade', tradeObj)
async def submit_order(self, *args, **kwargs): async def _wallet_update_handler(self, data):
return await self.orderManager.submit_order(*args, **kwargs) # [0,"wu",["exchange","USD",89134.66933283,0]]
uw = self.wallets._update_from_event(data)
self._emit('wallet_update', uw)
self.logger.info("Wallet update: {}".format(uw))
async def update_order(self, *args, **kwargs): async def _heart_beat_handler(self, data):
return await self.orderManager.update_order(*args, **kwargs) self.logger.debug("Heartbeat - {}".format(self.host))
async def cancel_order(self, *args, **kwargs): async def _margin_info_update_handler(self, data):
return await self.orderManager.cancel_order(*args, **kwargs) self._emit('margin_info_update', data)
self.logger.info("Margin info update: {}".format(data))
async def cancel_all_orders(self, *args, **kwargs): async def _funding_info_update_handler(self, data):
return await self.orderManager.cancel_all_orders(*args, **kwargs) self._emit('funding_info_update', data)
self.logger.info("Funding info update: {}".format(data))
async def cancel_order_multi(self, *args, **kwargs):
return await self.cancel_order_multi(*args, **kwargs) async def _notification_handler(self, data):
nInfo = data[2]
self._emit('notification', nInfo)
notificationType = nInfo[6]
notificationText = nInfo[7]
if notificationType == 'ERROR':
# self._emit('error', notificationText)
self.logger.error(
"Notification ERROR: {}".format(notificationText))
else:
self.logger.info(
"Notification SUCCESS: {}".format(notificationText))
async def _balance_update_handler(self, data):
self.logger.info('Balance update: {}'.format(data[2]))
self._emit('balance_update', data[2])
async def _order_closed_handler(self, data):
await self.orderManager.confirm_order_closed(data)
async def _order_update_handler(self, data):
await self.orderManager.confirm_order_update(data)
async def _order_new_handler(self, data):
await self.orderManager.confirm_order_new(data)
async def _order_snapshot_handler(self, data):
await self.orderManager.build_from_order_snapshot(data)
async def _wallet_snapshot_handler(self, data):
wallets = self.wallets._update_from_snapshot(data)
self._emit('wallet_snapshot', wallets)
async def _position_snapshot_handler(self, data):
self._emit('position_snapshot', data)
self.logger.info("Position snapshot: {}".format(data))
async def _funding_offer_snapshot_handler(self, data):
self._emit('funding_offer_snapshot', data)
self.logger.info("Funding offer snapshot: {}".format(data))
async def _funding_load_snapshot_handler(self, data):
self._emit('funding_loan_snapshot', data[2])
self.logger.info("Funding loan snapshot: {}".format(data))
async def _funding_credit_snapshot_handler(self, data):
self._emit('funding_credit_snapshot', data[2])
self.logger.info("Funding credit snapshot: {}".format(data))
async def _trade_handler(self, data):
symbol = self.subscriptionManager.get(data[0]).symbol
if type(data[1]) is list:
data = data[1]
# Process the batch of seed trades on
# connection
data.reverse()
for t in data:
trade = {
'mts': t[1],
'amount': t[2],
'price': t[3],
'symbol': symbol
}
self._emit('seed_trade', trade)
async def _candle_handler(self, data):
subscription = self.subscriptionManager.get(data[0])
if type(data[1][0]) is list:
# Process the batch of seed candles on
# websocket subscription
candlesSnapshot = data[1]
candlesSnapshot.reverse()
for c in candlesSnapshot:
candle = _parse_candle(
c, subscription.symbol, subscription.timeframe)
self._emit('seed_candle', candle)
else:
candle = _parse_candle(
data[1], subscription.symbol, subscription.timeframe)
self._emit('new_candle', candle)
async def _order_book_handler(self, data):
obInfo = data[1]
chan_id = data[0]
subscription = self.subscriptionManager.get(data[0])
symbol = subscription.symbol
if data[1] == "cs":
dChecksum = data[2] & 0xffffffff # force to signed int
checksum = self.orderBooks[symbol].checksum()
# force checksums to signed integers
isValid = (dChecksum) == (checksum)
if isValid:
msg = "Checksum orderbook validation for '{}' successful."
self.logger.debug(msg.format(symbol))
else:
msg = "Checksum orderbook invalid for '{}'. Resetting subscription."
self.logger.warn(msg.format(symbol))
# re-build orderbook with snapshot
await self.subscriptionManager.resubscribe(chan_id)
return
if obInfo == []:
self.orderBooks[symbol] = OrderBook()
return
isSnapshot = type(obInfo[0]) is list
if isSnapshot:
self.orderBooks[symbol] = OrderBook()
self.orderBooks[symbol].update_from_snapshot(obInfo)
self._emit('order_book_snapshot', {
'symbol': symbol, 'data': obInfo})
else:
self.orderBooks[symbol].update_with(obInfo)
self._emit('order_book_update', {'symbol': symbol, 'data': obInfo})
async def on_message(self, message):
self.logger.debug(message)
msg = json.loads(message)
self._emit('all', msg)
if type(msg) is dict:
# System messages are received as json
await self._ws_system_handler(msg)
elif type(msg) is list:
# All data messages are received as a list
await self._ws_data_handler(msg)
else:
self.logger.warn('Unknown websocket response: {}'.format(msg))
async def _ws_authenticate_socket(self):
jdata = generate_auth_payload(self.API_KEY, self.API_SECRET)
if self.dead_man_switch:
jdata['dms'] = 4
await self.ws.send(json.dumps(jdata))
async def on_open(self):
self.logger.info("Websocket opened.")
self._emit('connected')
# Orders are simulated in backtest mode
if self.API_KEY and self.API_SECRET:
await self._ws_authenticate_socket()
# enable order book checksums
if self.manageOrderBooks:
await self.enable_flag(Flags.CHECKSUM)
async def _send_auth_command(self, channel_name, data):
payload = [0, channel_name, None, data]
await self.ws.send(json.dumps(payload))
async def enable_flag(self, flag):
payload = {
"event": 'conf',
"flags": flag
}
await self.ws.send(json.dumps(payload))
def get_orderbook(self, symbol):
return self.orderBooks.get(symbol, None)
async def subscribe(self, *args, **kwargs):
return await self.subscriptionManager.subscribe(*args, **kwargs)
async def unsubscribe(self, *args, **kwargs):
return await self.subscriptionManager.unsubscribe(*args, **kwargs)
async def resubscribe(self, *args, **kwargs):
return await self.subscriptionManager.resubscribe(*args, **kwargs)
async def unsubscribe_all(self, *args, **kwargs):
return await self.subscriptionManager.unsubscribe_all(*args, **kwargs)
async def resubscribe_all(self, *args, **kwargs):
return await self.subscriptionManager.resubscribe_all(*args, **kwargs)
async def submit_order(self, *args, **kwargs):
return await self.orderManager.submit_order(*args, **kwargs)
async def update_order(self, *args, **kwargs):
return await self.orderManager.update_order(*args, **kwargs)
async def cancel_order(self, *args, **kwargs):
return await self.orderManager.cancel_order(*args, **kwargs)
async def cancel_all_orders(self, *args, **kwargs):
return await self.orderManager.cancel_all_orders(*args, **kwargs)
async def cancel_order_multi(self, *args, **kwargs):
return await self.cancel_order_multi(*args, **kwargs)

View File

@@ -1,3 +1,7 @@
"""
Module used as a interfeace to describe a generick websocket client
"""
import asyncio import asyncio
import websockets import websockets
import json import json
@@ -5,66 +9,106 @@ import json
from pyee import EventEmitter from pyee import EventEmitter
from ..utils.CustomLogger import CustomLogger from ..utils.CustomLogger import CustomLogger
class AuthError(Exception): pass
class AuthError(Exception):
"""
Thrown whenever there is a problem with the authentication packet
"""
pass
def is_json(myjson): def is_json(myjson):
try: try:
json_object = json.loads(myjson) json_object = json.loads(myjson)
except ValueError as e: except ValueError as e:
return False return False
return True return True
class GenericWebsocket(object):
def __init__(self, host, logLevel='INFO', loop=None): class GenericWebsocket:
self.host = host """
self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) Websocket object used to contain the base functionality of a websocket.
self.loop = loop or asyncio.get_event_loop() Inlcudes an event emitter and a standard websocket client.
self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=self.loop) """
self.ws = None
def run(self): def __init__(self, host, logLevel='INFO', loop=None):
self.loop.run_until_complete(self._main(self.host)) self.host = host
self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel)
self.loop = loop or asyncio.get_event_loop()
self.events = EventEmitter(
scheduler=asyncio.ensure_future, loop=self.loop)
self.ws = None
def get_task_executable(self): def run(self):
return self._main(self.host) """
Run the websocket connection indefinitely
"""
self.loop.run_until_complete(self._main(self.host))
async def _main(self, host): def get_task_executable(self):
async with websockets.connect(host) as websocket: """
self.ws = websocket Get the run indefinitely asyncio task
self.logger.info("Wesocket connectedt to {}".format(self.host)) """
while True: return self._main(self.host)
await asyncio.sleep(0)
message = await websocket.recv()
await self.on_message(message)
def remove_all_listeners(self, event): async def _main(self, host):
self.events.remove_all_listeners(event) async with websockets.connect(host) as websocket:
self.ws = websocket
self.logger.info("Wesocket connectedt to {}".format(self.host))
while True:
await asyncio.sleep(0)
message = await websocket.recv()
await self.on_message(message)
def on(self, event, func=None): def remove_all_listeners(self, event):
if not func: """
return self.events.on(event) Remove all listeners from event emitter
self.events.on(event, func) """
self.events.remove_all_listeners(event)
def once(self, event, func=None): def on(self, event, func=None):
if not func: """
return self.events.once(event) Add a new event to the event emitter
self.events.once(event, func) """
if not func:
return self.events.on(event)
self.events.on(event, func)
def _emit(self, event, *args, **kwargs): def once(self, event, func=None):
self.events.emit(event, *args, **kwargs) """
Add a new event to only fire once to the event
emitter
"""
if not func:
return self.events.once(event)
self.events.once(event, func)
async def on_error(self, error): def _emit(self, event, *args, **kwargs):
self.logger.error(error) self.events.emit(event, *args, **kwargs)
self.events.emit('error', error)
async def on_close(self): async def on_error(self, error):
self.logger.info("Websocket closed.") """
await self.ws.close() On websocket error print and fire event
self._emit('done') """
self.logger.error(error)
self.events.emit('error', error)
async def on_open(self): async def on_close(self):
pass """
On websocket close print and fire event
"""
self.logger.info("Websocket closed.")
await self.ws.close()
self._emit('done')
async def on_message(self, message): async def on_open(self):
pass """
On websocket open
"""
pass
async def on_message(self, message):
"""
On websocket message
"""
pass

View File

@@ -1,268 +1,264 @@
"""
Module used to house all of the functions/classes used to handle orders
"""
import time import time
import asyncio import asyncio
from ..utils.CustomLogger import CustomLogger from ..utils.CustomLogger import CustomLogger
from ..models import Order from ..models import Order
class OrderManager: class OrderManager:
def __init__(self, bfxapi, logLevel='INFO'):
self.bfxapi = bfxapi
self.pending_orders = {}
self.pending_callbacks = {}
self.closed_orders = {}
self.open_orders = {}
self.logger = CustomLogger('BfxOrderManager', logLevel=logLevel)
def get_open_orders(self):
return list(self.open_orders.values())
def get_closed_orders(self):
return list(self.closed_orders.values())
def get_pending_orders(self):
return list(self.pending_orders.values())
async def _confirm_order(self, order, isClosed=False):
""" """
Called every time an order signal has been received. This function Handles all of the functionality for opening, updating and closing order.
manages the local list of open orders. Also contains state such as all of your open orders and orders that have
closed.
""" """
if order.cId in self.pending_orders:
await self._execute_confirm_callback(order.cId, order)
if isClosed:
await self._execute_close_callback(order.cId, order)
order.set_confirmed()
# remove from pending orders list
del self.pending_orders[order.cId]
self.bfxapi._emit('order_confirmed', order)
else:
await self._execute_confirm_callback(order.id, order)
if isClosed:
await self._execute_close_callback(order.id, order)
async def confirm_order_closed(self, raw_ws_data): def __init__(self, bfxapi, logLevel='INFO'):
# order created and executed self.bfxapi = bfxapi
# [0,"oc",[1151349678,null,1542203391995,"tBTCUSD",1542203389940,1542203389966,0,0.1, self.pending_orders = {}
# "EXCHANGE MARKET",null,null,null,0,"EXECUTED @ 18922.0(0.03299997): was PARTIALLY FILLED self.pending_callbacks = {}
# @ 18909.0(0.06700003)",null,null,18909,18913.2899961,0,0,null,null,null,0,0,null,null,null, self.closed_orders = {}
# "API>BFX",null,null,null]] self.open_orders = {}
order = Order.from_raw_order(raw_ws_data[2]) self.logger = CustomLogger('BfxOrderManager', logLevel=logLevel)
order.set_open_state(False)
if order.id in self.open_orders:
del self.open_orders[order.id]
await self._confirm_order(order, isClosed=True)
self.logger.info("Order closed: {} {}".format(order.symbol, order.status))
self.bfxapi._emit('order_closed', order)
async def build_from_order_snapshot(self, raw_ws_data): def get_open_orders(self):
print (raw_ws_data) return list(self.open_orders.values())
#[0, 'os', [[1151363978, None, 1544460962979, 'tBTCUSD', 1544460959604, 1544460959626,
# -0.12620639, -0.12620639, 'EXCHANGE LIMIT', None, None,None, 0, 'ACTIVE', None, None, 18793,
# 0, 0, 0, None, None, None, 0, 0, None, None, None, 'API>BFX', None, None, None]]]
'''
Rebuild the user orderbook based on an incoming snapshot
'''
osData = raw_ws_data[2]
self.open_orders = {}
for raw_order in osData:
order = Order.from_raw_order(raw_order)
order.set_open_state(True)
self.open_orders[order.id] = order
self.bfxapi._emit('order_snapshot', self.get_open_orders())
async def confirm_order_update(self, raw_ws_data): def get_closed_orders(self):
# order created but partially filled return list(self.closed_orders.values())
# [0, 'ou', [1151351581, None, 1542629457873, 'tBTCUSD', 1542629458071,
# 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.from_raw_order(raw_ws_data[2])
order.set_open_state(True)
self.open_orders[order.id] = order
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): def get_pending_orders(self):
# order created but not executed / created but partially filled return list(self.pending_orders.values())
# [0, 'on', [1151351563, None, 1542624024383, 'tBTCUSD', 1542624024596,
# 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.from_raw_order(raw_ws_data[2])
order.set_open_state(True)
self.open_orders[order.id] = order
await self._confirm_order(order)
self.logger.info("Order new: {}".format(order))
self.bfxapi._emit('order_new', order)
def _gen_unqiue_cid(self): async def _confirm_order(self, order, isClosed=False):
return int(round(time.time() * 1000)) """
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:
await self._execute_confirm_callback(order.cId, order)
if isClosed:
await self._execute_close_callback(order.cId, order)
order.set_confirmed()
# remove from pending orders list
del self.pending_orders[order.cId]
self.bfxapi._emit('order_confirmed', order)
else:
await self._execute_confirm_callback(order.id, order)
if isClosed:
await self._execute_close_callback(order.id, order)
async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, async def confirm_order_closed(self, raw_ws_data):
hidden=False, price_trailing=None, price_aux_limit=None, oco_stop_price=None, order = Order.from_raw_order(raw_ws_data[2])
close=False, reduce_only=False, post_only=False, oco=False, time_in_force=None, order.set_open_state(False)
onConfirm=None, onClose=None, *args, **kwargs): if order.id in self.open_orders:
""" del self.open_orders[order.id]
Submit a new order await self._confirm_order(order, isClosed=True)
self.logger.info("Order closed: {} {}".format(
order.symbol, order.status))
self.bfxapi._emit('order_closed', order)
@param symbol: the name of the symbol i.e 'tBTCUSD async def build_from_order_snapshot(self, raw_ws_data):
@param price: the price you want to buy/sell at (must be positive) '''
@param amount: order size: how much you want to buy/sell, Rebuild the user orderbook based on an incoming snapshot
a negative amount indicates a sell order and positive a buy order '''
@param market_type Order.Type: please see Order.Type enum osData = raw_ws_data[2]
amount decimal string Positive for buy, Negative for sell self.open_orders = {}
@param hidden: if True, order should be hidden from orderbooks for raw_order in osData:
@param price_trailing: decimal trailing price order = Order.from_raw_order(raw_order)
@param price_aux_limit: decimal auxiliary Limit price (only for STOP LIMIT) order.set_open_state(True)
@param oco_stop_price: set the oco stop price (requires oco = True) self.open_orders[order.id] = order
@param close: if True, close position if position present self.bfxapi._emit('order_snapshot', self.get_open_orders())
@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 async def confirm_order_update(self, raw_ws_data):
@param onConfirm: function called when the bitfinex websocket receives signal that the order order = Order.from_raw_order(raw_ws_data[2])
was confirmed order.set_open_state(True)
@param onClose: function called when the bitfinex websocket receives signal that the order self.open_orders[order.id] = order
was closed due to being filled or cancelled await self._confirm_order(order)
""" self.logger.info("Order update: {}".format(order))
cId = self._gen_unqiue_cid() self.bfxapi._emit('order_update', order)
# create base payload with required data
payload = {
"cid": cId,
"type": str(market_type),
"symbol": symbol,
"amount": str(amount),
"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._create_callback(cId, onConfirm=onConfirm, onClose=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, price=None, amount=None, delta=None, price_aux_limit=None, async def confirm_order_new(self, raw_ws_data):
price_trailing=None, hidden=False, close=False, reduce_only=False, post_only=False, order = Order.from_raw_order(raw_ws_data[2])
time_in_force=None, onConfirm=None, onClose=None): order.set_open_state(True)
""" self.open_orders[order.id] = order
Update an existing order await self._confirm_order(order)
self.logger.info("Order new: {}".format(order))
self.bfxapi._emit('order_new', order)
@param orderId: the id of the order that you want to update def _gen_unqiue_cid(self):
@param price: the price you want to buy/sell at (must be positive) return int(round(time.time() * 1000))
@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._create_callback(order.cId, onConfirm=onConfirm, onClose=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 cancel_order(self, orderId, onConfirm=None, onClose=None): async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
""" hidden=False, price_trailing=None, price_aux_limit=None,
Cancel an existing open order 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 orderId: the id of the order that you want to update @param symbol: the name of the symbol i.e 'tBTCUSD
@param onConfirm: function called when the bitfinex websocket receives signal that the order @param price: the price you want to buy/sell at (must be positive)
was confirmed @param amount: order size: how much you want to buy/sell,
@param onClose: function called when the bitfinex websocket receives signal that the order a negative amount indicates a sell order and positive a buy order
was closed due to being filled or cancelled @param market_type Order.Type: please see Order.Type enum
""" amount decimal string Positive for buy, Negative for sell
# order = self.open_orders[orderId] @param hidden: if True, order should be hidden from orderbooks
self._create_callback(orderId, onConfirm=onConfirm, onClose=onClose) @param price_trailing: decimal trailing price
await self.bfxapi._send_auth_command('oc', { 'id': orderId }) @param price_aux_limit: decimal auxiliary Limit price (only for STOP LIMIT)
self.logger.info("Order cancel order_id={} dispatched".format(orderId)) @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
async def cancel_all_orders(self): @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
Cancel all existing open orders 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()
# create base payload with required data
payload = {
"cid": cId,
"type": str(market_type),
"symbol": symbol,
"amount": str(amount),
"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._create_callback(cId, onConfirm=onConfirm, onClose=onClose)
await self.bfxapi._send_auth_command('on', payload)
self.logger.info("Order cid={} ({} {} @ {}) dispatched".format(
cId, symbol, amount, price))
This function closes orders that have been tracked locally by the 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,
ids = [self.open_orders[x].id for x in self.open_orders] post_only=False, time_in_force=None, onConfirm=None, onClose=None):
await self.cancel_order_multi(ids) """
Update an existing order
async def cancel_order_multi(self, orderIds): @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)
Cancel existing open orders as a batch @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._create_callback(order.cId, onConfirm=onConfirm, onClose=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))
@param orderIds: an array of order ids async def cancel_order(self, orderId, onConfirm=None, onClose=None):
""" """
task_batch = [] Cancel an existing open order
for oid in orderIds:
task_batch += [
asyncio.ensure_future(self.open_orders[oid].close())
]
await asyncio.wait(*[ task_batch ])
def _create_callback(self, order_identifier, onConfirm=None, onClose=None): @param orderId: the id of the order that you want to update
if order_identifier in self.pending_callbacks: @param onConfirm: function called when the bitfinex websocket receives signal that the
self.pending_callbacks[order_identifier] += [(onClose, onConfirm)] order
else: was confirmed
self.pending_callbacks[order_identifier] = [(onClose, onConfirm)] @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._create_callback(orderId, onConfirm=onConfirm, onClose=onClose)
await self.bfxapi._send_auth_command('oc', {'id': orderId})
self.logger.info("Order cancel order_id={} dispatched".format(orderId))
async def _execute_close_callback(self, order_identifier, *args, **kwargs): async def cancel_all_orders(self):
if order_identifier in self.pending_callbacks: """
for c in self.pending_callbacks[order_identifier]: Cancel all existing open orders
if c[0]:
await c[0](*args, **kwargs)
del self.pending_callbacks[order_identifier]
async def _execute_confirm_callback(self, order_identifier, *args, **kwargs): This function closes orders that have been tracked locally by the OrderManager.
if order_identifier in self.pending_callbacks: """
for c in self.pending_callbacks[order_identifier]: ids = [self.open_orders[x].id for x in self.open_orders]
if c[1]: await self.cancel_order_multi(ids)
await c[1](*args, **kwargs)
def _calculate_flags(self, hidden, close, reduce_only, post_only, oco): async def cancel_order_multi(self, orderIds):
flags = 0 """
flags = flags + Order.Flags.HIDDEN if hidden else flags Cancel existing open orders as a batch
flags = flags + Order.Flags.CLOSE if close else flags
flags = flags + Order.Flags.REDUUCE_ONLY if reduce_only else flags @param orderIds: an array of order ids
flags = flags + Order.Flags.POST_ONLY if post_only else flags """
flags = flags + Order.Flags.OCO if oco else flags task_batch = []
return flags for oid in orderIds:
task_batch += [
asyncio.ensure_future(self.open_orders[oid].close())
]
await asyncio.wait(*[task_batch])
def _create_callback(self, order_identifier, onConfirm=None, onClose=None):
if order_identifier in self.pending_callbacks:
self.pending_callbacks[order_identifier] += [(onClose, onConfirm)]
else:
self.pending_callbacks[order_identifier] = [(onClose, onConfirm)]
async def _execute_close_callback(self, order_identifier, *args, **kwargs):
if order_identifier in self.pending_callbacks:
for c in self.pending_callbacks[order_identifier]:
if c[0]:
await c[0](*args, **kwargs)
del self.pending_callbacks[order_identifier]
async def _execute_confirm_callback(self, order_identifier, *args, **kwargs):
if order_identifier in self.pending_callbacks:
for c in self.pending_callbacks[order_identifier]:
if c[1]:
await c[1](*args, **kwargs)
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

View File

@@ -1,3 +1,8 @@
"""
Module used to house all of the functions/classes used to handle
subscriptions
"""
import json import json
import asyncio import asyncio
import time import time
@@ -5,126 +10,126 @@ import time
from ..utils.CustomLogger import CustomLogger from ..utils.CustomLogger import CustomLogger
from ..models import Subscription from ..models import Subscription
class SubscriptionManager: class SubscriptionManager:
def __init__(self, bfxapi, logLevel='INFO'): def __init__(self, bfxapi, logLevel='INFO'):
self.pending_subscriptions = {} self.pending_subscriptions = {}
self.subscriptions_chanid = {} self.subscriptions_chanid = {}
self.subscriptions_subid = {} self.subscriptions_subid = {}
self.unsubscribe_callbacks = {} self.unsubscribe_callbacks = {}
self.bfxapi = bfxapi self.bfxapi = bfxapi
self.logger = CustomLogger('BfxSubscriptionManager', logLevel=logLevel) self.logger = CustomLogger('BfxSubscriptionManager', logLevel=logLevel)
async def subscribe(self, channel_name, symbol, timeframe=None, **kwargs): async def subscribe(self, channel_name, symbol, timeframe=None, **kwargs):
""" """
Subscribe to a new channel Subscribe to a new channel
@param channel_name: the name of the channel i.e 'books', 'candles' @param channel_name: the name of the channel i.e 'books', 'candles'
@param symbol: the trading symbol i.e 'tBTCUSD' @param symbol: the trading symbol i.e 'tBTCUSD'
@param timeframe: sepecifies the data timeframe between each candle (only required @param timeframe: sepecifies the data timeframe between each candle (only required
for the candles channel) for the candles channel)
""" """
# create a new subscription # create a new subscription
subscription = Subscription(self.bfxapi.ws, channel_name, symbol, timeframe, **kwargs) subscription = Subscription(
self.logger.info("Subscribing to channel {}".format(channel_name)) self.bfxapi.ws, channel_name, symbol, timeframe, **kwargs)
key = "{}_{}".format(channel_name, subscription.key or symbol) self.logger.info("Subscribing to channel {}".format(channel_name))
self.pending_subscriptions[key] = subscription key = "{}_{}".format(channel_name, subscription.key or symbol)
await subscription.subscribe() self.pending_subscriptions[key] = subscription
await subscription.subscribe()
async def confirm_subscription(self, raw_ws_data): async def confirm_subscription(self, raw_ws_data):
# {"event":"subscribed","channel":"trades","chanId":1,"symbol":"tBTCUSD","pair":"BTCUSD"} symbol = raw_ws_data.get("symbol", None)
# {"event":"subscribed","channel":"candles","chanId":351,"key":"trade:1m:tBTCUSD"} channel = raw_ws_data.get("channel")
# {"event":"subscribed","channel":"book","chanId":4,"symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25","pair":"BTCUSD"} chan_id = raw_ws_data.get("chanId")
symbol = raw_ws_data.get("symbol", None) key = raw_ws_data.get("key", None)
channel = raw_ws_data.get("channel") get_key = "{}_{}".format(channel, key or symbol)
chanId = raw_ws_data.get("chanId")
key = raw_ws_data.get("key", None)
get_key = "{}_{}".format(channel, key or symbol)
if chanId in self.subscriptions_chanid: if chan_id in self.subscriptions_chanid:
# subscription has already existed in the past # subscription has already existed in the past
p_sub = self.subscriptions_chanid[chanId] p_sub = self.subscriptions_chanid[chan_id]
else: else:
# has just been created and is pending # has just been created and is pending
p_sub = self.pending_subscriptions[get_key] p_sub = self.pending_subscriptions[get_key]
# remove from pending list # remove from pending list
del self.pending_subscriptions[get_key] del self.pending_subscriptions[get_key]
p_sub.confirm_subscription(chanId) p_sub.confirm_subscription(chan_id)
# add to confirmed list # add to confirmed list
self.subscriptions_chanid[chanId] = p_sub self.subscriptions_chanid[chan_id] = p_sub
self.subscriptions_subid[p_sub.sub_id] = p_sub self.subscriptions_subid[p_sub.sub_id] = p_sub
self.bfxapi._emit('subscribed', p_sub) self.bfxapi._emit('subscribed', p_sub)
async def confirm_unsubscribe(self, raw_ws_data): async def confirm_unsubscribe(self, raw_ws_data):
chanId = raw_ws_data.get("chanId") chan_id = raw_ws_data.get("chanId")
sub = self.subscriptions_chanid[chanId] sub = self.subscriptions_chanid[chan_id]
sub.confirm_unsubscribe() sub.confirm_unsubscribe()
self.bfxapi._emit('unsubscribed', sub) self.bfxapi._emit('unsubscribed', sub)
# call onComplete callback if exists # call onComplete callback if exists
if sub.sub_id in self.unsubscribe_callbacks: if sub.sub_id in self.unsubscribe_callbacks:
await self.unsubscribe_callbacks[sub.sub_id]() await self.unsubscribe_callbacks[sub.sub_id]()
del self.unsubscribe_callbacks[sub.sub_id] del self.unsubscribe_callbacks[sub.sub_id]
def get(self, chanId): def get(self, chan_id):
return self.subscriptions_chanid[chanId] return self.subscriptions_chanid[chan_id]
async def unsubscribe(self, chanId, onComplete=None): async def unsubscribe(self, chan_id, onComplete=None):
""" """
Unsubscribe from the channel with the given chanId Unsubscribe from the channel with the given chanId
@param onComplete: function called when the bitfinex websocket resoponds with @param onComplete: function called when the bitfinex websocket resoponds with
a signal that confirms the subscription has been unsubscribed to a signal that confirms the subscription has been unsubscribed to
""" """
sub = self.subscriptions_chanid[chanId] sub = self.subscriptions_chanid[chan_id]
if onComplete: if onComplete:
self.unsubscribe_callbacks[sub.sub_id] = onComplete self.unsubscribe_callbacks[sub.sub_id] = onComplete
if sub.is_subscribed(): if sub.is_subscribed():
await self.subscriptions_chanid[chanId].unsubscribe() await self.subscriptions_chanid[chan_id].unsubscribe()
async def resubscribe(self, chanId): async def resubscribe(self, chan_id):
""" """
Unsubscribes and then subscribes to the channel with the given Id 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. This function is mostly used to force the channel to produce a fresh snapshot.
""" """
sub = self.subscriptions_chanid[chanId] sub = self.subscriptions_chanid[chan_d]
async def re_sub():
await sub.subscribe()
if sub.is_subscribed():
# unsubscribe first and call callback to subscribe
await self.unsubscribe(chanId, re_sub)
else:
# already unsibscribed, so just subscribe
await sub.subscribe()
def is_subscribed(self, chanId): async def re_sub():
""" await sub.subscribe()
Returns True if the channel with the given chanId is currenly subscribed to if sub.is_subscribed():
""" # unsubscribe first and call callback to subscribe
if chanId not in self.subscriptions_chanid: await self.unsubscribe(chan_id, re_sub)
return False else:
return self.subscriptions_chanid[chanId].is_subscribed() # already unsibscribed, so just subscribe
await sub.subscribe()
async def unsubscribe_all(self): def is_subscribed(self, chan_id):
""" """
Unsubscribe from all channels. Returns True if the channel with the given chanId is currenly subscribed to
""" """
task_batch = [] if chan_id not in self.subscriptions_chanid:
for chanId in self.subscriptions_chanid: return False
sub = self.get(chanId) return self.subscriptions_chanid[chan_id].is_subscribed()
if sub.is_subscribed():
task_batch += [
asyncio.ensure_future(self.unsubscribe(chanId))
]
await asyncio.wait(*[ task_batch ])
async def resubscribe_all(self): async def unsubscribe_all(self):
""" """
Unsubscribe and then subscribe to all channels Unsubscribe from all channels.
""" """
task_batch = [] task_batch = []
for chanId in self.subscriptions_chanid: for chan_id in self.subscriptions_chanid:
task_batch += [ sub = self.get(chan_id)
asyncio.ensure_future(self.resubscribe(chanId)) if sub.is_subscribed():
] task_batch += [
await asyncio.wait(*[ task_batch ]) asyncio.ensure_future(self.unsubscribe(chan_id))
]
await asyncio.wait(*[task_batch])
async def resubscribe_all(self):
"""
Unsubscribe and then subscribe to all channels
"""
task_batch = []
for chan_id in self.subscriptions_chanid:
task_batch += [
asyncio.ensure_future(self.resubscribe(chan_id))
]
await asyncio.wait(*[task_batch])

View File

@@ -1,27 +1,31 @@
"""
Module used to handle wallet updates and data types
"""
from ..models import Wallet from ..models import Wallet
class WalletManager: class WalletManager:
"""
This class is used to interact with all of the different wallets
"""
def __init__(self): def __init__(self):
self.wallets = {} self.wallets = {}
def _update_from_snapshot(self, raw_ws_data): def _update_from_snapshot(self, raw_ws_data):
# [0, 'ws', [['exchange', 'BTC', 41.25809589, 0, None], ['exchange', 'USD', 62761.86070104, 0, None]]] wData = raw_ws_data[2]
wData = raw_ws_data[2] self.wallets = {}
self.wallets = {} for wallet in wData:
for wallet in wData: new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3])
new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3]) self.wallets[new_wallet.key] = new_wallet
self.wallets[new_wallet.key] = new_wallet return self.get_wallets()
return self.get_wallets()
def _update_from_event(self, raw_ws_data): def _update_from_event(self, raw_ws_data):
# [0,"wu",["exchange","USD",62761.86070104,0,61618.66070104]] wallet = raw_ws_data[2]
wallet = raw_ws_data[2] new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3])
new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3]) self.wallets[new_wallet.key] = new_wallet
self.wallets[new_wallet.key] = new_wallet return new_wallet
return new_wallet
def get_wallets(self):
return list(self.wallets.values())
def get_wallets(self):
return list(self.wallets.values())

10
pylint.rc Normal file
View File

@@ -0,0 +1,10 @@
[MESSAGES CONTROL]
disable=too-few-public-methods,
import-error,
too-many-arguments,
duplicate-code,
too-many-locals,
no-init,
len-as-condition,
too-many-instance-attributes