import math

import numpy as np
import pytest

import jesse.helpers as jh
import jesse.services.selectors as selectors
from jesse import exceptions
from jesse.config import reset_config
from jesse.enums import exchanges, timeframes, order_types
from jesse.factories import range_candles, candles_from_close_prices
from jesse.models import ClosedTrade
from jesse.models import Order
from jesse.modes import backtest_mode
from jesse.routes import router
from jesse.store import store
from jesse.strategies import Strategy
from tests.data import test_candles_0
from tests.data import test_candles_1
from jesse.testing_utils import set_up, single_route_backtest, two_routes_backtest


def test_average_stop_loss_exception():
    with pytest.raises(exceptions.InvalidStrategy):
        single_route_backtest('Test39')


def test_average_take_profit_and_average_stop_loss():
    single_route_backtest('Test36')

    assert len(store.completed_trades.trades) == 2

    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 1
    assert t1.exit_price == 3.5
    assert t1.qty == 2

    t2: ClosedTrade = store.completed_trades.trades[1]
    assert t2.type == 'short'
    assert t2.entry_price == 11
    assert t2.exit_price == 13.5
    assert t2.qty == 2


def test_average_take_profit_exception():
    with pytest.raises(exceptions.InvalidStrategy):
        single_route_backtest('Test38')


def test_average_entry_price_property():
    single_route_backtest('TestAverageEntryPriceProperty')


def test_has_long_entry_orders_property():
    single_route_backtest('TestHasLongEntryOrdersProperty')


def test_has_short_entry_orders_property():
    single_route_backtest('TestHasShortEntryOrdersProperty')


def test_has_long_entry_orders_property_in_filters():
    single_route_backtest('TestHasLongAndShortEntryOrdersPropertiesInFilters')


def test_can_close_a_long_position_and_go_short_at_the_same_candle():
    single_route_backtest('Test45', is_futures_trading=True, leverage_mode='isolated')

    trades = store.completed_trades.trades

    assert len(trades) == 2
    # the position should no longer stay open because it gets liquidated eventually
    assert store.app.total_open_trades == 0
    assert store.app.total_liquidations == 1


def test_fee_rate_property():
    single_route_backtest('Test48')


def test_filter_readable_exception():
    with pytest.raises(Exception) as err:
        single_route_backtest('Test47')

    assert str(err.value).startswith("Invalid filter format")


def test_filters():
    single_route_backtest('Test37')

    assert len(store.completed_trades.trades) == 0


def test_forming_candles():
    reset_config()
    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test19'}
    ] 
    extra_routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_15}
    ]

    candles = {}
    key = jh.key(exchanges.SANDBOX, 'BTC-USDT')
    candles[key] = {
        'exchange': exchanges.SANDBOX,
        'symbol': 'BTC-USDT',
        'candles': test_candles_0
    }

    backtest_mode.run(False, {}, routes, extra_routes, '2019-04-01', '2019-04-02', candles)

    # use math.ceil because it must include forming candle too
    assert len(store.candles.get_candles(exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_5)) == math.ceil(1382 / 5)
    assert len(store.candles.get_candles(exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_15)) == math.ceil(
        1382 / 15)


def test_increasing_position_size_after_opening():
    single_route_backtest('Test16')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == (7 + 10) / 2
    assert t1.exit_price == 15
    assert t1.qty == 2
    assert t1.fee == 0


def test_is_smart_enough_to_open_positions_via_market_orders():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_1, 'strategy': 'Test05'}
    ]

    candles = {}
    key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
    candles[key] = {
        'exchange': exchanges.SANDBOX,
        'symbol': 'ETH-USDT',
        'candles': test_candles_1
    }

    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    assert len(store.completed_trades.trades) == 2

    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 129.23
    assert t1.exit_price == 128.35
    assert t1.qty == 10.204
    assert t1.fee == 0
    assert t1.opened_at == 1547201100000 + 60000
    assert t1.closed_at == 1547202840000 + 60000
    assert t1.orders[0].type == order_types.MARKET

    t2: ClosedTrade = store.completed_trades.trades[1]
    assert t2.type == 'short'
    assert t2.entry_price == 128.01
    assert t2.exit_price == 126.58
    assert t2.qty == 10
    assert t2.fee == 0
    assert t2.opened_at == 1547203560000 + 60000
    assert t2.closed_at == 1547203740000 + 60000
    assert t2.orders[0].type == order_types.MARKET


