Poloshing unit tests and finalizing Bittrex implementation

This commit is contained in:
fredfortier
2017-08-30 08:51:31 -04:00
parent c47e88c26f
commit d03ce37f6e
7 changed files with 146 additions and 67 deletions
+15 -46
View File
@@ -166,13 +166,8 @@ class Bitfinex(Exchange):
return order, executed_price
def update_portfolio(self):
"""
Update the portfolio cash and position balances based on the
latest ticker prices.
:return:
"""
def get_balances(self):
log.debug('retrieving wallets balances')
try:
response = self._request('balances', None)
balances = response.json()
@@ -184,36 +179,12 @@ class Bitfinex(Exchange):
error='unable to fetch balance {}'.format(balances['message'])
)
base_position = None
for position in balances:
if not base_position and position['type'] == 'exchange' \
and position['currency'] == self.base_currency:
base_position = position
std_balances = dict()
for balance in balances:
currency = balance['currency'].lower()
std_balances[currency] = float(balance['available'])
if position is None:
raise ValueError(
error='Base currency %s not found in portfolio' % self.base_currency
)
portfolio = self._portfolio
portfolio.cash = float(base_position['available'])
if portfolio.starting_cash is None:
portfolio.starting_cash = portfolio.cash
if portfolio.positions:
assets = portfolio.positions.keys()
tickers = self.tickers(assets)
portfolio.positions_value = 0.0
for ticker in tickers:
# TODO: convert if the position is not in the base currency
position = portfolio.positions[ticker['asset']]
position.last_sale_price = ticker['last_price']
position.last_sale_date = ticker['timestamp']
portfolio.positions_value += \
position.amount * position.last_sale_price
portfolio.portfolio_value = \
portfolio.positions_value + portfolio.cash
return std_balances
@property
def account(self):
@@ -397,17 +368,17 @@ class Bitfinex(Exchange):
date = pd.Timestamp.utcnow()
try:
response = self._request('order/new', req)
exchange_order = response.json()
order_status = response.json()
except Exception as e:
raise ExchangeRequestError(error=e)
if 'message' in exchange_order:
if 'message' in order_status:
raise ExchangeRequestError(
error='unable to create Bitfinex order {}'.format(
exchange_order['message'])
order_status['message'])
)
order_id = exchange_order['id']
order_id = str(order_status['id'])
order = Order(
dt=date,
asset=asset,
@@ -538,15 +509,14 @@ class Bitfinex(Exchange):
tickers = response.json()
formatted_tickers = []
ticks = dict()
for index, ticker in enumerate(tickers):
if not len(ticker) == 11:
raise ExchangeRequestError(
error='Invalid ticker in response: {}'.format(ticker)
)
tick = dict(
asset=assets[index],
ticks[assets[index]] = dict(
timestamp=pd.Timestamp.utcnow(),
bid=ticker[1],
ask=ticker[3],
@@ -555,7 +525,6 @@ class Bitfinex(Exchange):
high=ticker[9],
volume=ticker[8],
)
formatted_tickers.append(tick)
log.debug('got tickers {}'.format(formatted_tickers))
return formatted_tickers
log.debug('got tickers {}'.format(ticks))
return ticks
+38 -4
View File
@@ -80,8 +80,17 @@ class Bittrex(Exchange):
return symbol_map
def update_portfolio(self):
pass
def get_balances(self):
try:
balances = self.api.getbalances()
except Exception as e:
raise ExchangeRequestError(error=e)
std_balances = dict()
for balance in balances:
currency = balance['Currency'].lower()
std_balances[currency] = balance['Available']
return std_balances
def create_order(self, asset, amount, is_buy, style):
log.info('creating order')
@@ -249,9 +258,34 @@ class Bittrex(Exchange):
return ohlc_map[assets] \
if isinstance(assets, TradingPair) else ohlc_map
def tickers(self):
def tickers(self, assets):
"""
As of v1.1, Bittrex only allows one ticker at the time.
So we have to make multiple calls to fetch multiple assets.
:param assets:
:return:
"""
log.info('retrieving tickers')
pass
ticks = dict()
for asset in assets:
symbol = self.get_symbol(asset)
try:
ticker = self.api.getticker(symbol)
except Exception as e:
raise ExchangeRequestError(error=e)
# TODO: catch invalid ticker
ticks[asset] = dict(
timestamp=pd.Timestamp.utcnow(),
bid=ticker['Bid'],
ask=ticker['Ask'],
last_price=ticker['Last']
)
log.debug('got tickers {}'.format(ticks))
return ticks
def get_account(self):
log.info('retrieving account data')
+52 -6
View File
@@ -14,7 +14,7 @@ from catalyst.errors import (
SymbolNotFound,
)
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
InvalidOrderStyle
InvalidOrderStyle, BaseCurrencyNotFoundError
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
ExchangeLimitOrder, ExchangeStopOrder
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
@@ -35,15 +35,12 @@ class Exchange:
self._portfolio = None
self.minute_writer = None
self.minute_reader = None
self.base_currency = None
@abstractproperty
def positions(self):
pass
@abstractproperty
def update_portfolio(self):
pass
@property
def portfolio(self):
"""
@@ -113,7 +110,7 @@ class Exchange:
asset = self.assets[key]
if not asset:
raise SymbolNotFound('Asset not found: %s' % symbol)
raise SymbolNotFound(symbol=symbol)
return asset
@@ -379,6 +376,55 @@ class Exchange:
df = pd.concat(series)
return df
def update_portfolio(self):
"""
Update the portfolio cash and position balances based on the
latest ticker prices.
:return:
"""
balances = self.get_balances()
base_position_available = balances[self.base_currency] \
if self.base_currency in balances else None
if base_position_available is None:
raise BaseCurrencyNotFoundError(
base_currency=self.base_currency,
exchange=self.name
)
portfolio = self._portfolio
portfolio.cash = base_position_available
if portfolio.starting_cash is None:
portfolio.starting_cash = portfolio.cash
if portfolio.positions:
assets = portfolio.positions.keys()
tickers = self.tickers(assets)
portfolio.positions_value = 0.0
for asset in tickers:
# TODO: convert if the position is not in the base currency
ticker = tickers[asset]
position = portfolio.positions[asset]
position.last_sale_price = ticker['last_price']
position.last_sale_date = ticker['timestamp']
portfolio.positions_value += \
position.amount * position.last_sale_price
portfolio.portfolio_value = \
portfolio.positions_value + portfolio.cash
@abstractmethod
def get_balances(self):
"""
Retrieve wallet balances for the exchange
:return balances: A dict of currency => available balance
"""
pass
@abstractmethod
def create_order(self, asset, amount, is_buy, style):
pass
+7
View File
@@ -99,6 +99,13 @@ class SidHashError(ZiplineError):
).strip()
class BaseCurrencyNotFoundError(ZiplineError):
msg = (
'Algorithm base currency {base_currency} not found in exchange '
'{exchange}.'
).strip()
class MismatchingBaseCurrencies(ZiplineError):
msg = (
'Unable to trade with base currency {base_currency} when the '