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,
    )
Esempio n. 4
0
    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,
            )
        ]
Esempio n. 5
0
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,
    }
Esempio n. 6
0
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,
            ),
Esempio n. 11
0
    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