def test_is_similar_with_prices_close_to_own_price_very_low_prices():
    container = arbitrage_container_import.ArbitrageContainer(
        0.00000621, 0.00000645, trading_enums.EvaluatorStates.LONG)

    # too different prices comparing to own_exchange_price
    for price in (0.0000060, 0.0000061, 0.0000065, 0.000007):
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)

    # similar prices comparing to own_exchange_price
    for price in (0.000006196, 0.00000620, 0.00000646, 0.000006463):
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)

    container = arbitrage_container_import.ArbitrageContainer(
        0.00000062, 0.00000064, trading_enums.EvaluatorStates.LONG)

    # too different prices comparing to own_exchange_price
    for price in (0.00000060, 0.00000061, 0.00000065, 0.0000007):
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)

    # similar prices comparing to own_exchange_price
    for price in (0.0000006199, 0.0000006401):
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
async def test_ensure_no_existing_arbitrage_on_this_price():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        arbitrage_1 = arbitrage_container_import.ArbitrageContainer(
            10, 15, trading_enums.EvaluatorStates.LONG)
        arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
            20, 18, trading_enums.EvaluatorStates.SHORT)
        binance_consumer.open_arbitrages = [arbitrage_1, arbitrage_2]

        binance_producer.own_exchange_mark_price = 9
        assert binance_producer._ensure_no_existing_arbitrage_on_this_price(
            trading_enums.EvaluatorStates.LONG)
        assert binance_producer._ensure_no_existing_arbitrage_on_this_price(
            trading_enums.EvaluatorStates.SHORT)

        for price in (9.99, 10, 11, 15):
            binance_producer.own_exchange_mark_price = price
            assert not binance_producer._ensure_no_existing_arbitrage_on_this_price(
                trading_enums.EvaluatorStates.LONG)
            assert binance_producer._ensure_no_existing_arbitrage_on_this_price(
                trading_enums.EvaluatorStates.SHORT)

        for price in (18, 17.99, 20, 20.001):
            binance_producer.own_exchange_mark_price = price
            assert binance_producer._ensure_no_existing_arbitrage_on_this_price(
                trading_enums.EvaluatorStates.LONG)
            assert not binance_producer._ensure_no_existing_arbitrage_on_this_price(
                trading_enums.EvaluatorStates.SHORT)
async def test_create_initial_arbitrage_order():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        _, binance_consumer, _ = arbitrage_trading_mode_tests.exchange_tuple
        price = 10
        # long
        arbitrage = arbitrage_container_import.ArbitrageContainer(
            price, 15, trading_enums.EvaluatorStates.LONG)
        orders = await binance_consumer._create_initial_arbitrage_order(
            arbitrage)
        assert orders
        order = orders[0]
        assert order.exchange_order_type is trading_enums.TradeOrderType.LIMIT
        assert order.order_type is trading_enums.TraderOrderType.BUY_LIMIT
        assert order.side is trading_enums.TradeOrderSide.BUY
        assert order.symbol == binance_consumer.trading_mode.symbol
        assert order.order_id == arbitrage.initial_limit_order_id
        assert arbitrage in binance_consumer.open_arbitrages

        # short
        arbitrage = arbitrage_container_import.ArbitrageContainer(
            price, 15, trading_enums.EvaluatorStates.SHORT)
        orders = await binance_consumer._create_initial_arbitrage_order(
            arbitrage)
        assert orders
        order = orders[0]
        assert order.exchange_order_type is trading_enums.TradeOrderType.LIMIT
        assert order.order_type is trading_enums.TraderOrderType.SELL_LIMIT
        assert order.side is trading_enums.TradeOrderSide.SELL
        assert order.symbol == binance_consumer.trading_mode.symbol
        assert order.order_id == arbitrage.initial_limit_order_id
        assert arbitrage in binance_consumer.open_arbitrages
