Merge branch 'develop' into pr/samgermain/6780

This commit is contained in:
Matthias
2022-07-16 15:35:00 +02:00
151 changed files with 22464 additions and 18288 deletions

View File

@@ -17,9 +17,9 @@ from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOr
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
calculate_backoff, remove_credentials)
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
timeframe_to_next_date, timeframe_to_prev_date,
timeframe_to_seconds)
from freqtrade.exchange.exchange import (date_minus_candles, market_is_active, timeframe_to_minutes,
timeframe_to_msecs, timeframe_to_next_date,
timeframe_to_prev_date, timeframe_to_seconds)
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re
@@ -99,6 +99,8 @@ def test_remove_credentials(default_conf, caplog) -> None:
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
aei_mock = mocker.patch('freqtrade.exchange.Exchange.additional_exchange_init')
caplog.set_level(logging.INFO)
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True, 'asyncio_loop': True}
@@ -108,6 +110,7 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
caplog)
assert ex._api_async.aiohttp_trust_env
assert not ex._api.aiohttp_trust_env
assert aei_mock.call_count == 1
# Reset logging and config
caplog.clear()
@@ -302,6 +305,7 @@ def test_amount_to_precision(
(234.53, 4, 0.5, 235.0),
(0.891534, 4, 0.0001, 0.8916),
(64968.89, 4, 0.01, 64968.89),
(0.000000003483, 4, 1e-12, 0.000000003483),
])
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
@@ -936,6 +940,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
def test_validate_timeframes_not_in_config(default_conf, mocker):
# TODO: this test does not assert ...
del default_conf["timeframe"]
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
@@ -951,6 +956,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
mocker.patch('freqtrade.exchange.Exchange.validate_required_startup_candles')
Exchange(default_conf)
@@ -1081,6 +1087,13 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
with pytest.raises(OperationalException, match=r'This strategy requires 6000.*'):
Exchange(default_conf)
# Emulate kraken mode
ex._ft_has['ohlcv_has_history'] = False
with pytest.raises(OperationalException,
match=r'This strategy requires 2500.*, '
r'which is more than the amount.*'):
ex.validate_required_startup_candles(2500, '5m')
def test_exchange_has(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf)
@@ -1122,7 +1135,7 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
assert order["symbol"] == "ETH/BTC"
assert order["amount"] == 1
assert order["leverage"] == leverage
assert order["cost"] == 1 * 200 / leverage
assert order["cost"] == 1 * 200
@pytest.mark.parametrize('side,is_short,order_reason', [
@@ -1913,7 +1926,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
# one_call calculation * 1.8 should do 2 calls
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
since = 5 * 60 * exchange.ohlcv_candle_limit('5m', CandleType.SPOT) * 1.8
ret = exchange.get_historic_ohlcv(
pair,
"5m",
@@ -1979,7 +1992,7 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name, candle_ty
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
# one_call calculation * 1.8 should do 2 calls
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
since = 5 * 60 * exchange.ohlcv_candle_limit('5m', CandleType.SPOT) * 1.8
ret = exchange.get_historic_ohlcv_as_df(
pair,
"5m",
@@ -2033,7 +2046,7 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
)
# Required candles
candles = (end_ts - start_ts) / 300_000
exp = candles // exchange.ohlcv_candle_limit('5m') + 1
exp = candles // exchange.ohlcv_candle_limit('5m', CandleType.SPOT) + 1
# Depending on the exchange, this should be called between 1 and 6 times.
assert exchange._api_async.fetch_ohlcv.call_count == exp
@@ -2183,6 +2196,8 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
@pytest.mark.asyncio
async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
from freqtrade.exchange.common import _reset_logging_mixin
_reset_logging_mixin()
caplog.set_level(logging.INFO)
api_mock = MagicMock()
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection(
@@ -2836,6 +2851,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
until=trades_history[-1][0])
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
@@ -3001,6 +3017,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name, caplog):
default_conf['dry_run'] = True
@@ -3053,6 +3070,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
order_id='_', pair='TKN/BTC')
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
# Don't test FTX here - that needs a separate test
@@ -3380,7 +3398,7 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
# This should only run for bittrex
assert exchange_name == 'bittrex'
assert exchange.ohlcv_candle_limit(timeframe) == expected
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT) == expected
def test_timeframe_to_minutes():
@@ -3462,6 +3480,17 @@ def test_timeframe_to_next_date():
assert timeframe_to_next_date("5m", date) == date + timedelta(minutes=5)
def test_date_minus_candles():
date = datetime(2019, 8, 12, 13, 25, 0, tzinfo=timezone.utc)
assert date_minus_candles("5m", 3, date) == date - timedelta(minutes=15)
assert date_minus_candles("5m", 5, date) == date - timedelta(minutes=25)
assert date_minus_candles("1m", 6, date) == date - timedelta(minutes=6)
assert date_minus_candles("1h", 3, date) == date - timedelta(hours=3, minutes=25)
assert date_minus_candles("1h", 3) == timeframe_to_prev_date('1h') - timedelta(hours=3)
@pytest.mark.parametrize(
"market_symbol,base,quote,exchange,spot,margin,futures,trademode,add_dict,expected_result",
[
@@ -3556,7 +3585,7 @@ def test_order_has_fee(order, expected) -> None:
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
ex = get_patched_exchange(mocker, default_conf)
assert ex.extract_cost_curr_rate(order) == expected
assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected
@pytest.mark.parametrize("order,unknown_fee_rate,expected", [
@@ -3594,6 +3623,9 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
# Missing currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': None, 'cost': 0.005}}, None, None),
])
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
@@ -3602,7 +3634,8 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r
ex = get_patched_exchange(mocker, default_conf)
assert ex.calculate_fee_rate(order) == expected
assert ex.calculate_fee_rate(order['fee'], order['symbol'],
cost=order['cost'], amount=order['amount']) == expected
@pytest.mark.parametrize('retrycount,max_retries,expected', [
@@ -3831,6 +3864,7 @@ def test_validate_trading_mode_and_margin_mode(
("bibox", "spot", {"has": {"fetchCurrencies": False}}),
("bibox", "margin", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "margin"}}),
("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
("bybit", "spot", {"options": {"defaultType": "spot"}}),
("bybit", "futures", {"options": {"defaultType": "linear"}}),
("ftx", "futures", {"options": {"defaultType": "swap"}}),
("gateio", "futures", {"options": {"defaultType": "swap"}}),
@@ -3929,6 +3963,70 @@ def test_calculate_funding_fees(
) == kraken_fee
@pytest.mark.parametrize(
'mark_price,funding_rate,futures_funding_rate', [
(1000, 0.001, None),
(1000, 0.001, 0.01),
(1000, 0.001, 0.0),
(1000, 0.001, -0.01),
])
def test_combine_funding_and_mark(
default_conf,
mocker,
funding_rate,
mark_price,
futures_funding_rate,
):
exchange = get_patched_exchange(mocker, default_conf)
prior2_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=2))
prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1))
trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc))
funding_rates = DataFrame([
{'date': prior2_date, 'open': funding_rate},
{'date': prior_date, 'open': funding_rate},
{'date': trade_date, 'open': funding_rate},
])
mark_rates = DataFrame([
{'date': prior2_date, 'open': mark_price},
{'date': prior_date, 'open': mark_price},
{'date': trade_date, 'open': mark_price},
])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
assert 'open_mark' in df.columns
assert 'open_fund' in df.columns
assert len(df) == 3
funding_rates = DataFrame([
{'date': trade_date, 'open': funding_rate},
])
mark_rates = DataFrame([
{'date': prior2_date, 'open': mark_price},
{'date': prior_date, 'open': mark_price},
{'date': trade_date, 'open': mark_price},
])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
if futures_funding_rate is not None:
assert len(df) == 3
assert df.iloc[0]['open_fund'] == futures_funding_rate
assert df.iloc[1]['open_fund'] == futures_funding_rate
assert df.iloc[2]['open_fund'] == funding_rate
else:
assert len(df) == 1
# Empty funding rates
funding_rates = DataFrame([], columns=['date', 'open'])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
if futures_funding_rate is not None:
assert len(df) == 3
assert df.iloc[0]['open_fund'] == futures_funding_rate
assert df.iloc[1]['open_fund'] == futures_funding_rate
assert df.iloc[2]['open_fund'] == futures_funding_rate
else:
assert len(df) == 0
def test_get_or_calculate_liquidation_price(mocker, default_conf):
api_mock = MagicMock()
@@ -4799,8 +4897,10 @@ def test__get_params(mocker, default_conf, exchange_name):
if exchange_name == 'okx':
params2['tdMode'] = 'isolated'
params2['posSide'] = 'net'
assert exchange._get_params(
side="buy",
ordertype='market',
reduceOnly=False,
time_in_force='gtc',
@@ -4808,6 +4908,7 @@ def test__get_params(mocker, default_conf, exchange_name):
) == params1
assert exchange._get_params(
side="buy",
ordertype='market',
reduceOnly=False,
time_in_force='ioc',
@@ -4815,6 +4916,7 @@ def test__get_params(mocker, default_conf, exchange_name):
) == params1
assert exchange._get_params(
side="buy",
ordertype='limit',
reduceOnly=False,
time_in_force='gtc',
@@ -4827,6 +4929,7 @@ def test__get_params(mocker, default_conf, exchange_name):
exchange._params = {'test': True}
assert exchange._get_params(
side="buy",
ordertype='limit',
reduceOnly=True,
time_in_force='ioc',