def test_is_smart_enough_to_open_positions_via_stop_orders():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test06'}
    ]

    candles = {}
    key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
    candles[key] = {
        'exchange': exchanges.SANDBOX,
        'symbol': 'ETH-USDT',
        'candles': test_candles_1
    }

    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)
    assert len(store.completed_trades.trades) == 2

    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 129.33
    assert t1.exit_price == 128.35
    assert t1.qty == 10.204
    assert t1.fee == 0
    assert t1.opened_at == 1547201100000 + 60000
    assert t1.closed_at == 1547202840000 + 60000
    assert t1.orders[0].type == order_types.STOP

    t2: ClosedTrade = store.completed_trades.trades[1]
    assert t2.type == 'short'
    assert t2.entry_price == 128.05
    assert t2.exit_price == 126.58
    assert t2.qty == 10
    assert t2.fee == 0
    assert t2.opened_at == 1547203560000 + 60000
    assert t2.closed_at == 1547203740000 + 60000
    assert t2.orders[0].type == order_types.STOP


def test_liquidate():
    single_route_backtest('Test31')

    assert len(store.completed_trades.trades) == 2
    t1: ClosedTrade = store.completed_trades.trades[0]
    t2: ClosedTrade = store.completed_trades.trades[1]

    assert t1.type == 'long'
    assert t1.entry_price == 1
    assert t1.exit_price == 11

    assert t2.type == 'short'
    assert t2.entry_price == 21
    assert t2.exit_price == 41


def test_modifying_stop_loss_after_part_of_position_is_already_reduced_with_stop_loss():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_1, 'strategy': 'Test14'}
    ]

    generated_candles = candles_from_close_prices(
        list(range(1, 10)) + list(range(10, 1, -1))
    )

    candles = {}
    key = jh.key(exchanges.SANDBOX, 'BTC-USDT')
    candles[key] = {
        'exchange': exchanges.SANDBOX,
        'symbol': 'BTC-USDT',
        'candles': generated_candles
    }

    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == (4 * 2 + 6) / 3
    assert t1.qty == 1.5
    assert t1.fee == 0


def test_modifying_take_profit_after_opening_position():
    single_route_backtest('Test12')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == 16
    assert t1.qty == 1.5
    assert t1.fee == 0


def test_modifying_take_profit_after_part_of_position_is_already_reduced_with_profit():
    single_route_backtest('Test13')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == (16 * 2 + 11) / 3
    assert t1.qty == 1.5
    assert t1.fee == 0


def test_must_not_be_able_to_set_two_similar_routes():
    reset_config()
    router.set_routes([
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test01'},
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_30, 'strategy': 'Test02'},
    ])
    with pytest.raises(Exception) as err:
        store.reset(True)
    assert str(
        err.value).startswith('each exchange-symbol pair can be traded only once')


def test_on_reduced_position():
    single_route_backtest('Test18')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == 13
    assert t1.qty == 2
    assert t1.fee == 0


def test_on_route_canceled():
    two_routes_backtest('Test27', 'Test28')

    t1 = store.completed_trades.trades[0]

    assert t1.symbol == 'BTC-USDT'
    assert t1.type == 'long'
    assert t1.entry_price == 101
    assert t1.exit_price == 120
    assert t1.qty == 1


def test_on_route_increased_position_and_on_route_reduced_position_and_strategy_vars():
    two_routes_backtest('Test29', 'Test30')

    # long BTC-USD
    t1 = store.completed_trades.trades[0]
    # short BTC-USD
    t2 = store.completed_trades.trades[1]
    # long ETH-USD
    t3 = store.completed_trades.trades[2]

    assert t1.symbol == 'BTC-USDT'
    assert t1.type == 'long'
    assert t1.entry_price == 121
    assert t1.exit_price == 131
    assert t1.qty == 1

    assert t2.symbol == 'BTC-USDT'
    assert t2.type == 'short'
    assert t2.entry_price == 151
    assert t2.exit_price == 161
    assert t2.qty == 1

    assert t3.symbol == 'ETH-USDT'
    assert t3.type == 'long'
    # because we open at 10, and increase at 20, entry is the mean which is 15
    assert t3.entry_price == 15
    # (50 + 70) / 2
    assert t3.exit_price == 60
    assert t3.qty == 2


def test_on_route_open_position():
    two_routes_backtest('Test21', 'Test22')

    t1 = store.completed_trades.trades[0]
    t2 = store.completed_trades.trades[1]

    assert t1.symbol == 'BTC-USDT'
    assert t1.type == 'long'
    assert t1.entry_price == 101
    assert t1.exit_price == 110
    assert t1.qty == 1

    assert t2.symbol == 'ETH-USDT'
    assert t2.type == 'long'
    assert t2.entry_price == 10
    assert t2.exit_price == 20
    assert t2.qty == 1