async def test_ensure_no_expired_opportunities():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, exchange_manager = exchange_tuple
        arbitrage_1 = arbitrage_container_import.ArbitrageContainer(
            10, 15, trading_enums.EvaluatorStates.LONG)
        arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
            20, 17, trading_enums.EvaluatorStates.SHORT)
        binance_consumer.open_arbitrages = [arbitrage_1, arbitrage_2]

        with mock.patch.object(binance_producer,
                               "_cancel_order",
                               new=mock.AsyncMock()) as cancel_order_mock:
            # average price is 18
            # long order is valid
            # short order is expired (price > 17)
            await binance_producer._ensure_no_expired_opportunities(
                18, trading_enums.EvaluatorStates.LONG)
            assert arbitrage_2 not in binance_consumer.open_arbitrages
            cancel_order_mock.assert_called_once()
            cancel_order_mock.reset_mock()

            await binance_producer._ensure_no_expired_opportunities(
                18, trading_enums.EvaluatorStates.SHORT)
            assert binance_consumer.open_arbitrages == [arbitrage_1]
            cancel_order_mock.assert_not_called()
async def test_trigger_arbitrage_secondary_order():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, _, _ = exchange_tuple
        order_id = "1"
        price = 10
        quantity = 3
        fees = 0.1
        fees_currency = "BTC"
        symbol = "BTC/USDT"
        order_dict = get_order_dict(order_id, symbol, price, quantity,
                                    trading_enums.OrderStatus.FILLED.value,
                                    trading_enums.TradeOrderType.LIMIT.value,
                                    fees, fees_currency)
        with mock.patch.object(binance_producer,
                               "_create_arbitrage_secondary_order",
                               new=mock.AsyncMock()) as order_mock:
            # long: already bought, is now selling
            arbitrage = arbitrage_container_import.ArbitrageContainer(
                price, 15, trading_enums.EvaluatorStates.LONG)
            await binance_producer._trigger_arbitrage_secondary_order(
                arbitrage, order_dict, 3)
            updated_arbitrage, secondary_quantity = order_mock.mock_calls[
                0].args
            assert updated_arbitrage is arbitrage
            assert arbitrage.passed_initial_order
            assert arbitrage.initial_before_fee_filled_quantity == 30
            assert secondary_quantity == 2.9
            order_mock.reset_mock()

            # short: already sold, is now buying: no fee on base side
            arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
                price, 7, trading_enums.EvaluatorStates.SHORT)
            await binance_producer._trigger_arbitrage_secondary_order(
                arbitrage_2, order_dict, 3)
            updated_arbitrage, secondary_quantity = order_mock.mock_calls[
                0].args
            assert updated_arbitrage is arbitrage_2
            assert arbitrage_2.passed_initial_order
            assert arbitrage_2.initial_before_fee_filled_quantity == 3
            assert round(secondary_quantity, 5) == 4.14282
            order_mock.reset_mock()

            # short: already sold, is now buying: fee on base side
            arbitrage_3 = arbitrage_container_import.ArbitrageContainer(
                price, 7, trading_enums.EvaluatorStates.SHORT)
            order_dict = get_order_dict(
                order_id, symbol, price, quantity,
                trading_enums.OrderStatus.FILLED.value,
                trading_enums.TradeOrderType.STOP_LOSS.value, fees, "USDT")
            await binance_producer._trigger_arbitrage_secondary_order(
                arbitrage_3, order_dict, 3)
            updated_arbitrage, secondary_quantity = order_mock.mock_calls[
                0].args
            assert updated_arbitrage is arbitrage_3
            assert arbitrage_3.passed_initial_order
            assert arbitrage_3.initial_before_fee_filled_quantity == 3
            assert round(secondary_quantity, 5) == 4.27139
def test_is_expired():
    container = arbitrage_container_import.ArbitrageContainer(
        90, 100, trading_enums.EvaluatorStates.LONG)
    assert not container.is_expired(99.99)
    assert container.is_expired(99)

    container = arbitrage_container_import.ArbitrageContainer(
        100, 90, trading_enums.EvaluatorStates.SHORT)
    assert not container.is_expired(90.01)
    assert container.is_expired(91)
def test_is_expired_very_low_prices():
    container = arbitrage_container_import.ArbitrageContainer(
        0.00000621, 0.00000645, trading_enums.EvaluatorStates.LONG)
    assert not container.is_expired(0.00000644)
    assert container.is_expired(0.00000643)

    container = arbitrage_container_import.ArbitrageContainer(
        0.00000062, 0.00000064, trading_enums.EvaluatorStates.LONG)
    assert not container.is_expired(0.000000639)
    assert container.is_expired(0.000000637)
