From c47e88c26f78c413a52c789a68856a7adbe6ea7b Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 29 Aug 2017 16:05:38 -0400 Subject: [PATCH] More unit testing and refactoring related to the Bittrex addition --- catalyst/exchange/bitfinex/bitfinex.py | 9 ++-- catalyst/exchange/bittrex/bittrex.py | 70 +++++++++++++++++++++++--- catalyst/exchange/exchange_errors.py | 18 +++++++ tests/exchange/test_bittrex.py | 6 ++- 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index ec1908bf..e684e397 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -18,7 +18,7 @@ from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_errors import ( ExchangeRequestError, InvalidHistoryFrequencyError, - InvalidOrderStyle) + InvalidOrderStyle, OrderCancelError) from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \ ExchangeStopLimitOrder, ExchangeStopOrder from catalyst.finance.order import Order, ORDER_STATUS @@ -503,9 +503,10 @@ class Bitfinex(Exchange): raise ExchangeRequestError(error=e) if 'message' in status: - raise ExchangeRequestError( - error='Unable to cancel order: {} {}'.format( - order_id, status['message']) + raise OrderCancelError( + order_id=order_id, + exchange=self.name, + error=status['message'] ) def tickers(self, assets): diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py index 59ecc335..c91b9b31 100644 --- a/catalyst/exchange/bittrex/bittrex.py +++ b/catalyst/exchange/bittrex/bittrex.py @@ -1,14 +1,17 @@ import json import pandas as pd +import pytz from catalyst.assets._assets import TradingPair +from catalyst.finance.order import Order, ORDER_STATUS from logbook import Logger from six.moves import urllib from catalyst.exchange.bittrex.bittrex_api import Bittrex_api from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ - ExchangeRequestError, InvalidOrderStyle + ExchangeRequestError, InvalidOrderStyle, OrderNotFound, OrderCancelError, \ + CreateOrderError from catalyst.finance.execution import LimitOrder, StopLimitOrder log = Logger('Bittrex') @@ -99,7 +102,7 @@ class Bittrex(Exchange): if 'uuid' in order: return order['uuid'] else: - raise ExchangeRequestError(error='Order uuid not found.') + raise CreateOrderError(exchange=self.name, error=order) else: raise InvalidOrderStyle(exchange=self.name, style=style.__class__.__name__) @@ -111,13 +114,64 @@ class Bittrex(Exchange): log.info('retrieving open orders') pass - def get_order(self): - log.info('retrieving order') - pass + def _create_order(self, order_status): + log.info( + 'creating catalyst order from Bittrex {}'.format(order_status)) + if order_status['CancelInitiated']: + status = ORDER_STATUS.CANCELLED + elif order_status['Closed'] is not None: + status = ORDER_STATUS.FILLED + else: + status = ORDER_STATUS.OPEN - def cancel_order(self): - log.info('cancel order') - pass + date = pd.to_datetime(order_status['Opened'], utc=True) + amount = order_status['Quantity'] + filled = amount - order_status['QuantityRemaining'] + order = Order( + dt=date, + asset=self.assets[order_status['Exchange']], + amount=amount, + stop=None, # Not yet supported by Bittrex + limit=order_status['Limit'], + filled=filled, + id=order_status['OrderUuid'], + commission=order_status['CommissionPaid'] + ) + order.status = status + + executed_price = order_status['PricePerUnit'] + + return order, executed_price + + def get_order(self, order_id): + log.info('retrieving order') + try: + order_status = self.api.getorder(order_id) + except Exception as e: + raise ExchangeRequestError(error=e) + + if order_status is None: + raise OrderNotFound(order_id=order_id, exchange=self.name) + + order, executed_price = self._create_order(order_status) + return order + + def cancel_order(self, order_param): + order_id = order_param.id \ + if isinstance(order_param, Order) else order_param + log.info('cancelling order {}'.format(order_id)) + + try: + status = self.api.cancel(order_id) + except Exception as e: + raise ExchangeRequestError(error=e) + + if 'message' in status: + raise OrderCancelError( + order_id=order_id, + exchange=self.name, + error=status['message'] + ) def get_candles(self, data_frequency, assets, bar_count=None): """ diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index dbbb254e..b23fcdec 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -75,6 +75,24 @@ class InvalidOrderStyle(ZiplineError): ).strip() +class CreateOrderError(ZiplineError): + msg = ( + 'Unable to create order on exchange {exchange} {error}.' + ).strip() + + +class OrderNotFound(ZiplineError): + msg = ( + 'Order {order_id} not found on exchange {exchange}.' + ).strip() + + +class OrderCancelError(ZiplineError): + msg = ( + 'Unable to cancel order {order_id} on exchange {exchange} {error}.' + ).strip() + + class SidHashError(ZiplineError): msg = ( 'Unable to hash sid from symbol {symbol}.' diff --git a/tests/exchange/test_bittrex.py b/tests/exchange/test_bittrex.py index 9ca2d398..bf8fed1a 100644 --- a/tests/exchange/test_bittrex.py +++ b/tests/exchange/test_bittrex.py @@ -27,7 +27,7 @@ class BittrexTestCase(BaseExchangeTestCase): asset = self.exchange.get_asset('neo_btc') order_id = self.exchange.order( asset=asset, - limit_price=0.00055, + limit_price=0.0005, amount=1, ) log.info('order created {}'.format(order_id)) @@ -39,10 +39,12 @@ class BittrexTestCase(BaseExchangeTestCase): def test_get_order(self): log.info('retrieving order') + order = self.exchange.get_order(u'2c584020-9caf-4af5-bde0-332c0bba17e2') 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 def test_get_candles(self):