def test_reify_trades(market_state): trades = [ AbstractTrade('BTC', 'ETH', 'ETH', 4), AbstractTrade('BTC', 'ETH', 'BTC', 0.5), AbstractTrade('ETH', 'BTC', 'BCH', 3.14), AbstractTrade('BTC', 'WAT', 'BTC', 1.4), ] orders = PoloniexMarketAdapter.reify_trades(trades, market_state) assert orders == [ Order( 'BTC_ETH', 0.07420755, 4, Order.Direction.BUY, OrderType.fill_or_kill, ), Order( 'BTC_ETH', 0.07420755, 6.737858883631113, Order.Direction.BUY, OrderType.fill_or_kill, ), Order( 'BTC_ETH', 0.07420755, 5.124031796400001, Order.Direction.SELL, OrderType.fill_or_kill, ), ]
def test_execute_order_retries_exhausted(market_adapter): order = Order( 'BTC_ETH', 0.07420755, 2, Order.Direction.BUY, OrderType.fill_or_kill, ) balances = {'BTC': {'available': '1'}} responses = [ { 'error': 'Unable to fill order completely.' }, { 'orderNumber': 67890, 'resultingTrades': [] }, ] with patch.object(market_adapter.private_api, 'return_complete_balances', return_value=balances): with patch.object(market_adapter.private_api, 'buy', side_effect=responses) as mock_buy: order_id = market_adapter.execute_order(order, attempts=1) assert order_id is None mock_buy.assert_called_once_with( currency_pair=order.market, rate=order.price, amount=order.amount, order_type=order.type, )
def test_execute_order_sell(market_adapter): order = Order( 'BTC_ETH', 0.07420755, 2, Order.Direction.SELL, OrderType.fill_or_kill, ) balances = {'ETH': {'available': '4'}} response = {'orderNumber': 67890, 'resultingTrades': []} with patch.object(market_adapter.private_api, 'return_complete_balances', return_value=balances): with patch.object(market_adapter.private_api, 'sell', return_value=response) as mock_sell: order_id = market_adapter.execute_order(order) assert order_id == 67890 mock_sell.assert_called_once_with( currency_pair=order.market, rate=order.price, amount=order.amount, order_type=order.type, )
def reify_trade( cls, trade: AbstractTrade, market_state: MarketState, ) -> List[Order]: """Given an abstract trade, return a list of concrete orders that will accomplish the higher-level transaction described. """ markets = market_state.available_markets() market = format_currency_pair(trade.sell_coin, trade.buy_coin) if market not in markets: market = format_currency_pair(trade.buy_coin, trade.sell_coin) if market not in markets: raise NoMarketAvailableError( f'No market available between {trade.sell_coin} and ' f'{trade.buy_coin} and indirect trades are not yet supported' ) base, quote = split_currency_pair(market) # Price is given as base currency / quote currency price = market_state.price(market) # Order amount is given with respect to the quote currency quote_amount = market_state.estimate_value( trade.reference_coin, trade.reference_value, quote, ) # Order direction is given with respect to the quote currency if trade.sell_coin == base: # Buy quote currency; sell base currency direction = Order.Direction.BUY elif trade.sell_coin == quote: # Sell quote currency; buy quote currency direction = Order.Direction.SELL else: raise return [ Order( market, price, quote_amount, direction, OrderType.fill_or_kill, ) ]
def test_simulate_sell_order(): # Sell 1.5 ETH at 0.07414017 BTC/ETH sell = Order( 'BTC_ETH', 0.07414017, 1.5, Order.Direction.SELL, OrderType.fill_or_kill, ) balances = {'BTC': 1.0, 'ETH': 5} result = simulate_order(sell, balances) assert result == { 'BTC': 1.11121025500000001, 'ETH': 3.5, }
def test_simulate_buy_order(): # Buy 2 ETH at 0.07423378 BTC/ETH buy = Order( 'BTC_ETH', 0.07423378, 2, Order.Direction.BUY, OrderType.fill_or_kill, ) balances = {'BTC': 1.0} result = simulate_order(buy, balances) assert result == { 'BTC': 0.85153244, 'ETH': 2, }
def test_execute_order_invalid(market_adapter): order = Order( 'BTC_ETH', 0.07420755, 2, Order.Direction.BUY, OrderType.fill_or_kill, ) balances = {} with patch.object(market_adapter.private_api, 'return_complete_balances', return_value=balances): order_id = market_adapter.execute_order(order) assert order_id is None
def test_execute_order_sell_retry(market_adapter): order = Order( 'BTC_ETH', 0.07420755, 2, Order.Direction.SELL, OrderType.fill_or_kill, ) balances = {'ETH': {'available': '4'}} responses = [ { 'error': 'Unable to fill order completely.' }, { 'orderNumber': 67890, 'resultingTrades': [] }, ] with patch.object(market_adapter.private_api, 'return_complete_balances', return_value=balances): with patch.object(market_adapter.private_api, 'sell', side_effect=responses) as mock_sell: order_id = market_adapter.execute_order(order) assert order_id == 67890 mock_sell.assert_has_calls([ call( currency_pair=order.market, rate=order.price, amount=order.amount, order_type=order.type, ), call( currency_pair=order.market, rate=order.price - PoloniexMarketAdapter.ORDER_ADJUSTMENT, amount=order.amount, order_type=order.type, ), ])
def test_execute_order_unknown_error(market_adapter): order = Order( 'BTC_ETH', 0.07420755, 2, Order.Direction.BUY, OrderType.fill_or_kill, ) balances = {'BTC': {'available': '1'}} response = {'error': 'You are a bad person and you should feel bad.'} with patch.object(market_adapter.private_api, 'return_complete_balances', return_value=balances): with patch.object(market_adapter.private_api, 'buy', return_value=response): order_id = market_adapter.execute_order(order) assert order_id is None
}, # BTC/BCH 'ETH_BCH': { 'weighted_average': 1.63185726 }, # ETH/BCH } return MarketState(chart_data, {}, datetime.now(), 'BTC') @pytest.mark.parametrize('trade,expected', [ ( AbstractTrade('BTC', 'ETH', 'ETH', 4), [ Order( 'BTC_ETH', 0.07420755, 4, Order.Direction.BUY, OrderType.fill_or_kill, ), ], ), ( AbstractTrade('BTC', 'ETH', 'BTC', 0.5), [ Order( 'BTC_ETH', 0.07420755, 6.737858883631113, Order.Direction.BUY, OrderType.fill_or_kill, ),
def execute_order(self, order: Order, attempts: int = 8) -> Optional[int]: """Submit an order, returning the order number if the order is filled successfully or None otherwise. """ if attempts <= 0: logger.warning(f'Attempts exhausted; not executing order [{order}]') return None balances = self.get_balances() try: type(self).validate_order(order, balances) except OrderValidationError as e: logger.warning(f'Order failed validation: {e}') return None if order.direction == Order.Direction.BUY: method = self.private_api.buy else: method = self.private_api.sell response: Dict[Any, Any] = {} error = None try: response = method( currency_pair=order.market, rate=order.price, amount=order.amount, order_type=order.type, ) except PoloniexRequestError as e: logger.warning( f'Order [{order}] resulted in an error from Poloniex: {e}' ) logger.warning( f'{e.request.method} {e.request.url} {e.request.body}' ) error = e.message else: error = response.get('error') if 'orderNumber' in response: logger.info(f'Order [{order}] filled successfully') response['currencyPair'] = order.market logger.info(f'{response}') return response['orderNumber'] # TODO: Magic strings suck; find a better way to do this if error == 'Unable to fill order completely.': adjustment = type(self).ORDER_ADJUSTMENT if order.direction == Order.Direction.BUY: # Adjust price up a little new_price = order.price + adjustment else: # Adjust price down a little new_price = order.price - adjustment # Recurse and try again new_order = Order( order.market, new_price, order.amount, order.direction, order.type, ) return self.execute_order(new_order, attempts - 1) return None