async def test_mark_price_callback():
    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_tuple
        kraken_producer, _, _ = kraken_tuple

        with mock.patch.object(binance_producer, "_create_arbitrage_initial_order",
                               new=mock.AsyncMock()) as binance_order_mock, \
                mock.patch.object(kraken_producer, "_create_arbitrage_initial_order",
                                  new=mock.AsyncMock()) as kraken_order_mock:
            # no own exchange price yet
            await kraken_producer._mark_price_callback(binance, "", "", "",
                                                       1000)
            kraken_order_mock.assert_not_called()
            await binance_producer._mark_price_callback(
                kraken, "", "", "", 1000)
            binance_order_mock.assert_not_called()

            # set own exchange mark price on kraken
            kraken_producer.own_exchange_mark_price = 900
            # no effect on binance
            await binance_producer._mark_price_callback(
                kraken, "", "", "", 1000)
            binance_order_mock.assert_not_called()
            # create arbitrage on kraken
            await kraken_producer._mark_price_callback(binance, "", "", "",
                                                       1000)
            kraken_order_mock.assert_called_once()
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_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_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()
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_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
async def test_register_state():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple
        assert binance_producer.state is trading_enums.EvaluatorStates.NEUTRAL
        binance_producer._register_state(trading_enums.EvaluatorStates.LONG, 1)
        assert binance_producer.state is trading_enums.EvaluatorStates.LONG
        assert "1" in binance_producer.final_eval
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
async def test_log_arbitrage_opportunity_details():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, _, _ = exchange_tuple
        binance_producer.own_exchange_mark_price = 100
        debug_mock = mock.Mock()
        # do not mock with context manager to keep the mock in teardown
        binance_producer.logger = debug_mock

        binance_producer._log_arbitrage_opportunity_details(
            99.999, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(-0.001)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            90, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(-10)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            1, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(-99)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            0, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(-100)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            0, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(0)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            100.00001, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(0.00001)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            110, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(10)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            150, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(50)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            250, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(150)}%" in debug_mock.debug.call_args[
            0][0]

        binance_producer._log_arbitrage_opportunity_details(
            20100, trading_enums.EvaluatorStates.LONG)
        assert f"{pretty_printer.round_with_decimal_count(20000)}%" in debug_mock.debug.call_args[
            0][0]
async def test_get_quantity_from_holdings():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        _, binance_consumer, _ = arbitrage_trading_mode_tests.exchange_tuple
        binance_consumer.PORTFOLIO_PERCENT_PER_TRADE = 0.5
        assert binance_consumer._get_quantity_from_holdings(
            10, 100, trading_enums.EvaluatorStates.SHORT) == 5
        assert binance_consumer._get_quantity_from_holdings(
            10, 100, trading_enums.EvaluatorStates.LONG) == 50
async def test_get_stop_loss_price():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        _, binance_consumer, arbitrage_trading_mode_tests.exchange_manager = arbitrage_trading_mode_tests.exchange_tuple
        binance_consumer.STOP_LOSS_DELTA_FROM_OWN_PRICE = 0.01
        symbol_market = arbitrage_trading_mode_tests.exchange_manager.exchange.get_market_status(
            "BTC/USDT", with_fixer=False)
        assert binance_consumer._get_stop_loss_price(symbol_market, 100,
                                                     True) == 99
        assert binance_consumer._get_stop_loss_price(symbol_market, 100,
                                                     False) == 101
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_init():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        binance_producer, binance_consumer, _ = arbitrage_trading_mode_tests.exchange_tuple

        # trading mode
        assert len(binance_producer.trading_mode.consumers) == 2
        assert len(binance_producer.trading_mode.producers) == 1

        # consumer
        assert binance_consumer.PORTFOLIO_PERCENT_PER_TRADE != 0
        assert binance_consumer.STOP_LOSS_DELTA_FROM_OWN_PRICE != 0
        assert binance_consumer.open_arbitrages == []
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 == ""
async def test_init():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, binance_consumer, _ = exchange_tuple

        # producer
        assert binance_producer.own_exchange_mark_price is None
        assert binance_producer.other_exchanges_mark_prices == {}
        assert binance_producer.sup_triggering_price_delta_ratio > 1
        assert binance_producer.inf_triggering_price_delta_ratio < 1
        assert binance_producer.base
        assert binance_producer.quote
        assert binance_producer.lock
async def test_create_new_orders():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as arbitrage_trading_mode_tests.exchange_tuple:
        _, binance_consumer, _ = arbitrage_trading_mode_tests.exchange_tuple

        symbol = "BTC/USDT"
        final_note = None
        state = trading_enums.EvaluatorStates.SHORT
        with mock.patch.object(binance_consumer, "_create_initial_arbitrage_order",
                               new=mock.AsyncMock()) as initial_mock, \
                mock.patch.object(binance_consumer, "_create_secondary_arbitrage_order",
                                  new=mock.AsyncMock()) as secondary_mock:
            # no data in kwargs
            with pytest.raises(KeyError):
                await binance_consumer.create_new_orders(
                    symbol, final_note, state)
            initial_mock.assert_not_called()
            secondary_mock.assert_not_called()

            data = {
                arbitrage_trading_mode.ArbitrageModeConsumer.ARBITRAGE_PHASE_KEY:
                arbitrage_trading_mode.ArbitrageModeConsumer.INITIAL_PHASE,
                arbitrage_trading_mode.ArbitrageModeConsumer.ARBITRAGE_CONTAINER_KEY:
                None
            }
            await binance_consumer.create_new_orders(symbol,
                                                     final_note,
                                                     state,
                                                     data=data)
            initial_mock.assert_called_once()
            secondary_mock.assert_not_called()
            initial_mock.reset_mock()

            data = {
                arbitrage_trading_mode.ArbitrageModeConsumer.ARBITRAGE_PHASE_KEY:
                arbitrage_trading_mode.ArbitrageModeConsumer.SECONDARY_PHASE,
                arbitrage_trading_mode.ArbitrageModeConsumer.ARBITRAGE_CONTAINER_KEY:
                None,
                arbitrage_trading_mode.ArbitrageModeConsumer.QUANTITY_KEY:
                None
            }
            await binance_consumer.create_new_orders(symbol,
                                                     final_note,
                                                     state,
                                                     data=data)
            initial_mock.assert_not_called()
            secondary_mock.assert_called_once()