async def test_create_secondary_arbitrage_order():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        _, binance_consumer, _ = arbitrage_trading_mode_tests.exchange_tuple
        price = 10

        # long
        arbitrage = arbitrage_container_import.ArbitrageContainer(
            price, 15, trading_enums.EvaluatorStates.LONG)
        quantity = 5
        orders = await binance_consumer._create_secondary_arbitrage_order(
            arbitrage, quantity)
        assert orders

        limit_order = orders[0]
        assert limit_order.exchange_order_type is trading_enums.TradeOrderType.LIMIT
        assert limit_order.order_type is trading_enums.TraderOrderType.SELL_LIMIT
        assert limit_order.side is trading_enums.TradeOrderSide.SELL
        assert limit_order.symbol == binance_consumer.trading_mode.symbol
        assert limit_order.order_id == arbitrage.secondary_limit_order_id
        assert limit_order.origin_quantity == quantity

        stop_order = limit_order.linked_orders[0]
        assert stop_order.exchange_order_type is trading_enums.TradeOrderType.STOP_LOSS
        assert stop_order.order_type is trading_enums.TraderOrderType.STOP_LOSS
        assert stop_order.side is trading_enums.TradeOrderSide.SELL
        assert stop_order.symbol == binance_consumer.trading_mode.symbol
        assert stop_order.order_id == arbitrage.secondary_stop_order_id
        assert stop_order.origin_quantity == quantity

        # short
        arbitrage = arbitrage_container_import.ArbitrageContainer(
            price, 15, trading_enums.EvaluatorStates.SHORT)
        quantity = 5
        orders = await binance_consumer._create_secondary_arbitrage_order(
            arbitrage, quantity)
        assert orders

        limit_order = orders[0]
        assert limit_order.exchange_order_type is trading_enums.TradeOrderType.LIMIT
        assert limit_order.order_type is trading_enums.TraderOrderType.BUY_LIMIT
        assert limit_order.side is trading_enums.TradeOrderSide.BUY
        assert limit_order.symbol == binance_consumer.trading_mode.symbol
        assert limit_order.order_id == arbitrage.secondary_limit_order_id
        assert limit_order.origin_quantity == quantity

        stop_order = limit_order.linked_orders[0]
        assert stop_order.exchange_order_type is trading_enums.TradeOrderType.STOP_LOSS
        assert stop_order.order_type is trading_enums.TraderOrderType.STOP_LOSS
        assert stop_order.side is trading_enums.TradeOrderSide.BUY
        assert stop_order.symbol == binance_consumer.trading_mode.symbol
        assert stop_order.order_id == arbitrage.secondary_stop_order_id
        assert stop_order.origin_quantity == quantity
async def test_get_arbitrage():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        arbitrage_1 = arbitrage_container_import.ArbitrageContainer(
            10, 15, trading_enums.EvaluatorStates.LONG)
        arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
            20, 18, trading_enums.EvaluatorStates.SHORT)
        binance_consumer.open_arbitrages = [arbitrage_1, arbitrage_2]
        arbitrage_1.initial_limit_order_id = "1"
        assert arbitrage_1 is binance_producer._get_arbitrage("1")
        assert None is binance_producer._get_arbitrage("2")
async def test_close_arbitrage():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        arbitrage_1 = arbitrage_container_import.ArbitrageContainer(
            10, 15, trading_enums.EvaluatorStates.LONG)
        arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
            20, 17, trading_enums.EvaluatorStates.SHORT)
        binance_consumer.open_arbitrages = [arbitrage_1, arbitrage_2]
        binance_producer._close_arbitrage(arbitrage_1)
        assert arbitrage_1 not in binance_consumer.open_arbitrages
        assert binance_producer.state is trading_enums.EvaluatorStates.NEUTRAL
        assert binance_producer.final_eval == ""
def test_is_similar_with_prices_in_arbitrage_range():
    container = arbitrage_container_import.ArbitrageContainer(
        90, 100, trading_enums.EvaluatorStates.LONG)

    for price in range(container.own_exchange_price, container.target_price):
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)

    container = arbitrage_container_import.ArbitrageContainer(
        100, 90, trading_enums.EvaluatorStates.SHORT)

    for price in range(container.target_price, container.own_exchange_price):
        assert container.is_similar(price, trading_enums.EvaluatorStates.SHORT)
        assert container.is_similar(price, trading_enums.EvaluatorStates.SHORT)