def test_on_route_stop_loss():
    two_routes_backtest('Test25', 'Test26')

    t1 = store.completed_trades.trades[0]
    t2 = store.completed_trades.trades[1]

    assert t2.symbol == 'BTC-USDT'
    assert t2.type == 'long'
    assert t2.entry_price == 101
    assert t2.exit_price == 120
    assert t2.qty == 1

    assert t1.symbol == 'ETH-USDT'
    assert t1.type == 'short'
    assert t1.entry_price == 10
    assert t1.exit_price == 20
    assert t1.qty == 1


def test_on_route_take_profit():
    two_routes_backtest('Test23', 'Test24')

    t1 = store.completed_trades.trades[0]
    t2 = store.completed_trades.trades[1]

    assert t2.symbol == 'BTC-USDT'
    assert t2.type == 'long'
    assert t2.entry_price == 101
    assert t2.exit_price == 120
    assert t2.qty == 1

    assert t1.symbol == 'ETH-USDT'
    assert t1.type == 'long'
    assert t1.entry_price == 10
    assert t1.exit_price == 20
    assert t1.qty == 1


def test_opening_position_in_multiple_points():
    single_route_backtest('Test15')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == (7 + 9 + 11) / 3
    assert t1.exit_price == 15
    assert t1.qty == 1.5
    assert t1.fee == 0


def test_reducing_position_size_after_opening():
    single_route_backtest('Test17')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == (15 + 10) / 2
    assert t1.qty == 2
    assert t1.fee == 0


def test_shared_vars():
    two_routes_backtest('Test32', 'Test33')

    t1 = store.completed_trades.trades[0]

    assert t1.symbol == 'ETH-USDT'
    assert t1.type == 'long'
    assert t1.entry_price == 11
    assert t1.exit_price == 21
    assert t1.qty == 1


def test_should_buy_and_execute_buy():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test01'},
    ]

    candles = {}
    for r in routes:
        key = jh.key(r['exchange'], r['symbol'])
        candles[key] = {
            'exchange': r['exchange'],
            'symbol': r['symbol'],
            'candles': range_candles((5 * 3) * 20)
        }

    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    for r in router.routes:
        s: Strategy = r.strategy
        p = s.position

        assert p.is_close is True
        assert len(s.trades[0].orders) == 2
        o: Order = s.trades[0].orders[0]
        short_candles = store.candles.get_candles(r.exchange, r.symbol, '1m')
        assert o.price == short_candles[4][2]
        assert o.price == s.candles[0][2]
        assert o.created_at == short_candles[4][0] + 60_000
        assert o.is_executed is True
        assert s.trade is None
        trade: ClosedTrade = store.completed_trades.trades[0]
        assert trade.type == 'long'
        # must include executed orders, in this case it's entry and take_profit
        assert len(trade.orders) == 2
        assert trade.orders[0].side == 'buy'
        assert trade.orders[0].type == 'MARKET'
        assert trade.orders[1].side == 'sell'
        assert trade.orders[1].type == 'LIMIT'
        assert len(store.completed_trades.trades) == 1


def test_should_sell_and_execute_sell():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test02'},
    ]

    candles = {}
    for r in routes:
        key = jh.key(r['exchange'], r['symbol'])
        candles[key] = {
            'exchange': r['exchange'],
            'symbol': r['symbol'],
            'candles': range_candles((5 * 3) * 20)
        }

    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    for r in router.routes:
        s: Strategy = r.strategy
        p = s.position

        assert p.is_close is True
        orders = s.trades[-1].orders
        assert len(orders) == 2
        o: Order = orders[0]
        short_candles = store.candles.get_candles(r.exchange, r.symbol, '1m')
        assert o.price == short_candles[4][2]
        assert o.price == s.candles[0][2]
        assert o.created_at == short_candles[4][0] + 60_000
        assert o.is_executed is True
        assert s.trade is None
        assert len(store.completed_trades.trades) == 1
        assert store.completed_trades.trades[0].type == 'short'


def test_stop_loss_at_multiple_points():
    single_route_backtest('Test11')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'short'
    assert t1.entry_price == 3
    assert t1.exit_price == (6 + 5 + 4) / 3
    assert t1.qty == 1.5
    assert t1.fee == 0