async def test_trigger_arbitrage_opportunity():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, _, _ = exchange_tuple

        with mock.patch.object(binance_producer, "_create_arbitrage_initial_order", new=mock.AsyncMock()) as order_mock, \
                mock.patch.object(binance_producer, "_register_state", new=mock.Mock()) as register_mock, \
                mock.patch.object(binance_producer, "_log_arbitrage_opportunity_details", new=mock.Mock()) as \
                log_arbitrage_opportunity_details_mock:
            binance_producer.own_exchange_mark_price = 10
            await binance_producer._trigger_arbitrage_opportunity(
                15, trading_enums.EvaluatorStates.LONG)
            order_mock.assert_called_once()
            register_mock.assert_called_once_with(
                trading_enums.EvaluatorStates.LONG, 5)
            log_arbitrage_opportunity_details_mock.assert_called_once_with(
                15, trading_enums.EvaluatorStates.LONG)
async def test_own_exchange_mark_price_callback():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, _, _ = exchange_tuple

        with mock.patch.object(binance_producer,
                               "_create_arbitrage_initial_order",
                               new=mock.AsyncMock()) as order_mock:
            # no other exchange mark price yet
            await binance_producer._own_exchange_mark_price_callback(
                "", "", "", "", 11)
            assert binance_producer.own_exchange_mark_price == 11
            order_mock.assert_not_called()

            binance_producer.other_exchanges_mark_prices["kraken"] = 20
            binance_producer.other_exchanges_mark_prices["bitfinex"] = 22
            # other exchange mark price is set
            await binance_producer._own_exchange_mark_price_callback(
                "", "", "", "", 11)
            order_mock.assert_called_once()
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()
async def test_analyse_arbitrage_opportunities():
    async with arbitrage_trading_mode_tests.exchange(
            "binance") as exchange_tuple:
        binance_producer, _, _ = exchange_tuple

        with mock.patch.object(binance_producer, "_ensure_no_expired_opportunities",
                               new=mock.AsyncMock()) as expiration_mock, \
                mock.patch.object(binance_producer, "_trigger_arbitrage_opportunity",
                                  new=mock.AsyncMock()) as trigger_mock:
            # long opportunity 1
            binance_producer.own_exchange_mark_price = 10
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 100,
                "binanceje": 200,
                "bitfinex": 150
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_called_once_with(
                150, trading_enums.EvaluatorStates.LONG)
            trigger_mock.assert_called_once_with(
                150, trading_enums.EvaluatorStates.LONG)
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # short opportunity 1
            binance_producer.own_exchange_mark_price = 100
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 70,
                "binanceje": 71,
                "bitfinex": 75
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_called_once_with(
                72, trading_enums.EvaluatorStates.SHORT)
            trigger_mock.assert_called_once_with(
                72, trading_enums.EvaluatorStates.SHORT)
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # long opportunity but price too close to current price
            binance_producer.own_exchange_mark_price = 71.99
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 70,
                "binanceje": 71,
                "bitfinex": 75
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()

            # short opportunity but price too close to current price
            binance_producer.own_exchange_mark_price = 72.01
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 70,
                "binanceje": 71,
                "bitfinex": 75
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()

            # with higher numbers
            # higher numbers long opportunity
            # max long exclusive trigger should be 9803.921568627451 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 9802.9999
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_called_once_with(
                10000, trading_enums.EvaluatorStates.LONG)
            trigger_mock.assert_called_once_with(
                10000, trading_enums.EvaluatorStates.LONG)
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # higher numbers long opportunity: fail to pass threshold 1
            # max long exclusive trigger should be 9803.921568627451 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 9803.921568627451
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # higher numbers long opportunity: fail to pass threshold 2
            # max long exclusive trigger should be 9803.921568627451 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 9803.9216
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # higher numbers short opportunity
            # min short exclusive trigger should be 10204.081632653062 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 10205
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_called_once_with(
                10000, trading_enums.EvaluatorStates.SHORT)
            trigger_mock.assert_called_once_with(
                10000, trading_enums.EvaluatorStates.SHORT)
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # higher numbers short opportunity: fail to pass threshold 1
            # min short exclusive trigger should be 10204.081632653062 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 10203.081632653062
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()

            # higher numbers short opportunity: fail to pass threshold 2
            # min short exclusive trigger should be 10204.081632653062 on own_exchange_mark_price
            binance_producer.own_exchange_mark_price = 10204.0815
            binance_producer.other_exchanges_mark_prices = {
                "kraken": 9000,
                "binanceje": 10000,
                "bitfinex": 11000
            }
            await binance_producer._analyse_arbitrage_opportunities()
            expiration_mock.assert_not_called()
            trigger_mock.assert_not_called()
            expiration_mock.reset_mock()
            trigger_mock.reset_mock()