async def test_order_cancelled_callback():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        order_id = "1"
        price = 10
        quantity = 3
        fees = 0.1
        fees_currency = "BTC"
        symbol = "BTC/USD"
        order_dict = get_order_dict(order_id, symbol, price, quantity,
                                    trading_enums.OrderStatus.FILLED.value,
                                    trading_enums.TradeOrderType.LIMIT.value,
                                    fees, fees_currency)
        with mock.patch.object(binance_producer,
                               "_close_arbitrage",
                               new=mock.Mock()) as close_mock:
            # no open arbitrage
            await binance_producer.order_cancelled_callback(order_dict)
            close_mock.assert_not_called()

            # open arbitrage with different order id: nothing happens
            arbitrage = arbitrage_container_import.ArbitrageContainer(
                price, 15, trading_enums.EvaluatorStates.LONG)
            binance_consumer.open_arbitrages.append(arbitrage)
            await binance_producer.order_cancelled_callback(order_dict)
            close_mock.assert_not_called()

            # open arbitrage with this order id: arbitrage gets closed
            arbitrage.initial_limit_order_id = order_id
            await binance_producer.order_cancelled_callback(order_dict)
            close_mock.assert_called_once()
def test_should_be_discarded_after_order_cancel():
    container = arbitrage_container_import.ArbitrageContainer(
        90, 100, trading_enums.EvaluatorStates.LONG)
    assert not container.should_be_discarded_after_order_cancel("123")
    container.initial_limit_order_id = "123"
    assert container.should_be_discarded_after_order_cancel("123")
    assert not container.should_be_discarded_after_order_cancel("1234")
 async def _trigger_arbitrage_opportunity(self, other_exchanges_average_price, state):
     # ensure no similar arbitrage is already in place
     if self._ensure_no_existing_arbitrage_on_this_price(state):
         self._log_arbitrage_opportunity_details(other_exchanges_average_price, state)
         arbitrage_container = arbitrage_container_import.ArbitrageContainer(self.own_exchange_mark_price,
                                                                             other_exchanges_average_price, state)
         await self._create_arbitrage_initial_order(arbitrage_container)
         self._register_state(state, other_exchanges_average_price - self.own_exchange_mark_price)
async def test_get_open_arbitrages():
    binance = "binance"
    kraken = "kraken"
    async with arbitrage_trading_mode_tests.exchange(binance) as binance_tuple, \
            arbitrage_trading_mode_tests.exchange(kraken, backtesting=binance_tuple[2].backtesting) as kraken_tuple:
        binance_producer, binance_consumer, _ = binance_tuple
        kraken_producer, kraken_consumer, _ = kraken_tuple
        arbitrage_1 = arbitrage_container_import.ArbitrageContainer(
            10, 15, trading_enums.EvaluatorStates.LONG)
        arbitrage_2 = arbitrage_container_import.ArbitrageContainer(
            20, 17, trading_enums.EvaluatorStates.SHORT)
        binance_consumer.open_arbitrages = [arbitrage_1, arbitrage_2]
        assert kraken_consumer.open_arbitrages == []
        assert binance_producer._get_open_arbitrages(
        ) is binance_consumer.open_arbitrages
        assert kraken_producer._get_open_arbitrages(
        ) is kraken_consumer.open_arbitrages
def test_is_similar_with_prices_close_to_own_price():
    container = arbitrage_container_import.ArbitrageContainer(
        90, 100, trading_enums.EvaluatorStates.LONG)
    # same price and state
    assert container.is_similar(90, trading_enums.EvaluatorStates.LONG)
    # same price but different state
    assert not container.is_similar(90, trading_enums.EvaluatorStates.SHORT)

    # too different prices comparing to own_exchange_price
    for price in (110, 200, 80, 20, 0):
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)
        assert not container.is_similar(price,
                                        trading_enums.EvaluatorStates.LONG)

    # similar prices comparing to own_exchange_price
    for price in (89.97, 90.01):
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
        assert container.is_similar(price, trading_enums.EvaluatorStates.LONG)