def test_strategy_properties():
    two_routes_backtest('Test19', 'Test19')

    for r in router.routes:
        s: Strategy = r.strategy

        assert s.name == r.strategy_name
        assert s.symbol == r.symbol
        assert s.exchange == r.exchange
        assert s.timeframe == r.timeframe
        assert s.trade is None
        assert s._is_executing is False
        assert s._is_initiated is True
        np.testing.assert_equal(s.current_candle, store.candles.get_current_candle(r.exchange, r.symbol, r.timeframe))
        np.testing.assert_equal(s.candles, store.candles.get_candles(r.exchange, r.symbol, r.timeframe))
        assert s.position == selectors.get_position(r.exchange, r.symbol)
        assert s.orders == store.orders.get_orders(r.exchange, r.symbol)


def test_taking_profit_at_multiple_points():
    single_route_backtest('Test10')

    assert len(store.completed_trades.trades) == 1
    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 7
    assert t1.exit_price == (15 + 13 + 11) / 3
    assert t1.qty == 1.5
    assert t1.fee == 0
    assert t1.holding_period == 8 * 60


def test_terminate():
    """
    test that user can use terminate() method. in this unit test use it
    to close the open position.
    `"""
    single_route_backtest('Test41')

    # TODO
    # assert terminate() is actually executed by logging a
    # string init, and then checking for that log message
    # assert {'id': 2, 'message': 'executed terminate successfully', 'time': 1552315246171.0} in store.logs.info

    # assert inside strategies terminate() that we have indeed an open position

    # assert that Strategy's terminate() method closes the open position
    assert store.app.total_open_trades == 0
    assert store.app.total_open_pl == 0


def test_terminate_closes_trades_at_the_end_of_backtest():
    single_route_backtest('Test40')

    # assert that Strategy's _terminate() method closes the open position
    assert store.app.total_open_trades == 1
    assert store.app.total_open_pl == 97


def test_updating_stop_loss_and_take_profit_after_opening_the_position():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_1, 'strategy': 'Test07'}
    ]

    candles = {}
    key = jh.key(exchanges.SANDBOX, 'ETH-USDT')
    candles[key] = {
        'exchange': exchanges.SANDBOX,
        'symbol': 'ETH-USDT',
        'candles': test_candles_1
    }

    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    t1: ClosedTrade = store.completed_trades.trades[0]
    assert t1.type == 'long'
    assert t1.entry_price == 129.23
    assert t1.exit_price == 128.98
    assert t1.qty == 10.204
    assert t1.fee == 0
    assert t1.opened_at == 1547201100000 + 60000
    assert t1.closed_at == 1547201700000 + 60000
    assert t1.orders[0].type == order_types.MARKET

    t2: ClosedTrade = store.completed_trades.trades[1]
    assert t2.type == 'short'
    assert t2.entry_price == 128.01
    assert t2.exit_price == 127.66
    assert t2.qty == 10
    assert t2.fee == 0
    assert t2.opened_at == 1547203560000 + 60000
    assert t2.closed_at == 1547203680000 + 60000
    assert t2.orders[0].type == order_types.MARKET


def test_validation_for_equal_stop_loss_and_take_profit():
    with pytest.raises(Exception) as err:
        single_route_backtest('Test46')

    assert str(err.value).startswith('stop-loss and take-profit should not be exactly the same')


def test_increased_and_reduced_count():
    single_route_backtest('TestIncreasedAndReducedCount')


def test_before():
    single_route_backtest('TestBeforeMethod')


def test_after():
    single_route_backtest('TestAfterMethod')


def test_leverage_property():
    single_route_backtest('TestLeverageProperty1', is_futures_trading=False)

    single_route_backtest('TestLeverageProperty1', is_futures_trading=True, leverage=1)

    single_route_backtest('TestLeverageProperty2', is_futures_trading=True, leverage=2)


def test_reduce_only_market_orders():
    single_route_backtest('TestReduceOnlyMarketOrders', is_futures_trading=True, leverage=1)


def test_liquidation_in_isolated_mode_for_short_trades():
    single_route_backtest(
        'TestLiquidationInIsolatedModeForShortTrade', is_futures_trading=True, leverage=2,
        leverage_mode='isolated'
    )


def test_liquidation_in_isolated_mode_for_long_trades():
    single_route_backtest(
        'TestLiquidationInIsolatedModeForLongTrade', is_futures_trading=True, leverage=2,
        leverage_mode='isolated', trend='down'
    )


def test_mark_price():
    single_route_backtest(
        'TestMarkPrice', is_futures_trading=True,
    )


def test_log_method():
    single_route_backtest('TestLogMethodInStrategyClass')

    assert store.logs.info[1]['message'] == 'test info log'
    assert store.logs.errors[0]['message'] == 'test error log'


