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 '
+5 -1
View File
@@ -30,5 +30,9 @@ class BaseExchangeTestCase():
pass
@abstractmethod
def get_account(self):
def test_get_balances(self):
pass
@abstractmethod
def test_get_account(self):
pass
+11 -1
View File
@@ -37,6 +37,7 @@ class BitfinexTestCase(BaseExchangeTestCase):
def test_open_orders(self):
log.info('retrieving open orders')
orders = self.exchange.get_open_orders()
pass
def test_get_order(self):
@@ -53,12 +54,21 @@ class BitfinexTestCase(BaseExchangeTestCase):
def test_tickers(self):
log.info('retrieving tickers')
tickers = self.exchange.tickers([
self.exchange.get_asset('eth_usd'),
self.exchange.get_asset('btc_usd')
])
pass
def get_account(self):
def test_get_account(self):
log.info('retrieving account data')
pass
def test_get_balances(self):
log.info('testing exchange balances')
balances = self.exchange.get_balances()
pass
# def test_order(self):
# log.info('ordering from bitfinex')
# bitfinex = Bitfinex()
+18 -9
View File
@@ -1,11 +1,7 @@
from catalyst.exchange.bittrex.bittrex import Bittrex
from catalyst.finance.order import Order
from .base import BaseExchangeTestCase
from logbook import Logger
import pandas as pd
from catalyst.finance.execution import (MarketOrder,
LimitOrder,
StopOrder,
StopLimitOrder)
from catalyst.exchange.exchange_utils import get_exchange_auth
log = Logger('test_bittrex')
@@ -31,6 +27,7 @@ class BittrexTestCase(BaseExchangeTestCase):
amount=1,
)
log.info('order created {}'.format(order_id))
assert order_id is not None
pass
def test_open_orders(self):
@@ -39,10 +36,12 @@ class BittrexTestCase(BaseExchangeTestCase):
def test_get_order(self):
log.info('retrieving order')
order = self.exchange.get_order(u'2c584020-9caf-4af5-bde0-332c0bba17e2')
order = self.exchange.get_order(
u'2c584020-9caf-4af5-bde0-332c0bba17e2')
assert isinstance(order, Order)
pass
def test_cancel_order(self,):
def test_cancel_order(self, ):
log.info('cancel order')
self.exchange.cancel_order(u'dc7bcca2-5219-4145-8848-8a593d2a72f9')
pass
@@ -65,8 +64,18 @@ class BittrexTestCase(BaseExchangeTestCase):
def test_tickers(self):
log.info('retrieving tickers')
tickers = self.exchange.tickers([
self.exchange.get_asset('ubq_btc'),
self.exchange.get_asset('neo_btc')
])
assert len(tickers) == 2
pass
def get_account(self):
log.info('retrieving account data')
def test_get_balances(self):
log.info('testing wallet balances')
balances = self.exchange.get_balances()
pass
def test_get_account(self):
log.info('testing account data')
pass