def test_is_watching_this_order():
    container = arbitrage_container_import.ArbitrageContainer(
        90, 100, trading_enums.EvaluatorStates.LONG)
    assert not container.is_watching_this_order("init")
    assert not container.is_watching_this_order("sec")
    assert not container.is_watching_this_order("stop")

    container.initial_limit_order_id = "init"
    assert container.is_watching_this_order("init")
    assert not container.is_watching_this_order("sec")
    assert not container.is_watching_this_order("stop")

    container.secondary_limit_order_id = "sec"
    assert container.is_watching_this_order("init")
    assert container.is_watching_this_order("sec")
    assert not container.is_watching_this_order("stop")

    container.secondary_stop_order_id = "stop"
    assert container.is_watching_this_order("init")
    assert container.is_watching_this_order("sec")
    assert container.is_watching_this_order("stop")

    container.initial_limit_order_id = None
    assert not container.is_watching_this_order("init")
    assert container.is_watching_this_order("sec")
    assert container.is_watching_this_order("stop")

    container.secondary_limit_order_id = None
    assert not container.is_watching_this_order("init")
    assert not container.is_watching_this_order("sec")
    assert container.is_watching_this_order("stop")

    container.secondary_stop_order_id = None
    assert not container.is_watching_this_order("init")
    assert not container.is_watching_this_order("sec")
    assert not container.is_watching_this_order("stop")
async def test_order_filled_callback():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        order_id = "1"
        price = 10
        quantity = 3
        fees = 0.1
        fees_currency = "BTC"
        symbol = "BTC/USD"
        order_dict = get_order_dict(order_id, symbol, price, quantity,
                                    trading_enums.OrderStatus.FILLED.value,
                                    trading_enums.TradeOrderType.LIMIT.value,
                                    fees, fees_currency)
        with mock.patch.object(binance_producer, "_close_arbitrage", new=mock.Mock()) as close_mock, \
                mock.patch.object(binance_producer, "_trigger_arbitrage_secondary_order",
                                  new=mock.AsyncMock()) as trigger_mock, \
                mock.patch.object(binance_producer, "_log_results", new=mock.Mock()) as result_mock:
            # nothing happens: order id not in open arbitrages
            await binance_producer.order_filled_callback(order_dict)
            close_mock.assert_not_called()
            trigger_mock.assert_not_called()

            # order id now in open arbitrages
            arbitrage = arbitrage_container_import.ArbitrageContainer(
                price, 15, trading_enums.EvaluatorStates.LONG)
            arbitrage.initial_limit_order_id = order_id
            binance_consumer.open_arbitrages.append(arbitrage)

            await binance_producer.order_filled_callback(order_dict)
            close_mock.assert_not_called()
            result_mock.assert_not_called()
            # call create secondary order
            trigger_mock.assert_called_once()
            trigger_mock.reset_mock()

            # last step case 1: close arbitrage: fill callback with secondary limit order
            limit_id = "2"
            arbitrage.passed_initial_order = True
            arbitrage.secondary_limit_order_id = limit_id
            arbitrage.initial_before_fee_filled_quantity = 29.9
            sec_limit_order_dict = get_order_dict(
                limit_id, symbol, price, quantity,
                trading_enums.OrderStatus.FILLED.value,
                trading_enums.TradeOrderType.LIMIT.value, fees, fees_currency)
            await binance_producer.order_filled_callback(sec_limit_order_dict)
            # call close arbitrage
            close_mock.assert_called_once()
            trigger_mock.assert_not_called()
            result_mock.assert_called_once()
            _, arbitrage_success, filled_quantity = result_mock.mock_calls[
                0].args
            assert arbitrage_success
            assert filled_quantity == quantity * price
            close_mock.reset_mock()
            result_mock.reset_mock()

            # last step case 2: close arbitrage: fill callback with secondary stop order
            stop_id = "3"
            arbitrage.secondary_stop_order_id = stop_id
            sec_stop_order_dict = get_order_dict(
                stop_id, symbol, price, quantity,
                trading_enums.OrderStatus.FILLED.value,
                trading_enums.TradeOrderType.STOP_LOSS.value, fees,
                fees_currency)
            await binance_producer.order_filled_callback(sec_stop_order_dict)
            # call close arbitrage
            close_mock.assert_called_once()
            result_mock.assert_called_once()
            _, arbitrage_success, filled_quantity = result_mock.mock_calls[
                0].args
            assert not arbitrage_success
            assert filled_quantity == quantity * price
            trigger_mock.assert_not_called()