# pragma pylint: disable=missing-docstring
import json
import logging
import platform
import re
from copy import deepcopy
from datetime import UTC, datetime, timedelta
from pathlib import Path
from unittest.mock import MagicMock, Mock, PropertyMock

import numpy as np
import pandas as pd
import pytest
from xdist.scheduler.loadscope import LoadScopeScheduling

from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
from freqtrade.enums import CandleType, MarginMode, SignalDirection, TradingMode
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
from freqtrade.resolvers import ExchangeResolver
from freqtrade.system import set_mp_start_method
from freqtrade.util import dt_now, dt_ts
from freqtrade.worker import Worker
from tests.conftest_trades import (
    leverage_trade,
    mock_trade_1,
    mock_trade_2,
    mock_trade_3,
    mock_trade_4,
    mock_trade_5,
    mock_trade_6,
    short_trade,
)
from tests.conftest_trades_usdt import (
    mock_trade_usdt_1,
    mock_trade_usdt_2,
    mock_trade_usdt_3,
    mock_trade_usdt_4,
    mock_trade_usdt_5,
    mock_trade_usdt_6,
    mock_trade_usdt_7,
)


logging.getLogger("").setLevel(logging.INFO)


# Do not mask numpy errors as warnings that no one read, raise the exception
np.seterr(all="raise")

CURRENT_TEST_STRATEGY = "StrategyTestV3"
TRADE_SIDES = ("long", "short")
EXMS = "freqtrade.exchange.exchange.Exchange"


def pytest_addoption(parser):
    parser.addoption(
        "--longrun",
        action="store_true",
        dest="longrun",
        default=False,
        help="Enable long-run tests (ccxt compat)",
    )


def pytest_configure(config):
    config.addinivalue_line(
        "markers", "longrun: mark test that is running slowly and should not be run regularly"
    )
    if not config.option.longrun:
        config.option.markexpr = "not longrun"


class FixtureScheduler(LoadScopeScheduling):
    # Based on the suggestion in
    # https://github.com/pytest-dev/pytest-xdist/issues/18

    def _split_scope(self, nodeid):
        if "exchange_online" in nodeid:
            try:
                # Extract exchange ID from nodeid
                exchange_id = nodeid.split("[")[1].split("-")[0].rstrip("]")
                return exchange_id
            except Exception as e:
                print(e)
                pass

        return nodeid


def pytest_xdist_make_scheduler(config, log):
    return FixtureScheduler(config, log)


def log_has(line, logs):
    """Check if line is found on some caplog's message."""
    return any(line == message for message in logs.messages)


def log_has_when(line, logs, when):
    """Check if line is found in caplog's messages during a specified stage"""
    return any(line == message.message for message in logs.get_records(when))


def log_has_re(line, logs):
    """Check if line matches some caplog's message."""
    return any(re.match(line, message) for message in logs.messages)


def num_log_has(line, logs):
    """Check how many times line is found in caplog's messages."""
    return sum(line == message for message in logs.messages)


def num_log_has_re(line, logs):
    """Check how many times line matches caplog's messages."""
    return sum(bool(re.match(line, message)) for message in logs.messages)


def get_args(args):
    return Arguments(args).get_parsed_arg()


def generate_trades_history(n_rows, start_date: datetime | None = None, days=5):
    np.random.seed(42)
    if not start_date:
        start_date = datetime(2020, 1, 1, tzinfo=UTC)

        # Generate random data
    end_date = start_date + timedelta(days=days)
    _start_timestamp = start_date.timestamp()
    _end_timestamp = pd.to_datetime(end_date).timestamp()

    random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows)
    timestamp = pd.to_datetime(random_timestamps_in_seconds, unit="s")

    trade_id = [
        f"a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}" for _ in range(n_rows)
    ]

    side = np.random.choice(["buy", "sell"], n_rows)

    # Initial price and subsequent changes
    initial_price = 0.019626
    price_changes = np.random.normal(0, initial_price * 0.05, n_rows)
    price = np.cumsum(np.concatenate(([initial_price], price_changes)))[:n_rows]

    amount = np.random.uniform(0.011, 20, n_rows)
    cost = price * amount

    # Create DataFrame
    df = pd.DataFrame(
        {
            "timestamp": timestamp,
            "id": trade_id,
            "type": None,
            "side": side,
            "price": price,
            "amount": amount,
            "cost": cost,
        }
    )
    df["date"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
    df = df.sort_values("timestamp").reset_index(drop=True)
    assert list(df.columns) == [*constants.DEFAULT_TRADES_COLUMNS, "date"]
    return df


def generate_test_data(
    timeframe: str, size: int, start: str = "2020-07-05", random_seed=42, base=20
):
    np.random.seed(random_seed)

    base = np.random.normal(base, 2, size=size)
    if timeframe == "1y":
        date = pd.date_range(start, periods=size, freq="1YS", tz="UTC", unit="ms")
    elif timeframe == "1M":
        date = pd.date_range(start, periods=size, freq="1MS", tz="UTC", unit="ms")
    elif timeframe == "3M":
        date = pd.date_range(start, periods=size, freq="3MS", tz="UTC", unit="ms")
    elif timeframe == "1w" or timeframe == "7d":
        date = pd.date_range(start, periods=size, freq="1W-MON", tz="UTC", unit="ms")
    else:
        tf_mins = timeframe_to_minutes(timeframe)
        if tf_mins >= 1:
            date = pd.date_range(start, periods=size, freq=f"{tf_mins}min", tz="UTC", unit="ms")
        else:
            tf_secs = timeframe_to_seconds(timeframe)
            date = pd.date_range(start, periods=size, freq=f"{tf_secs}s", tz="UTC", unit="ms")
    df = pd.DataFrame(
        {
            "date": date,
            "open": base,
            "high": base + np.random.normal(2, 1, size=size),
            "low": base - np.random.normal(2, 1, size=size),
            "close": base + np.random.normal(0, 1, size=size),
            "volume": np.random.normal(200, size=size),
        }
    )
    df = df.dropna()
    return df


def generate_test_data_raw(timeframe: str, size: int, start: str = "2020-07-05", random_seed=42):
    """Generates data in the ohlcv format used by ccxt"""
    df = generate_test_data(timeframe, size, start, random_seed)
    df["date"] = df.loc[:, "date"].dt.as_unit("ms").astype("int64")
    return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns), strict=False))


# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
# TODO: This should be replaced with AsyncMock once support for python 3.7 is dropped.
def get_mock_coro(return_value=None, side_effect=None):
    async def mock_coro(*args, **kwargs):
        if side_effect:
            if isinstance(side_effect, list):
                effect = side_effect.pop(0)
            else:
                effect = side_effect
            if isinstance(effect, Exception):
                raise effect
            if callable(effect):
                return effect(*args, **kwargs)
            return effect
        else:
            return return_value

    return Mock(wraps=mock_coro)


def patched_configuration_load_config_file(mocker, config) -> None:
    mocker.patch(
        "freqtrade.configuration.load_config.load_config_file", lambda *args, **kwargs: config
    )


def patch_exchange(
    mocker, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True
) -> None:
    mocker.patch(f"{EXMS}.validate_config", MagicMock())
    mocker.patch(f"{EXMS}.validate_timeframes", MagicMock())
    mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
    mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
    mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
    mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=2))
    # Temporary patch ...
    mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")

    if mock_markets:
        mocker.patch(f"{EXMS}._load_async_markets", return_value={})
        if isinstance(mock_markets, bool):
            mock_markets = get_markets()
        mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=mock_markets))

    if mock_supported_modes:
        mocker.patch(
            f"freqtrade.exchange.{exchange}.{exchange.capitalize()}"
            "._supported_trading_mode_margin_pairs",
            PropertyMock(
                return_value=[
                    (TradingMode.SPOT, MarginMode.NONE),
                    (TradingMode.MARGIN, MarginMode.CROSS),
                    (TradingMode.MARGIN, MarginMode.ISOLATED),
                    (TradingMode.FUTURES, MarginMode.CROSS),
                    (TradingMode.FUTURES, MarginMode.ISOLATED),
                ]
            ),
        )

    if api_mock:
        mocker.patch(f"{EXMS}._init_ccxt", return_value=api_mock)
    else:
        mocker.patch(f"{EXMS}.get_fee", return_value=0.0025)
        mocker.patch(f"{EXMS}._init_ccxt", MagicMock())
        mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["5m", "15m", "1h", "1d"]))


def get_patched_exchange(
    mocker, config, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True
) -> Exchange:
    patch_exchange(mocker, api_mock, exchange, mock_markets, mock_supported_modes)
    config["exchange"]["name"] = exchange
    try:
        exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True)
    except ImportError:
        exchange = Exchange(config)
    return exchange


def patch_wallet(mocker, free=999.9) -> None:
    mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=free))


def patch_whitelist(mocker, conf) -> None:
    mocker.patch(
        "freqtrade.freqtradebot.FreqtradeBot._refresh_active_whitelist",
        MagicMock(return_value=conf["exchange"]["pair_whitelist"]),
    )


# Functions for recurrent object patching


def patch_freqtradebot(mocker, config) -> None:
    """
    This function patch _init_modules() to not call dependencies
    :param mocker: a Mocker object to apply patches
    :param config: Config to pass to the bot
    :return: None
    """
    mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
    patch_exchange(mocker)
    mocker.patch("freqtrade.freqtradebot.RPCManager._init", MagicMock())
    mocker.patch("freqtrade.freqtradebot.RPCManager.send_msg", MagicMock())
    patch_whitelist(mocker, config)
    mocker.patch("freqtrade.freqtradebot.ExternalMessageConsumer")
    mocker.patch("freqtrade.configuration.config_validation._validate_consumers")


def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
    """
    This function patches _init_modules() to not call dependencies
    :param mocker: a Mocker object to apply patches
    :param config: Config to pass to the bot
    :return: FreqtradeBot
    """
    patch_freqtradebot(mocker, config)
    return FreqtradeBot(config)


def get_patched_worker(mocker, config) -> Worker:
    """
    This function patches _init_modules() to not call dependencies
    :param mocker: a Mocker object to apply patches
    :param config: Config to pass to the bot
    :return: Worker
    """
    patch_freqtradebot(mocker, config)
    return Worker(args=None, config=config)


