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

@@ -9,4 +9,4 @@ python:
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
"""
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.loop = loop or asyncio.get_event_loop()
self.ws = BfxWebsocket(API_KEY=API_KEY, API_SECRET=API_SECRET, host=ws_host, 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) 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, self.rest = BfxRest(API_KEY=API_KEY, API_SECRET=API_SECRET, host=rest_host,
loop=self.loop, logLevel=logLevel, *args, **kwargs) 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,7 +1,15 @@
"""
Module used to describe all of the different data types
"""
import time import time
import datetime import datetime
class OrderType: class OrderType:
"""
Enum used to describe all of the different order types available for use
"""
MARKET = 'MARKET' MARKET = 'MARKET'
LIMIT = 'LIMIT' LIMIT = 'LIMIT'
STOP = 'STOP' STOP = 'STOP'
@@ -15,14 +23,25 @@ class OrderType:
EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP' EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP'
EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK' 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:
"""
Enum used to describe the different directions of an order
"""
BUY = 'buy' BUY = 'buy'
SELL = 'sell' SELL = 'sell'
class OrderClosedModel: class OrderClosedModel:
"""
Enum used ad an index match to locate the different values in a
raw order array
"""
ID = 0 ID = 0
GID = 1 GID = 1
CID = 2 CID = 2
@@ -42,27 +61,38 @@ class OrderClosedModel:
NOTIFY = 23 NOTIFY = 23
PLACE_ID = 25 PLACE_ID = 25
class OrderFlags: class OrderFlags:
"""
Enum used to explain the different values that can be passed in
as flags
"""
HIDDEN = 64 HIDDEN = 64
CLOSE = 12 CLOSE = 12
REDUCE_ONLY = 1024 REDUCE_ONLY = 1024
POST_ONLY = 4096 POST_ONLY = 4096
OCO = 16384 OCO = 16384
def now_in_mills(): def now_in_mills():
"""
Gets the current time in milliseconds
"""
return int(round(time.time() * 1000)) 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,
EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK.
TYPE_PREV string Previous order type TYPE_PREV string Previous order type
FLAGS int Upcoming Params Object (stay tuned) FLAGS int Upcoming Params Object (stay tuned)
ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED ORDER_STATUS string Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
@@ -71,34 +101,37 @@ class Order:
PRICE_TRAILING float The trailing price PRICE_TRAILING float The trailing price
PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT) PRICE_AUX_LIMIT float Auxiliary Limit price (for STOP LIMIT)
HIDDEN int 1 if Hidden, 0 if not hidden 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 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.gid = gid
self.cid = cid
self.symbol = symbol self.symbol = symbol
self.mtsCreate = mtsCreate self.mts_create = mts_create
self.mtsUpdate = mtsUpdate self.mts_update = mts_update
# self.amount = amount # self.amount = amount
self.amount = amount self.amount = amount
self.amountOrig = amountOrig self.amount_orig = amount_orig
self.type = oType self.type = o_type
self.typePrev = typePrev self.type_prev = typePrev
self.flags = flags self.flags = flags
self.status = status self.status = status
self.price = price self.price = price
self.priceAvg = priceAvg self.price_avg = price_avg
self.priceTrailing = priceTrailing self.price_trailing = price_trailing
self.priceAuxLimit = priceAuxLimit self.price_aux_limit = price_aux_limit
self.notfiy = notfiy self.notfiy = notfiy
self.placeId = placeId self.place_id = place_id
self.tag = "" self.tag = ""
self.fee = 0 self.fee = 0
@@ -106,56 +139,83 @@ class Order:
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):
"""
Parse a raw order object into an Order oject
@return Order
"""
oid = raw_order[OrderClosedModel.ID] oid = raw_order[OrderClosedModel.ID]
gId = raw_order[OrderClosedModel.GID] gid = raw_order[OrderClosedModel.GID]
cId = raw_order[OrderClosedModel.CID] cid = raw_order[OrderClosedModel.CID]
symbol = raw_order[OrderClosedModel.SYMBOL] symbol = raw_order[OrderClosedModel.SYMBOL]
mtsCreate = raw_order[OrderClosedModel.MTS_CREATE] mts_create = raw_order[OrderClosedModel.MTS_CREATE]
mtsUpdate = raw_order[OrderClosedModel.MTS_UPDATE] mts_update = raw_order[OrderClosedModel.MTS_UPDATE]
amount = raw_order[OrderClosedModel.AMOUNT] amount = raw_order[OrderClosedModel.AMOUNT]
amountOrig = raw_order[OrderClosedModel.AMOUNT_ORIG] amount_orig = raw_order[OrderClosedModel.AMOUNT_ORIG]
oType = raw_order[OrderClosedModel.TYPE] o_type = raw_order[OrderClosedModel.TYPE]
typePrev = raw_order[OrderClosedModel.TYPE_PREV] type_prev = raw_order[OrderClosedModel.TYPE_PREV]
flags = raw_order[OrderClosedModel.FLAGS] flags = raw_order[OrderClosedModel.FLAGS]
status = raw_order[OrderClosedModel.STATUS] status = raw_order[OrderClosedModel.STATUS]
price = raw_order[OrderClosedModel.PRICE] price = raw_order[OrderClosedModel.PRICE]
priceAvg = raw_order[OrderClosedModel.PRIVE_AVG] price_avg = raw_order[OrderClosedModel.PRIVE_AVG]
priceTrailing = raw_order[OrderClosedModel.PRICE_TRAILING] price_trailing = raw_order[OrderClosedModel.PRICE_TRAILING]
priceAuxLimit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT] price_aux_limit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT]
notfiy = raw_order[OrderClosedModel.NOTIFY] notfiy = raw_order[OrderClosedModel.NOTIFY]
placeId = raw_order[OrderClosedModel.PLACE_ID] place_id = raw_order[OrderClosedModel.PLACE_ID]
return Order(oid, gId, cId, symbol, mtsCreate, mtsUpdate, amount, amountOrig, oType, return Order(oid, gid, cid, symbol, mts_create, mts_update, amount,
typePrev, flags, status, price, priceAvg, priceTrailing, priceAuxLimit, notfiy, placeId) amount_orig, o_type, type_prev, flags, status, price, price_avg,
price_trailing, price_aux_limit, notfiy, place_id)
def set_confirmed(self): def set_confirmed(self):
"""
Set the state of the order to be confirmed
"""
self.is_pending_bool = False self.is_pending_bool = False
self.is_confirmed_bool = True self.is_confirmed_bool = True
def set_open_state(self, isOpen): def set_open_state(self, is_open):
self.is_open_bool = isOpen """
Set the is_open state of the order
"""
self.is_open_bool = is_open
def isOpen(self): def is_open(self):
"""
Check if the order is still open
@return bool: Ture if order open else False
"""
return self.is_open_bool return self.is_open_bool
def isPending(self): 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 return self.is_pending_bool
def isConfirmed(self): 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 return self.is_confirmed_bool
def __str__(self): def __str__(self):
''' Allow us to print the Order object in a pretty format ''' ''' Allow us to print the Order object in a pretty format '''
return "Order <'{}' mtsCreate={} status='{}' id={}>".format(self.symbol, self.mtsCreate, text = "Order <'{}' mts_create={} status='{}' id={}>"
self.status, self.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,9 +1,14 @@
"""
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,
negative values means a short position.
BASE_PRICE float The price at which you entered your 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 float The amount of funding being used for this position.
MARGIN_FUNDING_TYPE int 0 for daily, 1 for term. MARGIN_FUNDING_TYPE int 0 for daily, 1 for term.
@@ -13,24 +18,30 @@ class Position:
LEVERAGE float Beta value 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):
"""
Generate a Position object from a raw position array
@return Position
"""
return Position(*raw_position) return Position(*raw_position)
def __str__(self): def __str__(self):
''' Allow us to print the Trade object in a pretty format ''' ''' Allow us to print the Trade object in a pretty format '''
return "Position '{}' {} x {} <Sstatus='{}' p&l={}>".format( text = "Position '{}' {} x {} <status='{}' pl={}>"
self.symbol, self.base_price, self.amount, self.status, self.profit_loss) 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
self.chan_id = None
if timeframe: if timeframe:
self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol) self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol)
self.sub_id = int(round(time.time() * 1000)) self.sub_id = int(round(time.time() * 1000))
self.send_payload = self._generate_payload(**kwargs) 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):
"""
Send an unsubscription request to the bitfinex socket
"""
if not self.is_subscribed(): if not self.is_subscribed():
raise Exception("Subscription is not subscribed to websocket") raise Exception("Subscription is not subscribed to websocket")
payload = { 'event': 'unsubscribe', 'chanId': self.chanId } payload = {'event': 'unsubscribe', 'chan_id': self.chan_id}
await self.ws.send(json.dumps(payload)) 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):
"""
Update the subscription to unsubscribed state
"""
self.is_subscribed_bool = False self.is_subscribed_bool = False
def is_subscribed(self): def is_subscribed(self):
"""
Check if the subscription is currently subscribed
@return bool: True if subscribed else False
"""
return self.is_subscribed_bool return self.is_subscribed_bool
def _generate_payload(self, **kwargs): def _generate_payload(self, **kwargs):
payload = { 'event': 'subscribe', 'channel': self.channel_name, 'symbol': self.symbol } payload = {'event': 'subscribe',
'channel': self.channel_name, 'symbol': self.symbol}
if self.timeframe: if self.timeframe:
payload['key'] = self.key payload['key'] = self.key
payload.update(**kwargs) payload.update(**kwargs)
return payload return payload
def get_send_payload(self): def _get_send_payload(self):
return self.send_payload return self.send_payload

