diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index b46fe11f..de3384e2 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -17,8 +17,8 @@ from logbook import Logger from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_errors import ( ExchangeRequestError, - InvalidHistoryFrequencyError -) + InvalidHistoryFrequencyError, + InvalidOrderType) from catalyst.finance.execution import (MarketOrder, LimitOrder, StopOrder, @@ -40,7 +40,7 @@ class Bitfinex(Exchange): def __init__(self, key, secret, base_currency, portfolio=None): self.url = BITFINEX_URL self.key = key - self.secret = secret + self.secret = secret.encode('UTF-8') self.name = 'bitfinex' self.assets = {} self.load_assets() @@ -359,89 +359,26 @@ class Bitfinex(Exchange): return ohlc_map[assets] \ if isinstance(assets, TradingPair) else ohlc_map - def order(self, asset, amount, limit_price, stop_price, style): - """Place an order. - - Parameters - ---------- - asset : Asset - The asset that this order is for. - amount : int - The amount of shares to order. If ``amount`` is positive, this is - the number of shares to buy or cover. If ``amount`` is negative, - this is the number of shares to sell or short. - limit_price : float, optional - The limit price for the order. - stop_price : float, optional - The stop price for the order. - style : ExecutionStyle, optional - The execution style for the order. - - Returns - ------- - order_id : str or None - The unique identifier for this order, or None if no order was - placed. - - Notes - ----- - The ``limit_price`` and ``stop_price`` arguments provide shorthands for - passing common execution styles. Passing ``limit_price=N`` is - equivalent to ``style=LimitOrder(N)``. Similarly, passing - ``stop_price=M`` is equivalent to ``style=StopOrder(M)``, and passing - ``limit_price=N`` and ``stop_price=M`` is equivalent to - ``style=StopLimitOrder(N, M)``. It is an error to pass both a ``style`` - and ``limit_price`` or ``stop_price``. - - Bitfinex Order Types - -------------------- - LIMIT, MARKET, STOP, TRAILING STOP, - EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, - EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK. - - See Also - -------- - :class:`catalyst.finance.execution.ExecutionStyle` - :func:`catalyst.api.order_value` - :func:`catalyst.api.order_percent` + def create_order(self, asset, amount, is_buy, style): """ - if amount == 0: - log.warn('skipping order amount of 0') - return None - - base_currency = asset.symbol.split('_')[1] - if base_currency.lower() != self.base_currency.lower(): - raise NotImplementedError( - 'Currency pairs must share their base with the exchange.' - ) - - is_buy = (amount > 0) - - if isinstance(style, MarketOrder): - order_type = 'market' - elif isinstance(style, LimitOrder): - order_type = 'limit' - price = limit_price - elif isinstance(style, StopOrder): - order_type = 'stop' - price = stop_price - elif isinstance(style, StopLimitOrder): - log.warn('using limit order instead of stop/limit') - # TODO: Not sure how to do this with the api. Investigate. - order_type = 'limit' - price = limit_price - else: - raise NotImplementedError('%s orders not available' % style) - - log.debug( - 'ordering {amount} {symbol} for {price}'.format( - amount=amount, - symbol=asset.symbol, - price=price - ) - ) + Creating order on the exchange. + :param asset: + :param amount: + :param is_buy: + :param style: + :return: + """ exchange_symbol = self.get_symbol(asset) + if isinstance(style, LimitOrder) or isinstance(style, StopLimitOrder): + price = style.get_limit_price(is_buy) + order_type = 'limit' + elif isinstance(style, StopOrder): + price = style.get_stop_price(is_buy) + order_type = 'stop' + else: + raise InvalidOrderType() + req = dict( symbol=exchange_symbol, amount=str(float(abs(amount))), @@ -479,9 +416,6 @@ class Bitfinex(Exchange): limit=style.get_limit_price(is_buy), id=order_id ) - # TODO: is this required? - order.broker_order_id = order_id - self.portfolio.create_order(order) return order_id diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py index 5f8a1b17..0b79fa07 100644 --- a/catalyst/exchange/bittrex/bittrex.py +++ b/catalyst/exchange/bittrex/bittrex.py @@ -76,7 +76,7 @@ class Bittrex(Exchange): def update_portfolio(self): pass - def order(self): + def create_order(self, asset, amount, is_buy, style): log.info('creating order') pass diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 5dca2d64..f1d8ddfe 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -14,10 +14,15 @@ from catalyst.data.data_portal import BASE_FIELDS from catalyst.errors import ( SymbolNotFound, ) +from catalyst.exchange.exchange_errors import InvalidOrderType from catalyst.finance.order import ORDER_STATUS from catalyst.finance.transaction import Transaction from catalyst.exchange.exchange_utils import get_exchange_symbols from catalyst.exchange.exchange_portfolio import ExchangePortfolio +from catalyst.finance.execution import (MarketOrder, + LimitOrder, + StopOrder, + StopLimitOrder) log = Logger('Exchange') @@ -380,7 +385,10 @@ class Exchange: return pd.concat(frames) @abstractmethod - def order(self, asset, amount, limit_price, stop_price, style): + def create_order(self, asset, amount, is_buy, style): + pass + + def order(self, asset, amount, limit_price, stop_price, style=None): """Place an order. Parameters @@ -420,7 +428,38 @@ class Exchange: :func:`catalyst.api.order_value` :func:`catalyst.api.order_percent` """ - pass + if amount == 0: + log.warn('skipping order amount of 0') + return None + + if asset.base_currency != self.base_currency.lower(): + raise NotImplementedError( + 'Currency pairs must share their base with the exchange.' + ) + + is_buy = (amount > 0) + + if limit_price is not None and stop_price is not None: + style = StopLimitOrder(limit_price, stop_price, + exchange=self.name) + elif limit_price is not None: + style = LimitOrder(limit_price, exchange=self.name) + elif stop_price is not None: + style = StopOrder(stop_price, exchange=self.name) + elif style is None: + raise InvalidOrderType() + + display_price = limit_price if limit_price is not None else stop_price + log.debug( + 'issuing {side} order of {amount} {symbol} for {type}: {price}'.format( + side='buy' if is_buy else 'sell', + amount=amount, + symbol=asset.symbol, + type=style.__class__.__name__, + price='{}{}'.format(display_price, asset.base_currency) + ) + ) + return self.create_order(asset, amount, is_buy, style) @abstractmethod def get_open_orders(self, asset): diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 163ef2d2..29ae7ff6 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -67,3 +67,9 @@ class InvalidSymbolError(ZiplineError): '[Market Currency]_[Base Currency]. For example: eth_usd, btc_usd, ' 'neo_eth, ubq_btc. Error details: {error}' ).strip() + + +class InvalidOrderType(ZiplineError): + msg = ( + 'Order type not found.' + ).strip() diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index e48e4b64..4bcb4b6e 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -502,7 +502,7 @@ def run_algorithm(initialize, if exchange_name == 'bitfinex': exchange = Bitfinex( key=exchange_auth['key'], - secret=exchange_auth['secret'].encode('UTF-8'), + secret=exchange_auth['secret'], base_currency=base_currency, portfolio=portfolio ) diff --git a/tests/exchange/test_bitfinex.py b/tests/exchange/test_bitfinex.py index c0ca0610..4fd7b8ca 100644 --- a/tests/exchange/test_bitfinex.py +++ b/tests/exchange/test_bitfinex.py @@ -24,6 +24,15 @@ class BitfinexTestCase(BaseExchangeTestCase): def test_order(self): log.info('creating order') + asset = self.exchange.get_asset('eth_usd') + order_id = self.exchange.order( + asset=asset, + style=LimitOrder(limit_price=200), + limit_price=200, + amount=0.5, + stop_price=None + ) + log.info('order created {}'.format(order_id)) pass def test_open_orders(self): diff --git a/tests/exchange/test_bittrex.py b/tests/exchange/test_bittrex.py index edb1c19f..31d454e8 100644 --- a/tests/exchange/test_bittrex.py +++ b/tests/exchange/test_bittrex.py @@ -24,6 +24,15 @@ class BittrexTestCase(BaseExchangeTestCase): def test_order(self): log.info('creating order') + asset = self.exchange.get_asset('neo_eth') + order_id = self.exchange.order( + asset=asset, + style=LimitOrder(limit_price=200), + limit_price=200, + amount=0.5, + stop_price=None + ) + log.info('order created {}'.format(order_id)) pass def test_open_orders(self):