def patch_get_signal(
    freqtrade: FreqtradeBot,
    enter_long=True,
    exit_long=False,
    enter_short=False,
    exit_short=False,
    enter_tag: str | None = None,
    exit_tag: str | None = None,
) -> None:
    """
    :param mocker: mocker to patch IStrategy class
    :return: None
    """

    # returns (Signal-direction, signaname)
    def patched_get_entry_signal(*args, **kwargs):
        direction = None
        if enter_long and not any([exit_long, enter_short]):
            direction = SignalDirection.LONG
        if enter_short and not any([exit_short, enter_long]):
            direction = SignalDirection.SHORT

        return direction, enter_tag

    freqtrade.strategy.get_entry_signal = patched_get_entry_signal

    def patched_get_exit_signal(pair, timeframe, dataframe, is_short):
        if is_short:
            return enter_short, exit_short, exit_tag
        else:
            return enter_long, exit_long, exit_tag

    # returns (enter, exit)
    freqtrade.strategy.get_exit_signal = patched_get_exit_signal

    freqtrade.exchange.refresh_latest_ohlcv = lambda p: None


def create_mock_trades(fee, is_short: bool | None = False, use_db: bool = True):
    """
    Create some fake trades ...
    :param is_short: Optional bool, None creates a mix of long and short trades.
    """

    def add_trade(trade):
        if use_db:
            Trade.session.add(trade)
        else:
            LocalTrade.add_bt_trade(trade)

    is_short1 = is_short if is_short is not None else True
    is_short2 = is_short if is_short is not None else False
    # Simulate dry_run entries
    trade = mock_trade_1(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_2(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_3(fee, is_short2)
    add_trade(trade)

    trade = mock_trade_4(fee, is_short2)
    add_trade(trade)

    trade = mock_trade_5(fee, is_short2)
    add_trade(trade)

    trade = mock_trade_6(fee, is_short1)
    add_trade(trade)

    if use_db:
        Trade.commit()


def create_mock_trades_with_leverage(fee, use_db: bool = True):
    """
    Create some fake trades ...
    """
    if use_db:
        Trade.session.rollback()

    def add_trade(trade):
        if use_db:
            Trade.session.add(trade)
        else:
            LocalTrade.add_bt_trade(trade)

    # Simulate dry_run entries
    trade = mock_trade_1(fee, False)
    add_trade(trade)

    trade = mock_trade_2(fee, False)
    add_trade(trade)

    trade = mock_trade_3(fee, False)
    add_trade(trade)

    trade = mock_trade_4(fee, False)
    add_trade(trade)

    trade = mock_trade_5(fee, False)
    add_trade(trade)

    trade = mock_trade_6(fee, False)
    add_trade(trade)

    trade = short_trade(fee)
    add_trade(trade)

    trade = leverage_trade(fee)
    add_trade(trade)

    if use_db:
        Trade.session.flush()


def create_mock_trades_usdt(fee, is_short: bool | None = False, use_db: bool = True):
    """
    Create some fake trades ...
    """

    def add_trade(trade):
        if use_db:
            Trade.session.add(trade)
        else:
            LocalTrade.add_bt_trade(trade)

    is_short1 = is_short if is_short is not None else True
    is_short2 = is_short if is_short is not None else False

    # Simulate dry_run entries
    trade = mock_trade_usdt_1(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_usdt_2(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_usdt_3(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_usdt_4(fee, is_short2)
    add_trade(trade)

    trade = mock_trade_usdt_5(fee, is_short2)
    add_trade(trade)

    trade = mock_trade_usdt_6(fee, is_short1)
    add_trade(trade)

    trade = mock_trade_usdt_7(fee, is_short1)
    add_trade(trade)
    if use_db:
        Trade.commit()


@pytest.fixture(autouse=True)
def patch_gc(mocker) -> None:
    mocker.patch("freqtrade.main.gc_set_threshold")


@pytest.fixture(scope="session", autouse=True)
def fixture_set_mp_start_method():
    """
    Patch multiprocessing start mode globally
    Auto-used, runs once per session.
    """
    set_mp_start_method()


def is_arm(include_aarch64: bool = False) -> bool:
    machine = platform.machine()
    if include_aarch64:
        return "aarch64" in machine or "arm" in machine
    return "arm" in machine


def is_mac() -> bool:
    machine = platform.system()
    return "Darwin" in machine


@pytest.fixture(autouse=True)
def patch_torch_initlogs(mocker) -> None:
    if is_mac():
        # Mock torch import completely
        import sys
        import types

        module_name = "torch"
        mocked_module = types.ModuleType(module_name)
        sys.modules[module_name] = mocked_module
    else:
        try:
            mocker.patch("torch._logging._init_logs")
        except ModuleNotFoundError:
            # Allow running limited tests to run without freqAI dependencies
            pass


@pytest.fixture(autouse=True)
def user_dir(mocker, tmp_path) -> Path:
    user_dir = tmp_path / "user_data"
    mocker.patch("freqtrade.configuration.configuration.create_userdata_dir", return_value=user_dir)
    return user_dir


@pytest.fixture()
def keep_log_config_loggers(mocker):
    # Mock the _handle_existing_loggers function to prevent it from disabling all loggers.
    # This is necessary to keep all loggers active, and avoid random failures if
    # this file is ran before the test_rest_client file.
    mocker.patch("logging.config._handle_existing_loggers")


@pytest.fixture(autouse=True)
def patch_coingecko(mocker) -> None:
    """
    Mocker to coingecko to speed up tests
    :param mocker: mocker to patch coingecko class
    :return: None
    """

    tickermock = MagicMock(return_value={"bitcoin": {"usd": 12345.0}, "ethereum": {"usd": 12345.0}})
    listmock = MagicMock(
        return_value=[
            {"id": "bitcoin", "name": "Bitcoin", "symbol": "btc", "website_slug": "bitcoin"},
            {"id": "ethereum", "name": "Ethereum", "symbol": "eth", "website_slug": "ethereum"},
        ]
    )
    mocker.patch.multiple(
        "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
        get_price=tickermock,
        get_coins_list=listmock,
    )


@pytest.fixture(scope="function")
def init_persistence(default_conf):
    init_db(default_conf["db_url"])


@pytest.fixture(scope="function")
def default_conf(testdatadir):
    return get_default_conf(testdatadir)


@pytest.fixture(scope="function")
def default_conf_usdt(testdatadir):
    return get_default_conf_usdt(testdatadir)


def get_default_conf(testdatadir):
    """Returns validated configuration suitable for most tests"""
    configuration = {
        "max_open_trades": 1,
        "stake_currency": "BTC",
        "stake_amount": 0.001,
        "fiat_display_currency": "USD",
        "timeframe": "5m",
        "dry_run": True,
        "cancel_open_orders_on_exit": False,
        "minimal_roi": {"40": 0.0, "30": 0.01, "20": 0.02, "0": 0.04},
        "dry_run_wallet": 1000,
        "tradable_balance_ratio": 0.99,
        "stoploss": -0.10,
        "unfilledtimeout": {"entry": 10, "exit": 30},
        "entry_pricing": {
            "price_last_balance": 0.0,
            "use_order_book": False,
            "order_book_top": 1,
            "check_depth_of_market": {"enabled": False, "bids_to_ask_delta": 1},
        },
        "exit_pricing": {
            "use_order_book": False,
            "order_book_top": 1,
        },
        "exchange": {
            "name": "binance",
            "key": "key",
            "enable_ws": False,
            "secret": "secret",
            "pair_whitelist": ["ETH/BTC", "LTC/BTC", "XRP/BTC", "NEO/BTC"],
            "pair_blacklist": [
                "DOGE/BTC",
                "HOT/BTC",
            ],
        },
        "pairlists": [{"method": "StaticPairList"}],
        "telegram": {
            "enabled": False,
            "token": "token",
            "chat_id": "1235",
            "notification_settings": {},
        },
        "datadir": Path(testdatadir),
        "initial_state": "running",
        "db_url": "sqlite://",
        "user_data_dir": Path("user_data"),
        "verbosity": 3,
        "strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
        "strategy": CURRENT_TEST_STRATEGY,
        "disableparamexport": True,
        "internals": {},
        "export": "none",
        "dataformat_ohlcv": "feather",
        "dataformat_trades": "feather",
        "runmode": "dry_run",
        "trading_mode": "spot",
        "margin_mode": "",
        "candle_type_def": CandleType.SPOT,
        "original_config": {},
    }
    return configuration


def get_default_conf_usdt(testdatadir):
    configuration = get_default_conf(testdatadir)
    configuration.update(
        {
            "stake_amount": 60.0,
            "stake_currency": "USDT",
            "exchange": {
                "name": "binance",
                "enabled": True,
                "key": "key",
                "enable_ws": False,
                "secret": "secret",
                "pair_whitelist": [
                    "ETH/USDT",
                    "LTC/USDT",
                    "XRP/USDT",
                    "NEO/USDT",
                    "TKN/USDT",
                ],
                "pair_blacklist": [
                    "DOGE/USDT",
                    "HOT/USDT",
                ],
            },
        }
    )
    return configuration


@pytest.fixture
def fee():
    return MagicMock(return_value=0.0025)


@pytest.fixture
def ticker():
    return MagicMock(
        return_value={
            "bid": 0.00001098,
            "ask": 0.00001099,
            "last": 0.00001098,
        }
    )


@pytest.fixture
def ticker_sell_up():
    return MagicMock(
        return_value={
            "bid": 0.00001172,
            "ask": 0.00001173,
            "last": 0.00001172,
        }
    )


@pytest.fixture
def ticker_sell_down():
    return MagicMock(
        return_value={
            "bid": 0.00001044,
            "ask": 0.00001043,
            "last": 0.00001044,
        }
    )


@pytest.fixture
def ticker_usdt():
    return MagicMock(
        return_value={
            "bid": 2.0,
            "ask": 2.02,
            "last": 2.0,
        }
    )


@pytest.fixture
def ticker_usdt_sell_up():
    return MagicMock(
        return_value={
            "bid": 2.2,
            "ask": 2.3,
            "last": 2.2,
        }
    )


@pytest.fixture
def ticker_usdt_sell_down():
    return MagicMock(
        return_value={
            "bid": 2.01,
            "ask": 2.0,
            "last": 2.01,
        }
    )


@pytest.fixture
def markets():
    return get_markets()


def get_markets():
    # See get_markets_static() for immutable markets and do not modify them unless absolutely
    # necessary!
    return {
        "ETH/BTC": {
            "id": "ethbtc",
            "symbol": "ETH/BTC",
            "base": "ETH",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {"min": 1.0, "max": 2.0},
            },
        },
        "TKN/BTC": {
            "id": "tknbtc",
            "symbol": "TKN/BTC",
            "base": "TKN",
            "quote": "BTC",
            # According to ccxt, markets without active item set are also active
            # 'active': True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {"min": 1.0, "max": 5.0},
            },
        },
        "BLK/BTC": {
            "id": "blkbtc",
            "symbol": "BLK/BTC",
            "base": "BLK",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 1000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {"min": 1.0, "max": 3.0},
            },
        },
        "LTC/BTC": {
            "id": "ltcbtc",
            "symbol": "LTC/BTC",
            "base": "LTC",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {"min": None, "max": None},
            },
            "info": {},
        },
        "XRP/BTC": {
            "id": "xrpbtc",
            "symbol": "XRP/BTC",
            "base": "XRP",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "NEO/BTC": {
            "id": "neobtc",
            "symbol": "NEO/BTC",
            "base": "NEO",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "BTT/BTC": {
            "id": "BTTBTC",
            "symbol": "BTT/BTC",
            "base": "BTT",
            "quote": "BTC",
            "active": False,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "contractSize": None,
            "precision": {"base": 8, "quote": 8, "amount": 0, "price": 8},
            "limits": {
                "amount": {"min": 1.0, "max": 90000000.0},
                "price": {"min": None, "max": None},
                "cost": {"min": 0.0001, "max": None},
                "leverage": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "ETC/BTC": {
            "id": "ETCBTC",
            "symbol": "ETC/BTC",
            "base": "ETC",
            "quote": "BTC",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "contractSize": None,
            "precision": {"base": 8, "quote": 8, "amount": 2, "price": 7},
            "limits": {
                "amount": {"min": 0.01, "max": 90000000.0},
                "price": {"min": 1e-07, "max": 1000.0},
                "cost": {"min": 0.0001, "max": 9000000.0},
                "leverage": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "ETH/USDT": {
            "id": "USDT-ETH",
            "symbol": "ETH/USDT",
            "base": "ETH",
            "quote": "USDT",
            "settle": None,
            "baseId": "ETH",
            "quoteId": "USDT",
            "settleId": None,
            "type": "spot",
            "spot": True,
            "margin": True,
            "swap": True,
            "future": True,
            "option": False,
            "active": True,
            "contract": None,
            "linear": None,
            "inverse": None,
            "taker": 0.0006,
            "maker": 0.0002,
            "contractSize": None,
            "expiry": None,
            "expiryDateTime": None,
            "strike": None,
            "optionType": None,
            "precision": {
                "amount": 8,
                "price": 8,
            },
            "limits": {
                "leverage": {
                    "min": 1,
                    "max": 100,
                },
                "amount": {
                    "min": 0.02214286,
                    "max": None,
                },
                "price": {
                    "min": 1e-08,
                    "max": None,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {
                "maintenance_rate": "0.005",
            },
        },
        "BTC/USDT": {
            "id": "USDT-BTC",
            "symbol": "BTC/USDT",
            "base": "BTC",
            "quote": "USDT",
            "settle": None,
            "baseId": "BTC",
            "quoteId": "USDT",
            "settleId": None,
            "type": "spot",
            "spot": True,
            "margin": True,
            "swap": False,
            "future": False,
            "option": False,
            "active": True,
            "contract": None,
            "linear": None,
            "inverse": None,
            "taker": 0.0006,
            "maker": 0.0002,
            "contractSize": None,
            "expiry": None,
            "expiryDateTime": None,
            "strike": None,
            "optionType": None,
            "precision": {
                "amount": 4,
                "price": 4,
            },
            "limits": {
                "leverage": {
                    "min": 1,
                    "max": 100,
                },
                "amount": {
                    "min": 0.000221,
                    "max": None,
                },
                "price": {
                    "min": 1e-02,
                    "max": None,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {
                "maintenance_rate": "0.005",
            },
        },
        "LTC/USDT": {
            "id": "USDT-LTC",
            "symbol": "LTC/USDT",
            "base": "LTC",
            "quote": "USDT",
            "active": False,
            "spot": True,
            "future": True,
            "swap": True,
            "margin": True,
            "linear": None,
            "inverse": False,
            "type": "spot",
            "contractSize": None,
            "taker": 0.0006,
            "maker": 0.0002,
            "precision": {"amount": 8, "price": 8},
            "limits": {
                "amount": {"min": 0.06646786, "max": None},
                "price": {"min": 1e-08, "max": None},
                "leverage": {
                    "min": None,
                    "max": None,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "XRP/USDT": {
            "id": "xrpusdt",
            "symbol": "XRP/USDT",
            "base": "XRP",
            "quote": "USDT",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "taker": 0.0006,
            "maker": 0.0002,
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "contractSize": None,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 1000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
            },
            "info": {},
        },
        "NEO/USDT": {
            "id": "neousdt",
            "symbol": "NEO/USDT",
            "base": "NEO",
            "quote": "USDT",
            "settle": "",
            "baseId": "NEO",
            "quoteId": "USDT",
            "settleId": "",
            "type": "spot",
            "spot": True,
            "margin": True,
            "swap": False,
            "futures": False,
            "option": False,
            "active": True,
            "contract": False,
            "linear": None,
            "inverse": None,
            "taker": 0.0006,
            "maker": 0.0002,
            "contractSize": None,
            "expiry": None,
            "expiryDatetime": None,
            "strike": None,
            "optionType": None,
            "tierBased": None,
            "percentage": None,
            "lot": 0.00000001,
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "limits": {
                "leverage": {"min": 1, "max": 10},
                "amount": {
                    "min": 0.01,
                    "max": 1000,
                },
                "price": {
                    "min": None,
                    "max": 500000,
                },
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
            },
            "info": {},
        },
        "TKN/USDT": {
            "id": "tknusdt",
            "symbol": "TKN/USDT",
            "base": "TKN",
            "quote": "USDT",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "contractSize": None,
            "taker": 0.0006,
            "maker": 0.0002,
            "precision": {
                "price": 8,
                "amount": 8,
                "cost": 8,
            },
            "lot": 0.00000001,
            "limits": {
                "amount": {
                    "min": 0.01,
                    "max": 100000000000,
                },
                "price": {"min": None, "max": 500000},
                "cost": {
                    "min": 0.0001,
                    "max": 500000,
                },
                "leverage": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "LTC/USD": {
            "id": "USD-LTC",
            "symbol": "LTC/USD",
            "base": "LTC",
            "quote": "USD",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "contractSize": None,
            "precision": {"amount": 8, "price": 8},
            "limits": {
                "amount": {"min": 0.06646786, "max": None},
                "price": {"min": 1e-08, "max": None},
                "leverage": {
                    "min": None,
                    "max": None,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "XLTCUSDT": {
            "id": "xLTCUSDT",
            "symbol": "XLTCUSDT",
            "base": "LTC",
            "quote": "USDT",
            "active": True,
            "spot": False,
            "type": "swap",
            "contractSize": 0.01,
            "swap": False,
            "linear": False,
            "taker": 0.0006,
            "maker": 0.0002,
            "precision": {"amount": 8, "price": 8},
            "limits": {
                "leverage": {
                    "min": None,
                    "max": None,
                },
                "amount": {"min": 0.06646786, "max": None},
                "price": {"min": 1e-08, "max": None},
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "info": {},
        },
        "LTC/ETH": {
            "id": "LTCETH",
            "symbol": "LTC/ETH",
            "base": "LTC",
            "quote": "ETH",
            "active": True,
            "spot": True,
            "swap": False,
            "linear": None,
            "type": "spot",
            "contractSize": None,
            "precision": {"base": 8, "quote": 8, "amount": 3, "price": 5},
            "limits": {
                "leverage": {
                    "min": None,
                    "max": None,
                },
                "amount": {"min": 0.001, "max": 10000000.0},
                "price": {"min": 1e-05, "max": 1000.0},
                "cost": {"min": 0.01, "max": None},
            },
            "info": {},
        },
        "ETH/USDT:USDT": {
            "id": "ETH_USDT",
            "symbol": "ETH/USDT:USDT",
            "base": "ETH",
            "quote": "USDT",
            "settle": "USDT",
            "baseId": "ETH",
            "quoteId": "USDT",
            "settleId": "USDT",
            "type": "swap",
            "spot": False,
            "margin": False,
            "swap": True,
            "future": True,  # Binance mode ...
            "option": False,
            "contract": True,
            "linear": True,
            "inverse": False,
            "tierBased": False,
            "percentage": True,
            "taker": 0.0006,
            "maker": 0.0002,
            "contractSize": 10,
            "active": True,
            "expiry": None,
            "expiryDatetime": None,
            "strike": None,
            "optionType": None,
            "limits": {
                "leverage": {"min": 1, "max": 100},
                "amount": {"min": 1, "max": 300000},
                "price": {
                    "min": None,
                    "max": None,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "precision": {"price": 0.05, "amount": 1},
            "info": {},
        },
        "ADA/USDT:USDT": {
            "limits": {
                "leverage": {
                    "min": 1,
                    "max": 20,
                },
                "amount": {
                    "min": 1,
                    "max": 1000000,
                },
                "price": {
                    "min": 0.52981,
                    "max": 1.58943,
                },
                "cost": {
                    "min": None,
                    "max": None,
                },
            },
            "precision": {"amount": 1, "price": 0.00001},
            "tierBased": True,
            "percentage": True,
            "taker": 0.0000075,
            "maker": -0.0000025,
            "feeSide": "get",
            "tiers": {
                "maker": [
                    [0, 0.002],
                    [1.5, 0.00185],
                    [3, 0.00175],
                    [6, 0.00165],
                    [12.5, 0.00155],
                    [25, 0.00145],
                    [75, 0.00135],
                    [200, 0.00125],
                    [500, 0.00115],
                    [1250, 0.00105],
                    [2500, 0.00095],
                    [3000, 0.00085],
                    [6000, 0.00075],
                    [11000, 0.00065],
                    [20000, 0.00055],
                    [40000, 0.00055],
                    [75000, 0.00055],
                ],
                "taker": [
                    [0, 0.002],
                    [1.5, 0.00195],
                    [3, 0.00185],
                    [6, 0.00175],
                    [12.5, 0.00165],
                    [25, 0.00155],
                    [75, 0.00145],
                    [200, 0.00135],
                    [500, 0.00125],
                    [1250, 0.00115],
                    [2500, 0.00105],
                    [3000, 0.00095],
                    [6000, 0.00085],
                    [11000, 0.00075],
                    [20000, 0.00065],
                    [40000, 0.00065],
                    [75000, 0.00065],
                ],
            },
            "id": "ADA_USDT",
            "symbol": "ADA/USDT:USDT",
            "base": "ADA",
            "quote": "USDT",
            "settle": "USDT",
            "baseId": "ADA",
            "quoteId": "USDT",
            "settleId": "usdt",
            "type": "swap",
            "spot": False,
            "margin": False,
            "swap": True,
            "future": True,  # Binance mode ...
            "option": False,
            "active": True,
            "contract": True,
            "linear": True,
            "inverse": False,
            "contractSize": 0.01,
            "expiry": None,
            "expiryDatetime": None,
            "strike": None,
            "optionType": None,
            "info": {},
        },
        "SOL/BUSD:BUSD": {
            "limits": {
                "leverage": {"min": None, "max": None},
                "amount": {"min": 1, "max": 1000000},
                "price": {"min": 0.04, "max": 100000},
                "cost": {"min": 5, "max": None},
                "market": {"min": 1, "max": 1500},
            },
            "precision": {"amount": 0, "price": 2, "base": 8, "quote": 8},
            "tierBased": False,
            "percentage": True,
            "taker": 0.0004,
            "maker": 0.0002,
            "feeSide": "get",
            "id": "SOLBUSD",
            "lowercaseId": "solbusd",
            "symbol": "SOL/BUSD",
            "base": "SOL",
            "quote": "BUSD",
            "settle": "BUSD",
            "baseId": "SOL",
            "quoteId": "BUSD",
            "settleId": "BUSD",
            "type": "future",
            "spot": False,
            "margin": False,
            "future": True,
            "delivery": False,
            "option": False,
            "active": True,
            "contract": True,
            "linear": True,
            "inverse": False,
            "contractSize": 1,
            "expiry": None,
            "expiryDatetime": None,
            "strike": None,
            "optionType": None,
            "info": {
                "symbol": "SOLBUSD",
                "pair": "SOLBUSD",
                "contractType": "PERPETUAL",
                "deliveryDate": "4133404800000",
                "onboardDate": "1630566000000",
                "status": "TRADING",
                "maintMarginPercent": "2.5000",
                "requiredMarginPercent": "5.0000",
                "baseAsset": "SOL",
                "quoteAsset": "BUSD",
                "marginAsset": "BUSD",
                "pricePrecision": "4",
                "quantityPrecision": "0",
                "baseAssetPrecision": "8",
                "quotePrecision": "8",
                "underlyingType": "COIN",
                "underlyingSubType": [],
                "settlePlan": "0",
                "triggerProtect": "0.0500",
                "liquidationFee": "0.005000",
                "marketTakeBound": "0.05",
                "filters": [
                    {
                        "minPrice": "0.0400",
                        "maxPrice": "100000",
                        "filterType": "PRICE_FILTER",
                        "tickSize": "0.0100",
                    },
                    {"stepSize": "1", "filterType": "LOT_SIZE", "maxQty": "1000000", "minQty": "1"},
                    {
                        "stepSize": "1",
                        "filterType": "MARKET_LOT_SIZE",
                        "maxQty": "1500",
                        "minQty": "1",
                    },
                    {"limit": "200", "filterType": "MAX_NUM_ORDERS"},
                    {"limit": "10", "filterType": "MAX_NUM_ALGO_ORDERS"},
                    {"notional": "5", "filterType": "MIN_NOTIONAL"},
                    {
                        "multiplierDown": "0.9500",
                        "multiplierUp": "1.0500",
                        "multiplierDecimal": "4",
                        "filterType": "PERCENT_PRICE",
                    },
                ],
                "orderTypes": [
                    "LIMIT",
                    "MARKET",
                    "STOP",
                    "STOP_MARKET",
                    "TAKE_PROFIT",
                    "TAKE_PROFIT_MARKET",
                    "TRAILING_STOP_MARKET",
                ],
                "timeInForce": ["GTC", "IOC", "FOK", "GTX"],
            },
        },
    }


@pytest.fixture
def markets_static():
    # These markets are used in some tests that would need adaptation should anything change in
    # market list. Do not modify this list without a good reason! Do not modify market parameters
    # of listed pairs in get_markets() without a good reason either!
    static_markets = [
        "BLK/BTC",
        "BTT/BTC",
        "ETH/BTC",
        "ETH/USDT",
        "LTC/BTC",
        "LTC/ETH",
        "LTC/USD",
        "LTC/USDT",
        "NEO/BTC",
        "TKN/BTC",
        "XLTCUSDT",
        "XRP/BTC",
        "ADA/USDT:USDT",
        "ETH/USDT:USDT",
    ]
    all_markets = get_markets()
    return {m: all_markets[m] for m in static_markets}


@pytest.fixture
def shitcoinmarkets(markets_static):
    """
    Fixture with shitcoin markets - used to test filters in pairlists
    """
    shitmarkets = deepcopy(markets_static)
    shitmarkets.update(
        {
            "HOT/BTC": {
                "id": "HOTBTC",
                "symbol": "HOT/BTC",
                "base": "HOT",
                "quote": "BTC",
                "active": True,
                "spot": True,
                "type": "spot",
                "precision": {"base": 8, "quote": 8, "amount": 0, "price": 8},
                "limits": {
                    "amount": {"min": 1.0, "max": 90000000.0},
                    "price": {"min": None, "max": None},
                    "cost": {"min": 0.001, "max": None},
                },
                "info": {},
            },
            "FUEL/BTC": {
                "id": "FUELBTC",
                "symbol": "FUEL/BTC",
                "base": "FUEL",
                "quote": "BTC",
                "active": True,
                "spot": True,
                "type": "spot",
                "precision": {"base": 8, "quote": 8, "amount": 0, "price": 8},
                "limits": {
                    "amount": {"min": 1.0, "max": 90000000.0},
                    "price": {"min": 1e-08, "max": 1000.0},
                    "cost": {"min": 0.001, "max": None},
                },
                "info": {},
            },
            "NANO/USDT": {
                "percentage": True,
                "tierBased": False,
                "taker": 0.001,
                "maker": 0.001,
                "precision": {"base": 8, "quote": 8, "amount": 2, "price": 4},
                "limits": {
                    "leverage": {
                        "min": None,
                        "max": None,
                    },
                    "amount": {
                        "min": None,
                        "max": None,
                    },
                    "price": {
                        "min": None,
                        "max": None,
                    },
                    "cost": {
                        "min": None,
                        "max": None,
                    },
                },
                "id": "NANOUSDT",
                "symbol": "NANO/USDT",
                "base": "NANO",
                "quote": "USDT",
                "baseId": "NANO",
                "quoteId": "USDT",
                "info": {},
                "type": "spot",
                "spot": True,
                "future": False,
                "active": True,
            },
            "ADAHALF/USDT": {
                "percentage": True,
                "tierBased": False,
                "taker": 0.001,
                "maker": 0.001,
                "precision": {"base": 8, "quote": 8, "amount": 2, "price": 4},
                "limits": {
                    "leverage": {
                        "min": None,
                        "max": None,
                    },
                    "amount": {
                        "min": None,
                        "max": None,
                    },
                    "price": {
                        "min": None,
                        "max": None,
                    },
                    "cost": {
                        "min": None,
                        "max": None,
                    },
                },
                "id": "ADAHALFUSDT",
                "symbol": "ADAHALF/USDT",
                "base": "ADAHALF",
                "quote": "USDT",
                "baseId": "ADAHALF",
                "quoteId": "USDT",
                "info": {},
                "type": "spot",
                "spot": True,
                "future": False,
                "active": True,
            },
            "ADADOUBLE/USDT": {
                "percentage": True,
                "tierBased": False,
                "taker": 0.001,
                "maker": 0.001,
                "precision": {"base": 8, "quote": 8, "amount": 2, "price": 4},
                "limits": {
                    "leverage": {
                        "min": None,
                        "max": None,
                    },
                    "amount": {
                        "min": None,
                        "max": None,
                    },
                    "price": {
                        "min": None,
                        "max": None,
                    },
                    "cost": {
                        "min": None,
                        "max": None,
                    },
                },
                "id": "ADADOUBLEUSDT",
                "symbol": "ADADOUBLE/USDT",
                "base": "ADADOUBLE",
                "quote": "USDT",
                "baseId": "ADADOUBLE",
                "quoteId": "USDT",
                "info": {},
                "type": "spot",
                "spot": True,
                "future": False,
                "active": True,
            },
        }
    )
    return shitmarkets


@pytest.fixture
def markets_empty():
    return MagicMock(return_value=[])


@pytest.fixture(scope="function")
def limit_buy_order_open():
    return {
        "id": "mocked_limit_buy",
        "type": "limit",
        "side": "buy",
        "symbol": "mocked",
        "timestamp": dt_ts(),
        "datetime": dt_now().isoformat(),
        "price": 0.00001099,
        "average": 0.00001099,
        "amount": 90.99181073,
        "filled": 0.0,
        "cost": 0.0009999,
        "remaining": 90.99181073,
        "status": "open",
    }


@pytest.fixture
def limit_buy_order_old():
    return {
        "id": "mocked_limit_buy_old",
        "type": "limit",
        "side": "buy",
        "symbol": "mocked",
        "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
        "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
        "price": 0.00001099,
        "amount": 90.99181073,
        "filled": 0.0,
        "remaining": 90.99181073,
        "status": "open",
    }


@pytest.fixture
def limit_sell_order_old():
    return {
        "id": "mocked_limit_sell_old",
        "type": "limit",
        "side": "sell",
        "symbol": "ETH/BTC",
        "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
        "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
        "price": 0.00001099,
        "amount": 90.99181073,
        "filled": 0.0,
        "remaining": 90.99181073,
        "status": "open",
    }


@pytest.fixture
def limit_buy_order_old_partial():
    return {
        "id": "mocked_limit_buy_old_partial",
        "type": "limit",
        "side": "buy",
        "symbol": "ETH/BTC",
        "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
        "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
        "price": 0.00001099,
        "amount": 90.99181073,
        "filled": 23.0,
        "cost": 90.99181073 * 23.0,
        "remaining": 67.99181073,
        "status": "open",
    }


@pytest.fixture
def limit_buy_order_old_partial_canceled(limit_buy_order_old_partial):
    res = deepcopy(limit_buy_order_old_partial)
    res["status"] = "canceled"
    res["fee"] = {"cost": 0.023, "currency": "ETH"}
    return res


@pytest.fixture(scope="function")
def limit_buy_order_canceled_empty(request):
    # Indirect fixture
    # Documentation:
    # https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments

    exchange_name = request.param
    if exchange_name == "kraken":
        return {
            "info": {},
            "id": "AZNPFF-4AC4N-7MKTAT",
            "clientOrderId": None,
            "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
            "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
            "lastTradeTimestamp": None,
            "status": "canceled",
            "symbol": "LTC/USDT",
            "type": "limit",
            "side": "buy",
            "price": 34.3225,
            "cost": 0.0,
            "amount": 0.55,
            "filled": 0.0,
            "average": 0.0,
            "remaining": 0.55,
            "fee": {"cost": 0.0, "rate": None, "currency": "USDT"},
            "trades": [],
        }
    elif exchange_name == "binance":
        return {
            "info": {},
            "id": "1234512345",
            "clientOrderId": "alb1234123",
            "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
            "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
            "lastTradeTimestamp": None,
            "symbol": "LTC/USDT",
            "type": "limit",
            "side": "buy",
            "price": 0.016804,
            "amount": 0.55,
            "cost": 0.0,
            "average": None,
            "filled": 0.0,
            "remaining": 0.55,
            "status": "canceled",
            "fee": None,
            "trades": None,
        }
    else:
        return {
            "info": {},
            "id": "1234512345",
            "clientOrderId": "alb1234123",
            "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
            "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
            "lastTradeTimestamp": None,
            "symbol": "LTC/USDT",
            "type": "limit",
            "side": "buy",
            "price": 0.016804,
            "amount": 0.55,
            "cost": 0.0,
            "average": None,
            "filled": 0.0,
            "remaining": 0.55,
            "status": "canceled",
            "fee": None,
            "trades": None,
        }


@pytest.fixture
def limit_sell_order_open():
    return {
        "id": "mocked_limit_sell",
        "type": "limit",
        "side": "sell",
        "symbol": "mocked",
        "datetime": dt_now().isoformat(),
        "timestamp": dt_ts(),
        "price": 0.00001173,
        "amount": 90.99181073,
        "filled": 0.0,
        "remaining": 90.99181073,
        "status": "open",
    }


@pytest.fixture
def limit_sell_order(limit_sell_order_open):
    order = deepcopy(limit_sell_order_open)
    order["remaining"] = 0.0
    order["filled"] = order["amount"]
    order["status"] = "closed"
    return order


@pytest.fixture
def order_book_l2():
    return MagicMock(
        return_value={
            "bids": [
                [0.043936, 10.442],
                [0.043935, 31.865],
                [0.043933, 11.212],
                [0.043928, 0.088],
                [0.043925, 10.0],
                [0.043921, 10.0],
                [0.04392, 37.64],
                [0.043899, 0.066],
                [0.043885, 0.676],
                [0.04387, 22.758],
            ],
            "asks": [
                [0.043949, 0.346],
                [0.04395, 0.608],
                [0.043951, 3.948],
                [0.043954, 0.288],
                [0.043958, 9.277],
                [0.043995, 1.566],
                [0.044, 0.588],
                [0.044002, 0.992],
                [0.044003, 0.095],
                [0.04402, 37.64],
            ],
            "timestamp": None,
            "datetime": None,
            "nonce": 288004540,
        }
    )


@pytest.fixture
def order_book_l2_usd():
    return MagicMock(
        return_value={
            "symbol": "LTC/USDT",
            "bids": [
                [25.563, 49.269],
                [25.562, 83.0],
                [25.56, 106.0],
                [25.559, 15.381],
                [25.558, 29.299],
                [25.557, 34.624],
                [25.556, 10.0],
                [25.555, 14.684],
                [25.554, 45.91],
                [25.553, 50.0],
            ],
            "asks": [
                [25.566, 14.27],
                [25.567, 48.484],
                [25.568, 92.349],
                [25.572, 31.48],
                [25.573, 23.0],
                [25.574, 20.0],
                [25.575, 89.606],
                [25.576, 262.016],
                [25.577, 178.557],
                [25.578, 78.614],
            ],
            "timestamp": None,
            "datetime": None,
            "nonce": 2372149736,
        }
    )


@pytest.fixture
def ohlcv_history_list():
    return [
        [
            1511686200000,  # unix timestamp ms
            8.794e-05,  # open
            8.948e-05,  # high
            8.794e-05,  # low
            8.88e-05,  # close
            0.0877869,  # volume (in quote currency)
        ],
        [
            1511686500000,
            8.88e-05,
            8.942e-05,
            8.88e-05,
            8.893e-05,
            0.05874751,
        ],
        [1511686800000, 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, 0.7039405],
    ]


@pytest.fixture
def ohlcv_history(ohlcv_history_list):
    return ohlcv_to_dataframe(
        ohlcv_history_list, "5m", pair="UNITTEST/BTC", fill_missing=True, drop_incomplete=False
    )


@pytest.fixture
def tickers():
    return MagicMock(
        return_value={
            "ETH/BTC": {
                "symbol": "ETH/BTC",
                "timestamp": 1522014806207,
                "datetime": "2018-03-25T21:53:26.207Z",
                "high": 0.061697,
                "low": 0.060531,
                "bid": 0.061588,
                "bidVolume": 3.321,
                "ask": 0.061655,
                "askVolume": 0.212,
                "vwap": 0.06105296,
                "open": 0.060809,
                "close": 0.060761,
                "first": None,
                "last": 0.061588,
                "change": 1.281,
                "percentage": None,
                "average": None,
                "baseVolume": 111649.001,
                "quoteVolume": 6816.50176926,
                "info": {},
            },
            "TKN/BTC": {
                "symbol": "TKN/BTC",
                "timestamp": 1522014806169,
                "datetime": "2018-03-25T21:53:26.169Z",
                "high": 0.01885,
                "low": 0.018497,
                "bid": 0.018799,
                "bidVolume": 8.38,
                "ask": 0.018802,
                "askVolume": 15.0,
                "vwap": 0.01869197,
                "open": 0.018585,
                "close": 0.018573,
                "last": 0.018799,
                "baseVolume": 81058.66,
                "quoteVolume": 2247.48374509,
            },
            "BLK/BTC": {
                "symbol": "BLK/BTC",
                "timestamp": 1522014806072,
                "datetime": "2018-03-25T21:53:26.072Z",
                "high": 0.007745,
                "low": 0.007512,
                "bid": 0.007729,
                "bidVolume": 0.01,
                "ask": 0.007743,
                "askVolume": 21.37,
                "vwap": 0.00761466,
                "open": 0.007653,
                "close": 0.007652,
                "first": None,
                "last": 0.007743,
                "change": 1.176,
                "percentage": None,
                "average": None,
                "baseVolume": 295152.26,
                "quoteVolume": 1515.14631229,
                "info": {},
            },
            "LTC/BTC": {
                "symbol": "LTC/BTC",
                "timestamp": 1523787258992,
                "datetime": "2018-04-15T10:14:19.992Z",
                "high": 0.015978,
                "low": 0.0157,
                "bid": 0.015954,
                "bidVolume": 12.83,
                "ask": 0.015957,
                "askVolume": 0.49,
                "vwap": 0.01581636,
                "open": 0.015823,
                "close": 0.01582,
                "first": None,
                "last": 0.015951,
                "change": 0.809,
                "percentage": None,
                "average": None,
                "baseVolume": 88620.68,
                "quoteVolume": 1401.65697943,
                "info": {},
            },
            "BTT/BTC": {
                "symbol": "BTT/BTC",
                "timestamp": 1550936557206,
                "datetime": "2019-02-23T15:42:37.206Z",
                "high": 0.00000026,
                "low": 0.00000024,
                "bid": 0.00000024,
                "bidVolume": 2446894197.0,
                "ask": 0.00000025,
                "askVolume": 2447913837.0,
                "vwap": 0.00000025,
                "open": 0.00000026,
                "close": 0.00000024,
                "last": 0.00000024,
                "previousClose": 0.00000026,
                "change": -0.00000002,
                "percentage": -7.692,
                "average": None,
                "baseVolume": 4886464537.0,
                "quoteVolume": 1215.14489611,
                "info": {},
            },
            "HOT/BTC": {
                "symbol": "HOT/BTC",
                "timestamp": 1572273518661,
                "datetime": "2019-10-28T14:38:38.661Z",
                "high": 0.00000011,
                "low": 0.00000009,
                "bid": 0.0000001,
                "bidVolume": 1476027288.0,
                "ask": 0.00000011,
                "askVolume": 820153831.0,
                "vwap": 0.0000001,
                "open": 0.00000009,
                "close": 0.00000011,
                "last": 0.00000011,
                "previousClose": 0.00000009,
                "change": 0.00000002,
                "percentage": 22.222,
                "average": None,
                "baseVolume": 1442290324.0,
                "quoteVolume": 143.78311994,
                "info": {},
            },
            "FUEL/BTC": {
                "symbol": "FUEL/BTC",
                "timestamp": 1572340250771,
                "datetime": "2019-10-29T09:10:50.771Z",
                "high": 0.00000040,
                "low": 0.00000035,
                "bid": 0.00000036,
                "bidVolume": 8932318.0,
                "ask": 0.00000037,
                "askVolume": 10140774.0,
                "vwap": 0.00000037,
                "open": 0.00000039,
                "close": 0.00000037,
                "last": 0.00000037,
                "previousClose": 0.00000038,
                "change": -0.00000002,
                "percentage": -5.128,
                "average": None,
                "baseVolume": 168927742.0,
                "quoteVolume": 62.68220262,
                "info": {},
            },
            "BTC/USDT": {
                "symbol": "BTC/USDT",
                "timestamp": 1573758371399,
                "datetime": "2019-11-14T19:06:11.399Z",
                "high": 8800.0,
                "low": 8582.6,
                "bid": 8648.16,
                "bidVolume": 0.238771,
                "ask": 8648.72,
                "askVolume": 0.016253,
                "vwap": 8683.13647806,
                "open": 8759.7,
                "close": 8648.72,
                "last": 8648.72,
                "previousClose": 8759.67,
                "change": -110.98,
                "percentage": -1.267,
                "average": None,
                "baseVolume": 35025.943355,
                "quoteVolume": 304135046.4242901,
                "info": {},
            },
            "ETH/USDT": {
                "symbol": "ETH/USDT",
                "timestamp": 1522014804118,
                "datetime": "2018-03-25T21:53:24.118Z",
                "high": 530.88,
                "low": 512.0,
                "bid": 529.73,
                "bidVolume": 0.2,
                "ask": 530.21,
                "askVolume": 0.2464,
                "vwap": 521.02438405,
                "open": 527.27,
                "close": 528.42,
                "first": None,
                "last": 530.21,
                "change": 0.558,
                "percentage": 2.349,
                "average": None,
                "baseVolume": 72300.0659,
                "quoteVolume": 37670097.3022171,
                "info": {},
            },
            "TKN/USDT": {
                "symbol": "TKN/USDT",
                "timestamp": 1522014806198,
                "datetime": "2018-03-25T21:53:26.198Z",
                "high": 8718.0,
                "low": 8365.77,
                "bid": 8603.64,
                "bidVolume": 0.15846,
                "ask": 8603.67,
                "askVolume": 0.069147,
                "vwap": 8536.35621697,
                "open": 8680.0,
                "close": 8680.0,
                "first": None,
                "last": 8603.67,
                "change": -0.879,
                "percentage": -8.95,
                "average": None,
                "baseVolume": 30414.604298,
                "quoteVolume": 259629896.48584127,
                "info": {},
            },
            "BLK/USDT": {
                "symbol": "BLK/USDT",
                "timestamp": 1522014806145,
                "datetime": "2018-03-25T21:53:26.145Z",
                "high": 66.95,
                "low": 63.38,
                "bid": 66.473,
                "bidVolume": 4.968,
                "ask": 66.54,
                "askVolume": 2.704,
                "vwap": 65.0526901,
                "open": 66.43,
                "close": 66.383,
                "first": None,
                "last": 66.5,
                "change": 0.105,
                "percentage": None,
                "average": None,
                "baseVolume": 294106.204,
                "quoteVolume": 19132399.743954,
                "info": {},
            },
            "LTC/USDT": {
                "symbol": "LTC/USDT",
                "timestamp": 1523787257812,
                "datetime": "2018-04-15T10:14:18.812Z",
                "high": 129.94,
                "low": 124.0,
                "bid": 129.28,
                "bidVolume": 0.03201,
                "ask": 129.52,
                "askVolume": 0.14529,
                "vwap": 126.92838682,
                "open": 127.0,
                "close": 127.1,
                "first": None,
                "last": 129.28,
                "change": 1.795,
                "percentage": -2.5,
                "average": None,
                "baseVolume": 59698.79897,
                "quoteVolume": 29132399.743954,
                "info": {},
            },
            "XRP/BTC": {
                "symbol": "XRP/BTC",
                "timestamp": 1573758257534,
                "datetime": "2019-11-14T19:04:17.534Z",
                "high": 3.126e-05,
                "low": 3.061e-05,
                "bid": 3.093e-05,
                "bidVolume": 27901.0,
                "ask": 3.095e-05,
                "askVolume": 10551.0,
                "vwap": 3.091e-05,
                "open": 3.119e-05,
                "close": 3.094e-05,
                "last": 3.094e-05,
                "previousClose": 3.117e-05,
                "change": -2.5e-07,
                "percentage": -0.802,
                "average": None,
                "baseVolume": 37334921.0,
                "quoteVolume": 1154.19266394,
                "info": {},
            },
            "NANO/USDT": {
                "symbol": "NANO/USDT",
                "timestamp": 1580469388244,
                "datetime": "2020-01-31T11:16:28.244Z",
                "high": 0.7519,
                "low": 0.7154,
                "bid": 0.7305,
                "bidVolume": 300.3,
                "ask": 0.7342,
                "askVolume": 15.14,
                "vwap": 0.73645591,
                "open": 0.7154,
                "close": 0.7342,
                "last": 0.7342,
                "previousClose": 0.7189,
                "change": 0.0188,
                "percentage": 2.628,
                "average": None,
                "baseVolume": 439472.44,
                "quoteVolume": 323652.075405,
                "info": {},
            },
            # Example of leveraged pair with incomplete info
            "ADAHALF/USDT": {
                "symbol": "ADAHALF/USDT",
                "timestamp": 1580469388244,
                "datetime": "2020-01-31T11:16:28.244Z",
                "high": None,
                "low": None,
                "bid": 0.7305,
                "bidVolume": None,
                "ask": 0.7342,
                "askVolume": None,
                "vwap": None,
                "open": None,
                "close": None,
                "last": None,
                "previousClose": None,
                "change": None,
                "percentage": 2.628,
                "average": None,
                "baseVolume": 0.0,
                "quoteVolume": 0.0,
                "info": {},
            },
            "ADADOUBLE/USDT": {
                "symbol": "ADADOUBLE/USDT",
                "timestamp": 1580469388244,
                "datetime": "2020-01-31T11:16:28.244Z",
                "high": None,
                "low": None,
                "bid": 0.7305,
                "bidVolume": None,
                "ask": 0.7342,
                "askVolume": None,
                "vwap": None,
                "open": None,
                "close": None,
                "last": 0,
                "previousClose": None,
                "change": None,
                "percentage": 2.628,
                "average": None,
                "baseVolume": 0.0,
                "quoteVolume": 0.0,
                "info": {},
            },
        }
    )


@pytest.fixture
def dataframe_1m(testdatadir):
    with (testdatadir / "UNITTEST_BTC-1m.json").open("r") as data_file:
        return ohlcv_to_dataframe(
            json.load(data_file), "1m", pair="UNITTEST/BTC", fill_missing=True
        )


@pytest.fixture(scope="function")
def trades_for_order():
    return [
        {
            "info": {
                "id": 34567,
                "orderId": 123456,
                "price": "2.0",
                "qty": "8.00000000",
                "commission": "0.00800000",
                "commissionAsset": "LTC",
                "time": 1521663363189,
                "isBuyer": True,
                "isMaker": False,
                "isBestMatch": True,
            },
            "timestamp": 1521663363189,
            "datetime": "2018-03-21T20:16:03.189Z",
            "symbol": "LTC/USDT",
            "id": "34567",
            "order": "123456",
            "type": None,
            "side": "buy",
            "price": 2.0,
            "cost": 16.0,
            "amount": 8.0,
            "fee": {"cost": 0.008, "currency": "LTC"},
        }
    ]


@pytest.fixture(scope="function")
def trades_history():
    return [
        [1565798389463, "12618132aa9", None, "buy", 0.019627, 0.04, 0.00078508],
        [1565798399629, "1261813bb30", None, "buy", 0.019627, 0.244, 0.004788987999999999],
        [1565798399752, "1261813cc31", None, "sell", 0.019626, 0.011, 0.00021588599999999999],
        [1565798399862, "126181cc332", None, "sell", 0.019626, 0.011, 0.00021588599999999999],
        [1565798399862, "126181cc333", None, "sell", 0.019626, 0.012, 0.00021588599999999999],
        [1565798399872, "1261aa81334", None, "sell", 0.019626, 0.011, 0.00021588599999999999],
    ]


@pytest.fixture(scope="function")
def trades_history_df(trades_history):
    trades = trades_list_to_df(trades_history)
    trades["date"] = pd.to_datetime(trades["timestamp"], unit="ms", utc=True)
    return trades


@pytest.fixture(scope="function")
def fetch_trades_result():
    return [
        {
            "info": ["0.01962700", "0.04000000", "1565798399.4631551", "b", "m", "", "126181329"],
            "timestamp": 1565798399463,
            "datetime": "2019-08-14T15:59:59.463Z",
            "symbol": "ETH/BTC",
            "id": "126181329",
            "order": None,
            "type": None,
            "takerOrMaker": None,
            "side": "buy",
            "price": 0.019627,
            "amount": 0.04,
            "cost": 0.00078508,
            "fee": None,
        },
        {
            "info": ["0.01962700", "0.24400000", "1565798399.6291551", "b", "m", "", "126181330"],
            "timestamp": 1565798399629,
            "datetime": "2019-08-14T15:59:59.629Z",
            "symbol": "ETH/BTC",
            "id": "126181330",
            "order": None,
            "type": None,
            "takerOrMaker": None,
            "side": "buy",
            "price": 0.019627,
            "amount": 0.244,
            "cost": 0.004788987999999999,
            "fee": None,
        },
        {
            "info": ["0.01962600", "0.01100000", "1565798399.7521551", "s", "m", "", "126181331"],
            "timestamp": 1565798399752,
            "datetime": "2019-08-14T15:59:59.752Z",
            "symbol": "ETH/BTC",
            "id": "126181331",
            "order": None,
            "type": None,
            "takerOrMaker": None,
            "side": "sell",
            "price": 0.019626,
            "amount": 0.011,
            "cost": 0.00021588599999999999,
            "fee": None,
        },
        {
            "info": ["0.01962600", "0.01100000", "1565798399.8621551", "s", "m", "", "126181332"],
            "timestamp": 1565798399862,
            "datetime": "2019-08-14T15:59:59.862Z",
            "symbol": "ETH/BTC",
            "id": "126181332",
            "order": None,
            "type": None,
            "takerOrMaker": None,
            "side": "sell",
            "price": 0.019626,
            "amount": 0.011,
            "cost": 0.00021588599999999999,
            "fee": None,
        },
        {
            "info": [
                "0.01952600",
                "0.01200000",
                "1565798399.8721551",
                "s",
                "m",
                "",
                "126181333",
                1565798399872512133,
            ],
            "timestamp": 1565798399872,
            "datetime": "2019-08-14T15:59:59.872Z",
            "symbol": "ETH/BTC",
            "id": "126181333",
            "order": None,
            "type": None,
            "takerOrMaker": None,
            "side": "sell",
            "price": 0.019626,
            "amount": 0.011,
            "cost": 0.00021588599999999999,
            "fee": None,
        },
    ]


@pytest.fixture(scope="function")
def trades_for_order2():
    return [
        {
            "info": {},
            "timestamp": 1521663363189,
            "datetime": "2018-03-21T20:16:03.189Z",
            "symbol": "LTC/ETH",
            "id": "34567",
            "order": "123456",
            "type": None,
            "side": "buy",
            "price": 0.245441,
            "cost": 1.963528,
            "amount": 4.0,
            "fee": {"cost": 0.004, "currency": "LTC"},
        },
        {
            "info": {},
            "timestamp": 1521663363189,
            "datetime": "2018-03-21T20:16:03.189Z",
            "symbol": "LTC/ETH",
            "id": "34567",
            "order": "123456",
            "type": None,
            "side": "buy",
            "price": 0.245441,
            "cost": 1.963528,
            "amount": 4.0,
            "fee": {"cost": 0.004, "currency": "LTC"},
        },
    ]


@pytest.fixture
def buy_order_fee():
    return {
        "id": "mocked_limit_buy_old",
        "type": "limit",
        "side": "buy",
        "symbol": "mocked",
        "timestamp": dt_ts(dt_now() - timedelta(minutes=601)),
        "datetime": (dt_now() - timedelta(minutes=601)).isoformat(),
        "price": 0.245441,
        "amount": 8.0,
        "cost": 1.963528,
        "remaining": 90.99181073,
        "status": "closed",
        "fee": None,
    }


@pytest.fixture
def rpc_balance():
    return {
        "BTC": {"total": 12.0, "free": 12.0, "used": 0.0},
        "ETH": {"total": 0.0, "free": 0.0, "used": 0.0},
        "USDT": {"total": 10000.0, "free": 10000.0, "used": 0.0},
        "LTC": {"total": 10.0, "free": 10.0, "used": 0.0},
        "XRP": {"total": 0.1, "free": 0.01, "used": 0.0},
        "EUR": {"total": 10.0, "free": 10.0, "used": 0.0},
    }


@pytest.fixture
def testdatadir() -> Path:
    """Return the path where testdata files are stored"""
    return (Path(__file__).parent / "testdata").resolve()


@pytest.fixture(scope="function")
def import_fails() -> None:
    # Source of this test-method:
    # https://stackoverflow.com/questions/2481511/mocking-importerror-in-python
    import builtins

    realimport = builtins.__import__

    def mockedimport(name, *args, **kwargs):
        if name in ["filelock", "cysystemd.journal", "uvloop"]:
            raise ImportError(f"No module named '{name}'")
        return realimport(name, *args, **kwargs)

    builtins.__import__ = mockedimport

    # Run test - then cleanup
    yield

    # restore previous importfunction
    builtins.__import__ = realimport


@pytest.fixture(scope="function")
def open_trade():
    trade = Trade(
        pair="ETH/BTC",
        open_rate=0.00001099,
        exchange="binance",
        amount=90.99181073,
        fee_open=0.0,
        fee_close=0.0,
        stake_amount=1,
        open_date=dt_now() - timedelta(minutes=601),
        is_open=True,
    )
    trade.orders = [
        Order(
            ft_order_side="buy",
            ft_pair=trade.pair,
            ft_is_open=True,
            ft_amount=trade.amount,
            ft_price=trade.open_rate,
            order_id="123456789",
            status="closed",
            symbol=trade.pair,
            order_type="market",
            side="buy",
            price=trade.open_rate,
            average=trade.open_rate,
            filled=trade.amount,
            remaining=0,
            cost=trade.open_rate * trade.amount,
            order_date=trade.open_date,
            order_filled_date=trade.open_date,
        )
    ]
    return trade


@pytest.fixture(scope="function")
def open_trade_usdt():
    trade = Trade(
        pair="ADA/USDT",
        open_rate=2.0,
        exchange="binance",
        amount=30.0,
        fee_open=0.0,
        fee_close=0.0,
        stake_amount=60.0,
        open_date=dt_now() - timedelta(minutes=601),
        is_open=True,
    )
    trade.orders = [
        Order(
            ft_order_side="buy",
            ft_pair=trade.pair,
            ft_is_open=False,
            ft_amount=trade.amount,
            ft_price=trade.open_rate,
            order_id="123456789",
            status="closed",
            symbol=trade.pair,
            order_type="market",
            side="buy",
            price=trade.open_rate,
            average=trade.open_rate,
            filled=trade.amount,
            remaining=0,
            cost=trade.open_rate * trade.amount,
            order_date=trade.open_date,
            order_filled_date=trade.open_date,
        ),
        Order(
            ft_order_side="exit",
            ft_pair=trade.pair,
            ft_is_open=True,
            ft_amount=trade.amount,
            ft_price=trade.open_rate,
            order_id="123456789_exit",
            status="open",
            symbol=trade.pair,
            order_type="limit",
            side="sell",
            price=trade.open_rate,
            average=trade.open_rate,
            filled=trade.amount,
            remaining=0,
            cost=trade.open_rate * trade.amount,
            order_date=trade.open_date,
            order_filled_date=trade.open_date,
        ),
    ]
    return trade


@pytest.fixture(scope="function")
def limit_buy_order_usdt_open():
    return {
        "id": "mocked_limit_buy_usdt",
        "type": "limit",
        "side": "buy",
        "symbol": "mocked",
        "datetime": dt_now().isoformat(),
        "timestamp": dt_ts(),
        "price": 2.00,
        "average": 2.00,
        "amount": 30.0,
        "filled": 0.0,
        "cost": 60.0,
        "remaining": 30.0,
        "status": "open",
    }


@pytest.fixture(scope="function")
def limit_buy_order_usdt(limit_buy_order_usdt_open):
    order = deepcopy(limit_buy_order_usdt_open)
    order["status"] = "closed"
    order["filled"] = order["amount"]
    order["remaining"] = 0.0
    return order


@pytest.fixture
def limit_sell_order_usdt_open():
    return {
        "id": "mocked_limit_sell_usdt",
        "type": "limit",
        "side": "sell",
        "symbol": "mocked",
        "datetime": dt_now().isoformat(),
        "timestamp": dt_ts(),
        "price": 2.20,
        "amount": 30.0,
        "cost": 66.0,
        "filled": 0.0,
        "remaining": 30.0,
        "status": "open",
    }


@pytest.fixture
def limit_sell_order_usdt(limit_sell_order_usdt_open):
    order = deepcopy(limit_sell_order_usdt_open)
    order["remaining"] = 0.0
    order["filled"] = order["amount"]
    order["status"] = "closed"
    return order


@pytest.fixture(scope="function")
def market_buy_order_usdt():
    return {
        "id": "mocked_market_buy",
        "type": "market",
        "side": "buy",
        "symbol": "mocked",
        "timestamp": dt_ts(),
        "datetime": dt_now().isoformat(),
        "price": 2.00,
        "amount": 30.0,
        "filled": 30.0,
        "remaining": 0.0,
        "status": "closed",
    }


@pytest.fixture
def market_buy_order_usdt_doublefee(market_buy_order_usdt):
    order = deepcopy(market_buy_order_usdt)
    order["fee"] = None
    # Market orders filled with 2 trades can have fees in different currencies
    # assuming the account runs out of BNB.
    order["fees"] = [
        {"cost": 0.00025125, "currency": "BNB"},
        {"cost": 0.05030681, "currency": "USDT"},
    ]
    order["trades"] = [
        {
            "timestamp": None,
            "datetime": None,
            "symbol": "ETH/USDT",
            "id": None,
            "order": "123",
            "type": "market",
            "side": "sell",
            "takerOrMaker": None,
            "price": 2.01,
            "amount": 25.0,
            "cost": 50.25,
            "fee": {"cost": 0.00025125, "currency": "BNB"},
        },
        {
            "timestamp": None,
            "datetime": None,
            "symbol": "ETH/USDT",
            "id": None,
            "order": "123",
            "type": "market",
            "side": "sell",
            "takerOrMaker": None,
            "price": 2.0,
            "amount": 5,
            "cost": 10,
            "fee": {"cost": 0.0100306, "currency": "USDT"},
        },
    ]
    return order


@pytest.fixture
def market_sell_order_usdt():
    return {
        "id": "mocked_limit_sell",
        "type": "market",
        "side": "sell",
        "symbol": "mocked",
        "timestamp": dt_ts(),
        "datetime": dt_now().isoformat(),
        "price": 2.20,
        "amount": 30.0,
        "filled": 30.0,
        "remaining": 0.0,
        "status": "closed",
    }


@pytest.fixture(scope="function")
def limit_order(limit_buy_order_usdt, limit_sell_order_usdt):
    return {"buy": limit_buy_order_usdt, "sell": limit_sell_order_usdt}


@pytest.fixture(scope="function")
def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open):
    return {"buy": limit_buy_order_usdt_open, "sell": limit_sell_order_usdt_open}


@pytest.fixture(scope="function")
def mark_ohlcv():
    return [
        [1630454400000, 2.77, 2.77, 2.73, 2.73, 0],
        [1630458000000, 2.73, 2.76, 2.72, 2.74, 0],
        [1630461600000, 2.74, 2.76, 2.74, 2.76, 0],
        [1630465200000, 2.76, 2.76, 2.74, 2.76, 0],
        [1630468800000, 2.76, 2.77, 2.75, 2.77, 0],
        [1630472400000, 2.77, 2.79, 2.75, 2.78, 0],
        [1630476000000, 2.78, 2.80, 2.77, 2.77, 0],
        [1630479600000, 2.78, 2.79, 2.77, 2.77, 0],
        [1630483200000, 2.77, 2.79, 2.77, 2.78, 0],
        [1630486800000, 2.77, 2.84, 2.77, 2.84, 0],
        [1630490400000, 2.84, 2.85, 2.81, 2.81, 0],
        [1630494000000, 2.81, 2.83, 2.81, 2.81, 0],
        [1630497600000, 2.81, 2.84, 2.81, 2.82, 0],
        [1630501200000, 2.82, 2.83, 2.81, 2.81, 0],
    ]


@pytest.fixture(scope="function")
def funding_rate_history_hourly():
    return [
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000008,
            "timestamp": 1630454400000,
            "datetime": "2021-09-01T00:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000004,
            "timestamp": 1630458000000,
            "datetime": "2021-09-01T01:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000012,
            "timestamp": 1630461600000,
            "datetime": "2021-09-01T02:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000003,
            "timestamp": 1630465200000,
            "datetime": "2021-09-01T03:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000007,
            "timestamp": 1630468800000,
            "datetime": "2021-09-01T04:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000003,
            "timestamp": 1630472400000,
            "datetime": "2021-09-01T05:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000019,
            "timestamp": 1630476000000,
            "datetime": "2021-09-01T06:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000003,
            "timestamp": 1630479600000,
            "datetime": "2021-09-01T07:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000003,
            "timestamp": 1630483200000,
            "datetime": "2021-09-01T08:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0,
            "timestamp": 1630486800000,
            "datetime": "2021-09-01T09:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000013,
            "timestamp": 1630490400000,
            "datetime": "2021-09-01T10:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000077,
            "timestamp": 1630494000000,
            "datetime": "2021-09-01T11:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000072,
            "timestamp": 1630497600000,
            "datetime": "2021-09-01T12:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": 0.000097,
            "timestamp": 1630501200000,
            "datetime": "2021-09-01T13:00:00.000Z",
        },
    ]


@pytest.fixture(scope="function")
def funding_rate_history_octohourly():
    return [
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000008,
            "timestamp": 1630454400000,
            "datetime": "2021-09-01T00:00:00.000Z",
        },
        {
            "symbol": "ADA/USDT:USDT",
            "fundingRate": -0.000003,
            "timestamp": 1630483200000,
            "datetime": "2021-09-01T08:00:00.000Z",
        },
    ]


@pytest.fixture(scope="function")
def leverage_tiers():
    return {
        "1000SHIB/USDT:USDT": [
            {
                "minNotional": 0,
                "maxNotional": 50000,
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 50,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 50000,
                "maxNotional": 150000,
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 750.0,
            },
            {
                "minNotional": 150000,
                "maxNotional": 250000,
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 4500.0,
            },
            {
                "minNotional": 250000,
                "maxNotional": 500000,
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 17000.0,
            },
            {
                "minNotional": 500000,
                "maxNotional": 1000000,
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 4,
                "maintAmt": 29500.0,
            },
            {
                "minNotional": 1000000,
                "maxNotional": 2000000,
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 154500.0,
            },
            {
                "minNotional": 2000000,
                "maxNotional": 30000000,
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 654500.0,
            },
        ],
        "1INCH/USDT:USDT": [
            {
                "minNotional": 0,
                "maxNotional": 5000,
                "maintenanceMarginRate": 0.012,
                "maxLeverage": 50,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 5000,
                "maxNotional": 25000,
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 65.0,
            },
            {
                "minNotional": 25000,
                "maxNotional": 100000,
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 690.0,
            },
            {
                "minNotional": 100000,
                "maxNotional": 250000,
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 5690.0,
            },
            {
                "minNotional": 250000,
                "maxNotional": 1000000,
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 2,
                "maintAmt": 11940.0,
            },
            {
                "minNotional": 1000000,
                "maxNotional": 100000000,
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 386940.0,
            },
        ],
        "AAVE/USDT:USDT": [
            {
                "minNotional": 0,
                "maxNotional": 5000,
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 50,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 5000,
                "maxNotional": 25000,
                "maintenanceMarginRate": 0.02,
                "maxLeverage": 25,
                "maintAmt": 75.0,
            },
            {
                "minNotional": 25000,
                "maxNotional": 100000,
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 700.0,
            },
            {
                "minNotional": 100000,
                "maxNotional": 250000,
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 5700.0,
            },
            {
                "minNotional": 250000,
                "maxNotional": 1000000,
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 2,
                "maintAmt": 11950.0,
            },
            {
                "minNotional": 10000000,
                "maxNotional": 50000000,
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 386950.0,
            },
        ],
        "ADA/USDT:USDT": [
            {
                "minNotional": 0,
                "maxNotional": 100000,
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 100000,
                "maxNotional": 500000,
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 2500.0,
            },
            {
                "minNotional": 500000,
                "maxNotional": 1000000,
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 27500.0,
            },
            {
                "minNotional": 1000000,
                "maxNotional": 2000000,
                "maintenanceMarginRate": 0.15,
                "maxLeverage": 3,
                "maintAmt": 77500.0,
            },
            {
                "minNotional": 2000000,
                "maxNotional": 5000000,
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 277500.0,
            },
            {
                "minNotional": 5000000,
                "maxNotional": None,
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 1527500.0,
            },
        ],
        "XRP/USDT:USDT": [
            {
                "minNotional": 0,  # stake(before leverage) = 0
                "maxNotional": 100000,  # max stake(before leverage) = 5000
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 100000,  # stake = 10000.0
                "maxNotional": 500000,  # max_stake = 50000.0
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 2500.0,
            },
            {
                "minNotional": 500000,  # stake = 100000.0
                "maxNotional": 1000000,  # max_stake = 200000.0
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 27500.0,
            },
            {
                "minNotional": 1000000,  # stake = 333333.3333333333
                "maxNotional": 2000000,  # max_stake = 666666.6666666666
                "maintenanceMarginRate": 0.15,
                "maxLeverage": 3,
                "maintAmt": 77500.0,
            },
            {
                "minNotional": 2000000,  # stake = 1000000.0
                "maxNotional": 5000000,  # max_stake = 2500000.0
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 277500.0,
            },
            {
                "minNotional": 5000000,  # stake = 5000000.0
                "maxNotional": 30000000,  # max_stake = 30000000.0
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 1527500.0,
            },
        ],
        "BNB/USDT:USDT": [
            {
                "minNotional": 0,  # stake = 0.0
                "maxNotional": 10000,  # max_stake = 133.33333333333334
                "maintenanceMarginRate": 0.0065,
                "maxLeverage": 75,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 10000,  # stake = 200.0
                "maxNotional": 50000,  # max_stake = 1000.0
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 50,
                "maintAmt": 35.0,
            },
            {
                "minNotional": 50000,  # stake = 2000.0
                "maxNotional": 250000,  # max_stake = 10000.0
                "maintenanceMarginRate": 0.02,
                "maxLeverage": 25,
                "maintAmt": 535.0,
            },
            {
                "minNotional": 250000,  # stake = 25000.0
                "maxNotional": 1000000,  # max_stake = 100000.0
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 8035.0,
            },
            {
                "minNotional": 1000000,  # stake = 200000.0
                "maxNotional": 2000000,  # max_stake = 400000.0
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 58035.0,
            },
            {
                "minNotional": 2000000,  # stake = 500000.0
                "maxNotional": 5000000,  # max_stake = 1250000.0
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 4,
                "maintAmt": 108035.0,
            },
            {
                "minNotional": 5000000,  # stake = 1666666.6666666667
                "maxNotional": 10000000,  # max_stake = 3333333.3333333335
                "maintenanceMarginRate": 0.15,
                "maxLeverage": 3,
                "maintAmt": 233035.0,
            },
            {
                "minNotional": 10000000,  # stake = 5000000.0
                "maxNotional": 20000000,  # max_stake = 10000000.0
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 1233035.0,
            },
            {
                "minNotional": 20000000,  # stake = 20000000.0
                "maxNotional": 50000000,  # max_stake = 50000000.0
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 6233035.0,
            },
        ],
        "BTC/USDT:USDT": [
            {
                "minNotional": 0,  # stake = 0.0
                "maxNotional": 50000,  # max_stake = 400.0
                "maintenanceMarginRate": 0.004,
                "maxLeverage": 125,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 50000,  # stake = 500.0
                "maxNotional": 250000,  # max_stake = 2500.0
                "maintenanceMarginRate": 0.005,
                "maxLeverage": 100,
                "maintAmt": 50.0,
            },
            {
                "minNotional": 250000,  # stake = 5000.0
                "maxNotional": 1000000,  # max_stake = 20000.0
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 50,
                "maintAmt": 1300.0,
            },
            {
                "minNotional": 1000000,  # stake = 50000.0
                "maxNotional": 7500000,  # max_stake = 375000.0
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 16300.0,
            },
            {
                "minNotional": 7500000,  # stake = 750000.0
                "maxNotional": 40000000,  # max_stake = 4000000.0
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 203800.0,
            },
            {
                "minNotional": 40000000,  # stake = 8000000.0
                "maxNotional": 100000000,  # max_stake = 20000000.0
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 2203800.0,
            },
            {
                "minNotional": 100000000,  # stake = 25000000.0
                "maxNotional": 200000000,  # max_stake = 50000000.0
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 4,
                "maintAmt": 4703800.0,
            },
            {
                "minNotional": 200000000,  # stake = 66666666.666666664
                "maxNotional": 400000000,  # max_stake = 133333333.33333333
                "maintenanceMarginRate": 0.15,
                "maxLeverage": 3,
                "maintAmt": 9703800.0,
            },
            {
                "minNotional": 400000000,  # stake = 200000000.0
                "maxNotional": 600000000,  # max_stake = 300000000.0
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 4.97038e7,
            },
            {
                "minNotional": 600000000,  # stake = 600000000.0
                "maxNotional": 1000000000,  # max_stake = 1000000000.0
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 1.997038e8,
            },
        ],
        "ZEC/USDT:USDT": [
            {
                "minNotional": 0,
                "maxNotional": 50000,
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 50,
                "maintAmt": 0.0,
            },
            {
                "minNotional": 50000,
                "maxNotional": 150000,
                "maintenanceMarginRate": 0.025,
                "maxLeverage": 20,
                "maintAmt": 750.0,
            },
            {
                "minNotional": 150000,
                "maxNotional": 250000,
                "maintenanceMarginRate": 0.05,
                "maxLeverage": 10,
                "maintAmt": 4500.0,
            },
            {
                "minNotional": 250000,
                "maxNotional": 500000,
                "maintenanceMarginRate": 0.1,
                "maxLeverage": 5,
                "maintAmt": 17000.0,
            },
            {
                "minNotional": 500000,
                "maxNotional": 1000000,
                "maintenanceMarginRate": 0.125,
                "maxLeverage": 4,
                "maintAmt": 29500.0,
            },
            {
                "minNotional": 1000000,
                "maxNotional": 2000000,
                "maintenanceMarginRate": 0.25,
                "maxLeverage": 2,
                "maintAmt": 154500.0,
            },
            {
                "minNotional": 2000000,
                "maxNotional": 30000000,
                "maintenanceMarginRate": 0.5,
                "maxLeverage": 1,
                "maintAmt": 654500.0,
            },
        ],
        "TIA/USDT:USDT": [
            # Okx tier - these have a gap between maxNotional and the next minNotional
            {
                "minNotional": 0.0,
                "maxNotional": 6500.0,
                "maintenanceMarginRate": 0.0065,
                "maxLeverage": 50.0,
                "maintAmt": None,
            },
            {
                "minNotional": 6501.0,
                "maxNotional": 12000.0,
                "maintenanceMarginRate": 0.01,
                "maxLeverage": 40.0,
                "maintAmt": None,
            },
            {
                "minNotional": 12001.0,
                "maxNotional": 25000.0,
                "maintenanceMarginRate": 0.015,
                "maxLeverage": 20.0,
                "maintAmt": None,
            },
            {
                "minNotional": 25001.0,
                "maxNotional": 50000.0,
                "maintenanceMarginRate": 0.02,
                "maxLeverage": 18.18,
                "maintAmt": None,
            },
        ],
    }