def test_using_market_order_for_low_price_difference():
    single_route_backtest('TestMarketOrderForLowPriceDifference')


def test_dna_method():
    single_route_backtest('TestDnaMethod')


def test_default_hyperparameters():
    single_route_backtest('TestDefaultHyperparameters')


def test_positions():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': '5m', 'strategy': 'TestPositions'},
        {'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': '5m', 'strategy': 'TestPositions'},
    ]

    candles = {}
    for r in routes:
        key = jh.key(r['exchange'], r['symbol'])
        candles[key] = {
            'exchange': r['exchange'],
            'symbol': r['symbol'],
            'candles': range_candles((5 * 3) * 20)
        }
    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    # assertions done in the TestPositions


def test_portfolio_value():
    set_up()

    routes = [
        {'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': '5m', 'strategy': 'TestPortfolioValue'},
        {'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': '5m', 'strategy': 'TestPortfolioValue'},
    ]

    candles = {}
    for r in routes:
        key = jh.key(r['exchange'], r['symbol'])
        candles[key] = {
            'exchange': r['exchange'],
            'symbol': r['symbol'],
            'candles': range_candles((5 * 3) * 20)
        }
    # run backtest (dates are fake just to pass)
    backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles)

    # assertions done in the TestPortfolioValue


def test_portfolio_value_includes_position_value_and_open_orders_value():
    # in futures mode
    single_route_backtest(
        'TestPortfolioValueIncludesPositionValueAndOpenOrdersValue',
        is_futures_trading=True,
    )
    # in the spot mode
    single_route_backtest(
        'TestPortfolioValueIncludesPositionValueAndOpenOrdersValue',
        is_futures_trading=False,
    )


def test_multiple_entry_orders_update_entry_long():
    single_route_backtest('TestMultipleEntryOrdersUpdateEntryLongPositions')


def test_multiple_entry_orders_update_entry_short():
    single_route_backtest('TestMultipleEntryOrdersUpdateEntryShortPositions')


def test_can_cancel_entry_orders_after_open_position():
    # long position - setting to []
    single_route_backtest('TestCanCancelEntryOrdersAfterOpenPositionLong1')
    # long position - setting to None
    single_route_backtest('TestCanCancelEntryOrdersAfterOpenPositionLong2')

    # short position - setting to []
    single_route_backtest('TestCanCancelEntryOrdersAfterOpenPositionShort1')
    # short position - setting to None
    single_route_backtest('TestCanCancelEntryOrdersAfterOpenPositionShort2')


def test_stop_loss_price_is_replaced_with_market_order():
    # long position
    single_route_backtest('TestStopLossPriceIsReplacedWithMarketOrderForBetterPriceLongPosition')
    # short position
    single_route_backtest('TestStopLossPriceIsReplacedWithMarketOrderForBetterPriceShortPosition')


def test_take_profit_price_is_replaced_with_market_order():
    # long position
    single_route_backtest('TestTakeProfitPriceIsReplacedWithMarketOrderWhenMoreConvenientLongPosition')
    # short position
    single_route_backtest('TestTakeProfitPriceIsReplacedWithMarketOrderWhenMoreConvenientShortPosition')


def test_can_run_without_shorting():
    single_route_backtest('TestCanRunWithoutShorting')


def test_entry_orders_and_exit_orders_properties():
    single_route_backtest('TestEntryOrdersAndExitOrdersProperties')


def test_exchange_type_property():
    # spot
    single_route_backtest('TestExchangeTypeProperty1', is_futures_trading=False)
    # futures
    single_route_backtest('TestExchangeTypeProperty2', is_futures_trading=True)


def test_on_cancel_method():
    single_route_backtest('TestOnCancelMethod')


def test_order_price_cannot_be_greater_than_zero():
    with pytest.raises(exceptions.InvalidStrategy):
        single_route_backtest('TestOrderPriceCannotBeGreaterThanZero')


def test_daily_balances_property():
    single_route_backtest('TestDailyBalancesProperty', candles_count=10*1440)


def test_capital_property_raises_not_implemented_error():
    with pytest.raises(NotImplementedError):
        single_route_backtest('TestCapitalPropertyRaisesNotImplementedError')


def test_strategy_variables_are_reset_before_opening_new_position():
    single_route_backtest(
        'TestStrategyVariablesAreResetBeforeOpeningNewPosition',
        is_futures_trading=False
    )


def test_can_open_a_new_position_immediately_after_closing_via_update_position():
    single_route_backtest('TestCanOpenANewPositionImmediatelyAfterClosingViaUpdatePosition')