View File

@@ -1,9 +1,14 @@
"""
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
@@ -18,9 +23,10 @@ class Trade:
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.id = tid
self.pair = pair self.pair = pair
self.mts_create = mts_create self.mts_create = mts_create
self.date = datetime.datetime.fromtimestamp(mts_create/1000.0) self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
@@ -36,7 +42,11 @@ class Trade:
@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'] """
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) return Trade(*raw_trade)
def __str__(self): def __str__(self):

View File

@@ -1,5 +1,13 @@
"""
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
@@ -9,9 +17,15 @@ class Wallet:
self.key = "{}_{}".format(wType, currency) self.key = "{}_{}".format(wType, currency)
def set_balance(self, data): def set_balance(self, data):
"""
Set the balance of the wallet
"""
self.balance = data self.balance = data
def set_unsettled_interest(self, data): def set_unsettled_interest(self, data):
"""
Set the unsettled interest of the wallet
"""
self.unsettled_interest = data self.unsettled_interest = data
def __str__(self): def __str__(self):

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,7 +12,14 @@ 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:
"""
BFX rest interface contains functions which are used to interact with both the public
and private Bitfinex http api's.
To use the private api you have to set the API_KEY and API_SECRET variables to your
api key.
"""
def __init__(self, API_KEY, API_SECRET, host='https://api.bitfinex.com/v2', loop=None, def __init__(self, API_KEY, API_SECRET, host='https://api.bitfinex.com/v2', loop=None,
logLevel='INFO', *args, **kwargs): logLevel='INFO', *args, **kwargs):
@@ -19,6 +30,11 @@ class BfxRest:
self.logger = CustomLogger('BfxRest', logLevel=logLevel) self.logger = CustomLogger('BfxRest', logLevel=logLevel)
async def fetch(self, endpoint, params=""): async def fetch(self, endpoint, params=""):
"""
Fetch a GET request from the bitfinex host
@return reponse
"""
url = '{}/{}{}'.format(self.host, endpoint, params) url = '{}/{}{}'.format(self.host, endpoint, params)
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(url) as resp: async with session.get(url) as resp:
@@ -29,6 +45,11 @@ class BfxRest:
return await resp.json() return await resp.json()
async def post(self, endpoint, data={}, params=""): async def post(self, endpoint, data={}, params=""):
"""
Request a POST to the bitfinex host
@return response
"""
url = '{}/{}'.format(self.host, endpoint) url = '{}/{}'.format(self.host, endpoint)
sData = json.dumps(data) sData = json.dumps(data)
headers = generate_auth_headers( headers = generate_auth_headers(
@@ -47,6 +68,9 @@ class BfxRest:
################################################## ##################################################
async def get_seed_candles(self, symbol): async def get_seed_candles(self, symbol):
"""
Used by the honey framework, this function gets the last 4k candles.
"""
endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol) endpoint = 'candles/trade:1m:{}/hist?limit=5000&_bfx=1'.format(symbol)
time_difference = (1000 * 60) * 5000 time_difference = (1000 * 60) * 5000
# get now to the nearest min # get now to the nearest min
@@ -77,12 +101,14 @@ class BfxRest:
@param start int: millisecond start time @param start int: millisecond start time
@param end int: millisecond end time @param end int: millisecond end time
@param limit int: max number of items in response @param limit int: max number of items in response
@param tf int: timeframe inbetween candles i.e 1m (min), ..., 1D (day), ... 1M (month) @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 @param sort int: if = 1 it sorts results returned with old > new
@return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ] @return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ]
""" """
endpoint = "candles/trade:{}:{}/{}".format(tf, symbol, section) endpoint = "candles/trade:{}:{}/{}".format(tf, symbol, section)
params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) params = "?start={}&end={}&limit={}&sort={}".format(
start, end, limit, sort)
candles = await self.fetch(endpoint, params=params) candles = await self.fetch(endpoint, params=params)
return candles return candles
@@ -97,7 +123,8 @@ class BfxRest:
@return Array [ ID, MTS, AMOUNT, RATE, PERIOD? ] @return Array [ ID, MTS, AMOUNT, RATE, PERIOD? ]
""" """
endpoint = "trades/{}/hist".format(symbol) endpoint = "trades/{}/hist".format(symbol)
params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) params = "?start={}&end={}&limit={}&sort={}".format(
start, end, limit, sort)
trades = await self.fetch(endpoint, params=params) trades = await self.fetch(endpoint, params=params)
return trades return trades
@@ -121,8 +148,8 @@ class BfxRest:
as well as the last trade price. as well as the last trade price.
@parms symbols symbol string: pair symbol i.e tBTCUSD @parms symbols symbol string: pair symbol i.e tBTCUSD
@return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,
LAST_PRICE, VOLUME, HIGH, LOW ] DAILY_CHANGE_PERC, LAST_PRICE, VOLUME, HIGH, LOW ]
""" """
endpoint = "ticker/{}".format(symbol) endpoint = "ticker/{}".format(symbol)
ticker = await self.fetch(endpoint) ticker = await self.fetch(endpoint)
@@ -178,7 +205,8 @@ class BfxRest:
@return Array <models.Order> @return Array <models.Order>
""" """
endpoint = "auth/r/orders/{}/hist".format(symbol) endpoint = "auth/r/orders/{}/hist".format(symbol)
params = "?start={}&end={}&limit={}&sort={}".format(start, end, limit, sort) params = "?start={}&end={}&limit={}&sort={}".format(
start, end, limit, sort)
raw_orders = await self.post(endpoint, params=params) raw_orders = await self.post(endpoint, params=params)
return [Order.from_raw_order(ro) for ro in raw_orders] return [Order.from_raw_order(ro) for ro in raw_orders]
@@ -299,8 +327,8 @@ class BfxRest:
################################################## ##################################################
async def __submit_order(self, symbol, amount, price, oType=Order.Type.LIMIT, 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, is_hidden=False, is_postonly=False, use_all_available=False,
stop_buy_price=0, stop_sell_price=0): stop_order=False, stop_buy_price=0, stop_sell_price=0):
""" """
Submit a new order Submit a new order

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,6 +26,9 @@ KEYWORD_COLORS = {
} }
def formatter_message(message, use_color = True): def formatter_message(message, use_color = True):
"""
Syntax highlight certain keywords
"""
if use_color: if use_color:
message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ) message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
else: else:
@@ -29,6 +36,9 @@ def formatter_message(message, use_color = True):
return message 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,7 +14,12 @@ 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: class Flags:
"""
Enum used to index the available flags used in the authentication
websocket packet
"""
DEC_S = 9 DEC_S = 9
TIME_S = 32 TIME_S = 32
TIMESTAMP = 32768 TIMESTAMP = 32768
@@ -25,6 +34,7 @@ class Flags:
131072: 'CHECKSUM' 131072: 'CHECKSUM'
} }
def _parse_candle(cData, symbol, tf): def _parse_candle(cData, symbol, tf):
return { return {
'mts': cData[0], 'mts': cData[0],
@@ -37,6 +47,7 @@ def _parse_candle(cData, symbol, tf):
'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],
@@ -54,10 +65,11 @@ def _parse_trade(tData, symbol):
'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
""" """
@@ -85,8 +97,8 @@ class BfxWebsocket(GenericWebsocket):
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, def __init__(self, API_KEY=None, API_SECRET=None, host='wss://api.bitfinex.com/ws/2',
dead_man_switch=False, logLevel='INFO', *args, **kwargs): manageOrderBooks=False, dead_man_switch=False, logLevel='INFO', *args, **kwargs):
self.API_KEY = API_KEY self.API_KEY = API_KEY
self.API_SECRET = API_SECRET self.API_SECRET = API_SECRET
self.manageOrderBooks = manageOrderBooks self.manageOrderBooks = manageOrderBooks
@@ -94,7 +106,8 @@ class BfxWebsocket(GenericWebsocket):
self.pendingOrders = {} self.pendingOrders = {}
self.orderBooks = {} self.orderBooks = {}
super(BfxWebsocket, self).__init__(host, logLevel=logLevel, *args, **kwargs) super(BfxWebsocket, self).__init__(
host, logLevel=logLevel, *args, **kwargs)
self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel) self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel)
self.orderManager = OrderManager(self, logLevel=logLevel) self.orderManager = OrderManager(self, logLevel=logLevel)
self.wallets = WalletManager() self.wallets = WalletManager()
@@ -133,16 +146,17 @@ class BfxWebsocket(GenericWebsocket):
if eType in self._WS_SYSTEM_HANDLERS: if eType in self._WS_SYSTEM_HANDLERS:
await self._WS_SYSTEM_HANDLERS[eType](msg) await self._WS_SYSTEM_HANDLERS[eType](msg)
else: else:
self.logger.warn("Unknown websocket event: '{}' {}".format(eType, msg)) self.logger.warn(
"Unknown websocket event: '{}' {}".format(eType, msg))
async def _ws_data_handler(self, data): async def _ws_data_handler(self, data):
dataEvent = data[1] dataEvent = data[1]
chanId = data[0] chan_id = data[0]
if type(dataEvent) is str and dataEvent in self._WS_DATA_HANDLERS: if type(dataEvent) is str and dataEvent in self._WS_DATA_HANDLERS:
return await self._WS_DATA_HANDLERS[dataEvent](data) return await self._WS_DATA_HANDLERS[dataEvent](data)
elif self.subscriptionManager.is_subscribed(chanId): elif self.subscriptionManager.is_subscribed(chan_id):
subscription = self.subscriptionManager.get(chanId) subscription = self.subscriptionManager.get(chan_id)
# candles do not have an event # candles do not have an event
if subscription.channel_name == 'candles': if subscription.channel_name == 'candles':
await self._candle_handler(data) await self._candle_handler(data)
@@ -151,12 +165,13 @@ class BfxWebsocket(GenericWebsocket):
if subscription.channel_name == 'trades': if subscription.channel_name == 'trades':
await self._trade_handler(data) await self._trade_handler(data)
else: else:
self.logger.warn("Unknown data event: '{}' {}".format(dataEvent, data)) self.logger.warn(
"Unknown data event: '{}' {}".format(dataEvent, data))
async def _system_info_handler(self, data): async def _system_info_handler(self, data):
self.logger.info(data) self.logger.info(data)
if data.get('serverId', None): if data.get('serverId', None):
## connection has been established # connection has been established
await self.on_open() await self.on_open()
async def _system_conf_handler(self, data): async def _system_conf_handler(self, data):
@@ -169,7 +184,8 @@ class BfxWebsocket(GenericWebsocket):
if status == "OK": if status == "OK":
self.logger.info("Enabled config flag {}".format(flagString)) self.logger.info("Enabled config flag {}".format(flagString))
else: else:
self.logger.error("Unable to enable config flag {}".format(flagString)) self.logger.error(
"Unable to enable config flag {}".format(flagString))
async def _system_subscribed_handler(self, data): async def _system_subscribed_handler(self, data):
await self.subscriptionManager.confirm_subscription(data) await self.subscriptionManager.confirm_subscription(data)
@@ -221,19 +237,17 @@ class BfxWebsocket(GenericWebsocket):
self.logger.info("Funding info update: {}".format(data)) self.logger.info("Funding info update: {}".format(data))
async def _notification_handler(self, 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] nInfo = data[2]
self._emit('notification', nInfo) self._emit('notification', nInfo)
notificationType = nInfo[6] notificationType = nInfo[6]
notificationText = nInfo[7] notificationText = nInfo[7]
if notificationType == 'ERROR': if notificationType == 'ERROR':
# self._emit('error', notificationText) # self._emit('error', notificationText)
self.logger.error("Notification ERROR: {}".format(notificationText)) self.logger.error(
"Notification ERROR: {}".format(notificationText))
else: else:
self.logger.info("Notification SUCCESS: {}".format(notificationText)) self.logger.info(
"Notification SUCCESS: {}".format(notificationText))
async def _balance_update_handler(self, data): async def _balance_update_handler(self, data):
self.logger.info('Balance update: {}'.format(data[2])) self.logger.info('Balance update: {}'.format(data[2]))
@@ -295,15 +309,17 @@ class BfxWebsocket(GenericWebsocket):
candlesSnapshot = data[1] candlesSnapshot = data[1]
candlesSnapshot.reverse() candlesSnapshot.reverse()
for c in candlesSnapshot: for c in candlesSnapshot:
candle = _parse_candle(c, subscription.symbol, subscription.timeframe) candle = _parse_candle(
c, subscription.symbol, subscription.timeframe)
self._emit('seed_candle', candle) self._emit('seed_candle', candle)
else: else:
candle = _parse_candle(data[1], subscription.symbol, subscription.timeframe) candle = _parse_candle(
data[1], subscription.symbol, subscription.timeframe)
self._emit('new_candle', candle) self._emit('new_candle', candle)
async def _order_book_handler(self, data): async def _order_book_handler(self, data):
obInfo = data[1] obInfo = data[1]
chanId = data[0] chan_id = data[0]
subscription = self.subscriptionManager.get(data[0]) subscription = self.subscriptionManager.get(data[0])
symbol = subscription.symbol symbol = subscription.symbol
if data[1] == "cs": if data[1] == "cs":
@@ -312,13 +328,13 @@ class BfxWebsocket(GenericWebsocket):
# force checksums to signed integers # force checksums to signed integers
isValid = (dChecksum) == (checksum) isValid = (dChecksum) == (checksum)
if isValid: if isValid:
self.logger.debug("Checksum orderbook validation for '{}' successful." msg = "Checksum orderbook validation for '{}' successful."
.format(symbol)) self.logger.debug(msg.format(symbol))
else: else:
self.logger.warn("Checksum orderbook invalid for '{}'. Resetting subscription." msg = "Checksum orderbook invalid for '{}'. Resetting subscription."
.format(symbol)) self.logger.warn(msg.format(symbol))
# re-build orderbook with snapshot # re-build orderbook with snapshot
await self.subscriptionManager.resubscribe(chanId) await self.subscriptionManager.resubscribe(chan_id)
return return
if obInfo == []: if obInfo == []:
self.orderBooks[symbol] = OrderBook() self.orderBooks[symbol] = OrderBook()
@@ -326,10 +342,11 @@ class BfxWebsocket(GenericWebsocket):
isSnapshot = type(obInfo[0]) is list isSnapshot = type(obInfo[0]) is list
if isSnapshot: if isSnapshot:
self.orderBooks[symbol] = OrderBook() self.orderBooks[symbol] = OrderBook()
self.orderBooks[symbol].updateFromSnapshot(obInfo) self.orderBooks[symbol].update_from_snapshot(obInfo)
self._emit('order_book_snapshot', { 'symbol': symbol, 'data': obInfo }) self._emit('order_book_snapshot', {
'symbol': symbol, 'data': obInfo})
else: else:
self.orderBooks[symbol].updateWith(obInfo) self.orderBooks[symbol].update_with(obInfo)
self._emit('order_book_update', {'symbol': symbol, 'data': obInfo}) self._emit('order_book_update', {'symbol': symbol, 'data': obInfo})
async def on_message(self, message): async def on_message(self, message):

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,7 +9,13 @@ 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:
@@ -14,19 +24,31 @@ def is_json(myjson):
return False return False
return True return True
class GenericWebsocket(object):
class GenericWebsocket:
"""
Websocket object used to contain the base functionality of a websocket.
Inlcudes an event emitter and a standard websocket client.
"""
def __init__(self, host, logLevel='INFO', loop=None): def __init__(self, host, logLevel='INFO', loop=None):
self.host = host self.host = host
self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel)
self.loop = loop or asyncio.get_event_loop() self.loop = loop or asyncio.get_event_loop()
self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=self.loop) self.events = EventEmitter(
scheduler=asyncio.ensure_future, loop=self.loop)
self.ws = None self.ws = None
def run(self): def run(self):
"""
Run the websocket connection indefinitely
"""
self.loop.run_until_complete(self._main(self.host)) self.loop.run_until_complete(self._main(self.host))
def get_task_executable(self): def get_task_executable(self):
"""
Get the run indefinitely asyncio task
"""
return self._main(self.host) return self._main(self.host)
async def _main(self, host): async def _main(self, host):
@@ -39,14 +61,24 @@ class GenericWebsocket(object):
await self.on_message(message) await self.on_message(message)
def remove_all_listeners(self, event): def remove_all_listeners(self, event):
"""
Remove all listeners from event emitter
"""
self.events.remove_all_listeners(event) self.events.remove_all_listeners(event)
def on(self, event, func=None): def on(self, event, func=None):
"""
Add a new event to the event emitter
"""
if not func: if not func:
return self.events.on(event) return self.events.on(event)
self.events.on(event, func) self.events.on(event, func)
def once(self, event, func=None): def once(self, event, func=None):
"""
Add a new event to only fire once to the event
emitter
"""
if not func: if not func:
return self.events.once(event) return self.events.once(event)
self.events.once(event, func) self.events.once(event, func)
@@ -55,16 +87,28 @@ class GenericWebsocket(object):
self.events.emit(event, *args, **kwargs) self.events.emit(event, *args, **kwargs)
async def on_error(self, error): async def on_error(self, error):
"""
On websocket error print and fire event
"""
self.logger.error(error) self.logger.error(error)
self.events.emit('error', error) self.events.emit('error', error)
async def on_close(self): async def on_close(self):
"""
On websocket close print and fire event
"""
self.logger.info("Websocket closed.") self.logger.info("Websocket closed.")
await self.ws.close() await self.ws.close()
self._emit('done') self._emit('done')
async def on_open(self): async def on_open(self):
"""
On websocket open
"""
pass pass
async def on_message(self, message): async def on_message(self, message):
"""
On websocket message
"""
pass pass

View File

@@ -1,10 +1,20 @@
"""
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:
"""
Handles all of the functionality for opening, updating and closing order.
Also contains state such as all of your open orders and orders that have
closed.
"""
def __init__(self, bfxapi, logLevel='INFO'): def __init__(self, bfxapi, logLevel='INFO'):
self.bfxapi = bfxapi self.bfxapi = bfxapi
@@ -42,24 +52,16 @@ class OrderManager:
await self._execute_close_callback(order.id, order) await self._execute_close_callback(order.id, order)
async def confirm_order_closed(self, raw_ws_data): async def confirm_order_closed(self, raw_ws_data):
# order created and executed
# [0,"oc",[1151349678,null,1542203391995,"tBTCUSD",1542203389940,1542203389966,0,0.1,
# "EXCHANGE MARKET",null,null,null,0,"EXECUTED @ 18922.0(0.03299997): was PARTIALLY FILLED
# @ 18909.0(0.06700003)",null,null,18909,18913.2899961,0,0,null,null,null,0,0,null,null,null,
# "API>BFX",null,null,null]]
order = Order.from_raw_order(raw_ws_data[2]) order = Order.from_raw_order(raw_ws_data[2])
order.set_open_state(False) order.set_open_state(False)
if order.id in self.open_orders: if order.id in self.open_orders:
del self.open_orders[order.id] del self.open_orders[order.id]
await self._confirm_order(order, isClosed=True) await self._confirm_order(order, isClosed=True)
self.logger.info("Order closed: {} {}".format(order.symbol, order.status)) self.logger.info("Order closed: {} {}".format(
order.symbol, order.status))
self.bfxapi._emit('order_closed', order) self.bfxapi._emit('order_closed', order)
async def build_from_order_snapshot(self, raw_ws_data): async def build_from_order_snapshot(self, raw_ws_data):
print (raw_ws_data)
#[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 Rebuild the user orderbook based on an incoming snapshot
''' '''
@@ -72,11 +74,6 @@ class OrderManager:
self.bfxapi._emit('order_snapshot', self.get_open_orders()) self.bfxapi._emit('order_snapshot', self.get_open_orders())
async def confirm_order_update(self, raw_ws_data): async def confirm_order_update(self, raw_ws_data):
# order created but partially filled
# [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 = Order.from_raw_order(raw_ws_data[2])
order.set_open_state(True) order.set_open_state(True)
self.open_orders[order.id] = order self.open_orders[order.id] = order
@@ -85,11 +82,6 @@ class OrderManager:
self.bfxapi._emit('order_update', order) self.bfxapi._emit('order_update', order)
async def confirm_order_new(self, raw_ws_data): async def confirm_order_new(self, raw_ws_data):
# order created but not executed / created but partially filled
# [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 = Order.from_raw_order(raw_ws_data[2])
order.set_open_state(True) order.set_open_state(True)
self.open_orders[order.id] = order self.open_orders[order.id] = order
@@ -101,8 +93,9 @@ class OrderManager:
return int(round(time.time() * 1000)) return int(round(time.time() * 1000))
async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT, async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
hidden=False, price_trailing=None, price_aux_limit=None, oco_stop_price=None, hidden=False, price_trailing=None, price_aux_limit=None,
close=False, reduce_only=False, post_only=False, oco=False, time_in_force=None, oco_stop_price=None, close=False, reduce_only=False,
post_only=False, oco=False, time_in_force=None,
onConfirm=None, onClose=None, *args, **kwargs): onConfirm=None, onClose=None, *args, **kwargs):
""" """
Submit a new order Submit a new order
@@ -140,7 +133,8 @@ class OrderManager:
"price": str(price), "price": str(price),
} }
# caclulate and add flags # caclulate and add flags
flags = self._calculate_flags(hidden, close, reduce_only, post_only, oco) flags = self._calculate_flags(
hidden, close, reduce_only, post_only, oco)
payload['flags'] = flags payload['flags'] = flags
# add extra parameters # add extra parameters
if (price_trailing): if (price_trailing):
@@ -159,8 +153,8 @@ class OrderManager:
cId, symbol, amount, price)) cId, symbol, amount, price))
async def update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None, async def update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None,
price_trailing=None, hidden=False, close=False, reduce_only=False, post_only=False, price_trailing=None, hidden=False, close=False, reduce_only=False,
time_in_force=None, onConfirm=None, onClose=None): post_only=False, time_in_force=None, onConfirm=None, onClose=None):
""" """
Update an existing order Update an existing order
@@ -197,7 +191,8 @@ class OrderManager:
payload['price_trailing'] = str(price_trailing) payload['price_trailing'] = str(price_trailing)
if time_in_force is not None: if time_in_force is not None:
payload['time_in_force'] = str(time_in_force) payload['time_in_force'] = str(time_in_force)
flags = self._calculate_flags(hidden, close, reduce_only, post_only, False) flags = self._calculate_flags(
hidden, close, reduce_only, post_only, False)
payload['flags'] = flags payload['flags'] = flags
await self.bfxapi._send_auth_command('ou', payload) await self.bfxapi._send_auth_command('ou', payload)
self.logger.info("Update Order order_id={} dispatched".format(orderId)) self.logger.info("Update Order order_id={} dispatched".format(orderId))
@@ -207,7 +202,8 @@ class OrderManager:
Cancel an existing open order Cancel an existing open order
@param orderId: the id of the order that you want to update @param orderId: the id of the order that you want to update
@param onConfirm: function called when the bitfinex websocket receives signal that the order @param onConfirm: function called when the bitfinex websocket receives signal that the
order
was confirmed was confirmed
@param onClose: function called when the bitfinex websocket receives signal that the order @param onClose: function called when the bitfinex websocket receives signal that the order
was closed due to being filled or cancelled was closed due to being filled or cancelled

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,6 +10,7 @@ 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'):
@@ -25,39 +31,37 @@ class SubscriptionManager:
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.bfxapi.ws, channel_name, symbol, timeframe, **kwargs)
self.logger.info("Subscribing to channel {}".format(channel_name)) self.logger.info("Subscribing to channel {}".format(channel_name))
key = "{}_{}".format(channel_name, subscription.key or symbol) key = "{}_{}".format(channel_name, subscription.key or symbol)
self.pending_subscriptions[key] = subscription self.pending_subscriptions[key] = subscription
await subscription.subscribe() 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"}
# {"event":"subscribed","channel":"candles","chanId":351,"key":"trade:1m:tBTCUSD"}
# {"event":"subscribed","channel":"book","chanId":4,"symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25","pair":"BTCUSD"}
symbol = raw_ws_data.get("symbol", None) symbol = raw_ws_data.get("symbol", None)
channel = raw_ws_data.get("channel") channel = raw_ws_data.get("channel")
chanId = raw_ws_data.get("chanId") chan_id = raw_ws_data.get("chanId")
key = raw_ws_data.get("key", None) key = raw_ws_data.get("key", None)
get_key = "{}_{}".format(channel, key or symbol) 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
@@ -65,56 +69,57 @@ class SubscriptionManager:
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(): async def re_sub():
await sub.subscribe() await sub.subscribe()
if sub.is_subscribed(): if sub.is_subscribed():
# unsubscribe first and call callback to subscribe # unsubscribe first and call callback to subscribe
await self.unsubscribe(chanId, re_sub) await self.unsubscribe(chan_id, re_sub)
else: else:
# already unsibscribed, so just subscribe # already unsibscribed, so just subscribe
await sub.subscribe() await sub.subscribe()
def is_subscribed(self, chanId): def is_subscribed(self, chan_id):
""" """
Returns True if the channel with the given chanId is currenly subscribed to Returns True if the channel with the given chanId is currenly subscribed to
""" """
if chanId not in self.subscriptions_chanid: if chan_id not in self.subscriptions_chanid:
return False return False
return self.subscriptions_chanid[chanId].is_subscribed() return self.subscriptions_chanid[chan_id].is_subscribed()
async def unsubscribe_all(self): async def unsubscribe_all(self):
""" """
Unsubscribe from all channels. Unsubscribe from all channels.
""" """
task_batch = [] task_batch = []
for chanId in self.subscriptions_chanid: for chan_id in self.subscriptions_chanid:
sub = self.get(chanId) sub = self.get(chan_id)
if sub.is_subscribed(): if sub.is_subscribed():
task_batch += [ task_batch += [
asyncio.ensure_future(self.unsubscribe(chanId)) asyncio.ensure_future(self.unsubscribe(chan_id))
] ]
await asyncio.wait(*[task_batch]) await asyncio.wait(*[task_batch])
@@ -123,8 +128,8 @@ class SubscriptionManager:
Unsubscribe and then subscribe to all channels Unsubscribe and then subscribe to all channels
""" """
task_batch = [] task_batch = []
for chanId in self.subscriptions_chanid: for chan_id in self.subscriptions_chanid:
task_batch += [ task_batch += [
asyncio.ensure_future(self.resubscribe(chanId)) asyncio.ensure_future(self.resubscribe(chan_id))
] ]
await asyncio.wait(*[task_batch]) await asyncio.wait(*[task_batch])

View File

@@ -1,13 +1,19 @@
"""
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:
@@ -16,7 +22,6 @@ class WalletManager:
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
@@ -24,4 +29,3 @@ class WalletManager:
def get_wallets(self): def get_wallets(self):
return list(self.wallets.values()) 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