Merge branch 'develop' into pr/imxuwang/3799
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.configuration.timerange import TimeRange
|
||||
from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format,
|
||||
ohlcv_fill_up_missing_data, ohlcv_to_dataframe,
|
||||
trades_dict_to_list, trades_remove_duplicates, trim_dataframe)
|
||||
trades_dict_to_list, trades_remove_duplicates,
|
||||
trades_to_ohlcv, trim_dataframe)
|
||||
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
|
||||
validate_backtest_data)
|
||||
from tests.conftest import log_has
|
||||
@@ -26,6 +29,28 @@ def test_ohlcv_to_dataframe(ohlcv_history_list, caplog):
|
||||
assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog)
|
||||
|
||||
|
||||
def test_trades_to_ohlcv(ohlcv_history_list, caplog):
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
with pytest.raises(ValueError, match="Trade-list empty."):
|
||||
trades_to_ohlcv([], '1m')
|
||||
|
||||
trades = [
|
||||
[1570752011620, "13519807", None, "sell", 0.00141342, 23.0, 0.03250866],
|
||||
[1570752011620, "13519808", None, "sell", 0.00141266, 54.0, 0.07628364],
|
||||
[1570752017964, "13519809", None, "sell", 0.00141266, 8.0, 0.01130128]]
|
||||
|
||||
df = trades_to_ohlcv(trades, '1m')
|
||||
assert not df.empty
|
||||
assert len(df) == 1
|
||||
assert 'open' in df.columns
|
||||
assert 'high' in df.columns
|
||||
assert 'low' in df.columns
|
||||
assert 'close' in df.columns
|
||||
assert df.loc[:, 'high'][0] == 0.00141342
|
||||
assert df.loc[:, 'low'][0] == 0.00141266
|
||||
|
||||
|
||||
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
||||
data = load_pair_history(datadir=testdatadir,
|
||||
timeframe='1m',
|
||||
|
@@ -312,10 +312,7 @@ def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog,
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_1)
|
||||
_clean_test_file(file1_5)
|
||||
assert log_has(
|
||||
'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. '
|
||||
'Error: File Error', caplog
|
||||
)
|
||||
assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog)
|
||||
|
||||
|
||||
def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||
@@ -620,6 +617,12 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file5)
|
||||
|
||||
assert not log_has('Could not convert NoDatapair to OHLCV.', caplog)
|
||||
|
||||
convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'],
|
||||
datadir=testdatadir, timerange=tr, erase=True)
|
||||
assert log_has('Could not convert NoDatapair to OHLCV.', caplog)
|
||||
|
||||
|
||||
def test_datahandler_ohlcv_get_pairs(testdatadir):
|
||||
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m')
|
||||
|
@@ -1307,6 +1307,57 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
|
||||
assert log_has_re(r"Async code raised an exception: .*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
ohlcv = [
|
||||
[
|
||||
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
],
|
||||
[
|
||||
arrow.utcnow().shift(minutes=5).int_timestamp * 1000, # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
],
|
||||
[
|
||||
arrow.utcnow().shift(minutes=10).int_timestamp * 1000, # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
async def mock_candle_hist(pair, timeframe, since_ms):
|
||||
return pair, timeframe, ohlcv
|
||||
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
|
||||
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
|
||||
ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int((
|
||||
arrow.utcnow().int_timestamp - since) * 1000))
|
||||
|
||||
assert exchange._async_get_candle_history.call_count == 2
|
||||
# Returns twice the above OHLCV data
|
||||
assert len(ret) == 2
|
||||
assert isinstance(ret, DataFrame)
|
||||
assert 'date' in ret.columns
|
||||
assert 'open' in ret.columns
|
||||
assert 'close' in ret.columns
|
||||
assert 'high' in ret.columns
|
||||
|
||||
|
||||
def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
||||
ohlcv = [
|
||||
[
|
||||
|
@@ -10,6 +10,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
STOPLOSS_ORDERTYPE = 'stop-loss'
|
||||
STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit'
|
||||
|
||||
|
||||
def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
@@ -156,7 +157,8 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
"get_balances", "fetch_balance")
|
||||
|
||||
|
||||
def test_stoploss_order_kraken(default_conf, mocker):
|
||||
@pytest.mark.parametrize('ordertype', ['market', 'limit'])
|
||||
def test_stoploss_order_kraken(default_conf, mocker, ordertype):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
|
||||
@@ -173,24 +175,26 @@ def test_stoploss_order_kraken(default_conf, mocker):
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
|
||||
# stoploss_on_exchange_limit_ratio is irrelevant for kraken market orders
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||
assert api_mock.create_order.call_count == 1
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
order_types={'stoploss': ordertype,
|
||||
'stoploss_on_exchange_limit_ratio': 0.99
|
||||
})
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
if ordertype == 'limit':
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {
|
||||
'trading_agreement': 'agree', 'price2': 217.8}
|
||||
else:
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {
|
||||
'trading_agreement': 'agree'}
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {'trading_agreement': 'agree'}
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
|
@@ -328,6 +328,118 @@ tc20 = BTContainer(data=[
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
# Test 21: trailing_stop ROI collision.
|
||||
# Roi should trigger before Trailing stop - otherwise Trailing stop profits can be > ROI
|
||||
# which cannot happen in reality
|
||||
# stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle
|
||||
tc21 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
|
||||
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time.
|
||||
# applying a positive trailing stop of 3% - ROI should apply before trailing stop.
|
||||
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2
|
||||
tc22 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
# Test 23: trailing_stop Raises in candle 2 (does not trigger)
|
||||
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
|
||||
# ROI is changed after this to 4%, dropping ROI below trailing_stop_positive, causing a sell
|
||||
# in the candle after the raised stoploss candle with ROI reason.
|
||||
# Stoploss would trigger in this candle too, but it's no longer relevant.
|
||||
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell)
|
||||
tc23 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5251, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
|
||||
# Stoploss at 1%.
|
||||
# Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle)
|
||||
tc24 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4986, 4600, 6172, 0, 0],
|
||||
[3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal
|
||||
[4, 5010, 4987, 4977, 4995, 6172, 0, 0],
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
|
||||
# Stoploss at 1%.
|
||||
# Sell-signal wins over stoploss
|
||||
tc25 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4986, 4600, 6172, 0, 0],
|
||||
[3, 5010, 5000, 4986, 5010, 6172, 0, 1],
|
||||
[4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
# Test 26: Sell with signal sell in candle 3 (ROI at signal candle)
|
||||
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
|
||||
# Sell-signal wins over stoploss
|
||||
tc26 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4986, 4600, 6172, 0, 0],
|
||||
[3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal
|
||||
[4, 5010, 4987, 4855, 4995, 6172, 0, 0],
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
# Test 27: Sell with signal sell in candle 3 (ROI at signal candle)
|
||||
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal
|
||||
# TODO: figure out if sell-signal should win over ROI
|
||||
# Sell-signal wins over stoploss
|
||||
tc27 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4986, 4600, 6172, 0, 0],
|
||||
[3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal
|
||||
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
@@ -351,6 +463,13 @@ TESTS = [
|
||||
tc18,
|
||||
tc19,
|
||||
tc20,
|
||||
tc21,
|
||||
tc22,
|
||||
tc23,
|
||||
tc24,
|
||||
tc25,
|
||||
tc26,
|
||||
tc27,
|
||||
]
|
||||
|
||||
|
||||
|
@@ -58,7 +58,7 @@ def whitelist_conf_2(default_conf):
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def whitelist_conf_3(default_conf):
|
||||
def whitelist_conf_agefilter(default_conf):
|
||||
default_conf['stake_currency'] = 'BTC'
|
||||
default_conf['exchange']['pair_whitelist'] = [
|
||||
'ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC',
|
||||
@@ -246,7 +246,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
{"method": "PrecisionFilter"},
|
||||
{"method": "PriceFilter", "low_price_ratio": 0.03},
|
||||
{"method": "SpreadFilter", "max_spread_ratio": 0.005},
|
||||
{"method": "ShuffleFilter"}],
|
||||
{"method": "ShuffleFilter"}, {"method": "PerformanceFilter"}],
|
||||
"ETH", []),
|
||||
# AgeFilter and VolumePairList (require 2 days only, all should pass age test)
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
@@ -326,6 +326,13 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
# ShuffleFilter only
|
||||
([{"method": "ShuffleFilter", "seed": 42}],
|
||||
"BTC", 'filter_at_the_beginning'), # OperationalException expected
|
||||
# PerformanceFilter after StaticPairList
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "PerformanceFilter"}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
|
||||
# PerformanceFilter only
|
||||
([{"method": "PerformanceFilter"}],
|
||||
"BTC", 'filter_at_the_beginning'), # OperationalException expected
|
||||
# SpreadFilter after StaticPairList
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "SpreadFilter", "max_spread_ratio": 0.005}],
|
||||
@@ -340,6 +347,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "PriceFilter", "low_price_ratio": 0.02}],
|
||||
"USDT", ['ETH/USDT', 'NANO/USDT']),
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"min_rate_of_change": 0.01, "refresh_period": 1440}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
|
||||
])
|
||||
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
||||
ohlcv_history_list, pairlists, base_currency,
|
||||
@@ -366,6 +377,11 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
|
||||
)
|
||||
|
||||
# Provide for PerformanceFilter's dependency
|
||||
mocker.patch.multiple('freqtrade.persistence.Trade',
|
||||
get_overall_performance=MagicMock(return_value=[])
|
||||
)
|
||||
|
||||
# Set whitelist_result to None if pairlist is invalid and should produce exception
|
||||
if whitelist_result == 'filter_at_the_beginning':
|
||||
with pytest.raises(OperationalException,
|
||||
@@ -409,7 +425,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
assert not log_has(logmsg, caplog)
|
||||
|
||||
|
||||
def test_PrecisionFilter_error(mocker, whitelist_conf, tickers) -> None:
|
||||
def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
|
||||
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}]
|
||||
del whitelist_conf['stoploss']
|
||||
|
||||
@@ -482,7 +498,7 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS)
|
||||
def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, markets, pairlist, tickers):
|
||||
def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, tickers):
|
||||
whitelist_conf['pairlists'][0]['method'] = pairlist
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
@@ -498,7 +514,7 @@ def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, markets, pa
|
||||
pairlist_handler._whitelist_for_active_markets(['ETH/BTC'])
|
||||
|
||||
|
||||
def test_volumepairlist_invalid_sortvalue(mocker, markets, whitelist_conf):
|
||||
def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf):
|
||||
whitelist_conf['pairlists'][0].update({"sort_key": "asdf"})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
@@ -528,7 +544,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
|
||||
assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf
|
||||
|
||||
|
||||
def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers, caplog):
|
||||
def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'AgeFilter', 'min_days_listed': -1}]
|
||||
|
||||
@@ -543,7 +559,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers, caplog):
|
||||
def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'AgeFilter', 'min_days_listed': 99999}]
|
||||
|
||||
@@ -559,7 +575,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list):
|
||||
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history_list):
|
||||
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
@@ -571,7 +587,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
|
||||
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_3)
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count > 0
|
||||
@@ -582,6 +598,62 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
|
||||
|
||||
|
||||
def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'RangeStabilityFilter', 'lookback_days': 99999}]
|
||||
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'RangeStabilityFilter requires lookback_days to not exceed '
|
||||
r'exchange max request size \([0-9]+\)'):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'RangeStabilityFilter', 'lookback_days': 0}]
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match='RangeStabilityFilter requires lookback_days to be >= 1'):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('min_rate_of_change,expected_length', [
|
||||
(0.01, 5),
|
||||
(0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist.
|
||||
])
|
||||
def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list,
|
||||
min_rate_of_change, expected_length):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'RangeStabilityFilter', 'lookback_days': 2,
|
||||
'min_rate_of_change': min_rate_of_change}]
|
||||
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == expected_length
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count > 0
|
||||
|
||||
previous_call_count = freqtrade.exchange.get_historic_ohlcv.call_count
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == expected_length
|
||||
# Should not have increased since first call.
|
||||
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [
|
||||
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010,
|
||||
"max_price": 1.0},
|
||||
@@ -617,6 +689,11 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
|
||||
None,
|
||||
"PriceFilter requires max_price to be >= 0"
|
||||
), # OperationalException expected
|
||||
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01},
|
||||
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
|
||||
"0.01 over the last days.'}]",
|
||||
None
|
||||
),
|
||||
])
|
||||
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
|
||||
desc_expected, exception_expected):
|
||||
@@ -636,7 +713,7 @@ def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
|
||||
|
||||
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
|
||||
def test_pairlistmanager_no_pairlist(mocker, whitelist_conf):
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
|
||||
whitelist_conf['pairlists'] = []
|
||||
@@ -644,3 +721,63 @@ def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"No Pairlist Handlers defined"):
|
||||
get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pairlists,pair_allowlist,overall_performance,allowlist_result", [
|
||||
# No trades yet
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], [], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
|
||||
# Happy path: Descending order, all values filled
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC'],
|
||||
[{'pair': 'TKN/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}],
|
||||
['TKN/BTC', 'ETH/BTC']),
|
||||
# Performance data outside allow list ignored
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC'],
|
||||
[{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3},
|
||||
{'pair': 'ETH/BTC', 'profit': 4, 'count': 2}],
|
||||
['ETH/BTC', 'TKN/BTC']),
|
||||
# Partial performance data missing and sorted between positive and negative profit
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
|
||||
[{'pair': 'ETH/BTC', 'profit': -5, 'count': 100},
|
||||
{'pair': 'TKN/BTC', 'profit': 4, 'count': 2}],
|
||||
['TKN/BTC', 'LTC/BTC', 'ETH/BTC']),
|
||||
# Tie in performance data broken by count (ascending)
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
|
||||
[{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 101},
|
||||
{'pair': 'TKN/BTC', 'profit': -5.01, 'count': 2},
|
||||
{'pair': 'ETH/BTC', 'profit': -5.01, 'count': 100}],
|
||||
['TKN/BTC', 'ETH/BTC', 'LTC/BTC']),
|
||||
# Tie in performance and count, broken by alphabetical sort
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
|
||||
[{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 1},
|
||||
{'pair': 'TKN/BTC', 'profit': -5.01, 'count': 1},
|
||||
{'pair': 'ETH/BTC', 'profit': -5.01, 'count': 1}],
|
||||
['ETH/BTC', 'LTC/BTC', 'TKN/BTC']),
|
||||
])
|
||||
def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance,
|
||||
allowlist_result, tickers, markets, ohlcv_history_list):
|
||||
allowlist_conf = whitelist_conf
|
||||
allowlist_conf['pairlists'] = pairlists
|
||||
allowlist_conf['exchange']['pair_whitelist'] = pair_allowlist
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, allowlist_conf)
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
get_tickers=tickers,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
|
||||
)
|
||||
mocker.patch.multiple('freqtrade.persistence.Trade',
|
||||
get_overall_performance=MagicMock(return_value=overall_performance),
|
||||
)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
allowlist = freqtrade.pairlists.whitelist
|
||||
assert allowlist == allowlist_result
|
||||
|
@@ -868,7 +868,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
||||
assert trade.open_rate == 0.0001
|
||||
|
||||
# Test buy pair not with stakes
|
||||
with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
|
||||
with pytest.raises(RPCException,
|
||||
match=r'Wrong pair selected. Only pairs with stake-currency.*'):
|
||||
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
|
||||
pair = 'XRP/BTC'
|
||||
|
||||
|
@@ -58,7 +58,6 @@ def test__init__(default_conf, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
telegram = Telegram(get_patched_freqtradebot(mocker, default_conf))
|
||||
assert telegram._updater is None
|
||||
assert telegram._config == default_conf
|
||||
|
||||
|
||||
@@ -339,6 +338,18 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# Reset msg_mock
|
||||
msg_mock.reset_mock()
|
||||
context.args = []
|
||||
telegram._daily(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Daily' in msg_mock.call_args_list[0][0][0]
|
||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# Reset msg_mock
|
||||
msg_mock.reset_mock()
|
||||
freqtradebot.config['max_open_trades'] = 2
|
||||
@@ -882,7 +893,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||
context.args = []
|
||||
telegram._forcesell(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
|
||||
assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# Invalid argument
|
||||
msg_mock.reset_mock()
|
||||
@@ -1224,8 +1235,14 @@ def test_telegram_trades(mocker, update, default_conf, fee):
|
||||
telegram._trades(update=update, context=context)
|
||||
assert "<b>0 recent trades</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert "<pre>" not in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
context.args = ['hello']
|
||||
telegram._trades(update=update, context=context)
|
||||
assert "<b>0 recent trades</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert "<pre>" not in msg_mock.call_args_list[0][0][0]
|
||||
msg_mock.reset_mock()
|
||||
|
||||
create_mock_trades(fee)
|
||||
|
||||
context = MagicMock()
|
||||
@@ -1252,7 +1269,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
|
||||
context.args = []
|
||||
|
||||
telegram._delete_trade(update=update, context=context)
|
||||
assert "invalid argument" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
create_mock_trades(fee)
|
||||
|
@@ -16,6 +16,7 @@ from freqtrade.configuration import (Configuration, check_exchange, remove_crede
|
||||
from freqtrade.configuration.config_validation import validate_config_schema
|
||||
from freqtrade.configuration.deprecated_settings import (check_conflicting_settings,
|
||||
process_deprecated_setting,
|
||||
process_removed_setting,
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.load_config import load_config_file, log_config_error_range
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
||||
@@ -663,7 +664,7 @@ def test_set_loggers() -> None:
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
def test_set_loggers_syslog(mocker):
|
||||
def test_set_loggers_syslog():
|
||||
logger = logging.getLogger()
|
||||
orig_handlers = logger.handlers
|
||||
logger.handlers = []
|
||||
@@ -678,10 +679,38 @@ def test_set_loggers_syslog(mocker):
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
# reset handlers to not break pytest
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
def test_set_loggers_Filehandler(tmpdir):
|
||||
logger = logging.getLogger()
|
||||
orig_handlers = logger.handlers
|
||||
logger.handlers = []
|
||||
logfile = Path(tmpdir) / 'ft_logfile.log'
|
||||
config = {'verbosity': 2,
|
||||
'logfile': str(logfile),
|
||||
}
|
||||
|
||||
setup_logging_pre()
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
# reset handlers to not break pytest
|
||||
if logfile.exists:
|
||||
logfile.unlink()
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.")
|
||||
def test_set_loggers_journald(mocker):
|
||||
logger = logging.getLogger()
|
||||
@@ -812,6 +841,21 @@ def test_validate_edge(edge_conf):
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
|
||||
def test_validate_edge2(edge_conf):
|
||||
edge_conf.update({"ask_strategy": {
|
||||
"use_sell_signal": True,
|
||||
}})
|
||||
# Passes test
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
edge_conf.update({"ask_strategy": {
|
||||
"use_sell_signal": False,
|
||||
}})
|
||||
with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, "
|
||||
"otherwise no sells will happen."):
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
|
||||
def test_validate_whitelist(default_conf):
|
||||
default_conf['runmode'] = RunMode.DRY_RUN
|
||||
# Test regular case - has whitelist and uses StaticPairlist
|
||||
@@ -1018,13 +1062,11 @@ def test_pairlist_resolving_fallback(mocker):
|
||||
assert config['datadir'] == Path.cwd() / "user_data/data/binance"
|
||||
|
||||
|
||||
@pytest.mark.skip(reason='Currently no deprecated / moved sections')
|
||||
# The below is kept as a sample for the future.
|
||||
@pytest.mark.parametrize("setting", [
|
||||
("ask_strategy", "use_sell_signal", True,
|
||||
"experimental", "use_sell_signal", False),
|
||||
("ask_strategy", "sell_profit_only", False,
|
||||
"experimental", "sell_profit_only", True),
|
||||
("ask_strategy", "ignore_roi_if_buy_signal", False,
|
||||
"experimental", "ignore_roi_if_buy_signal", True),
|
||||
])
|
||||
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
@@ -1054,7 +1096,27 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca
|
||||
assert default_conf[setting[0]][setting[1]] == setting[5]
|
||||
|
||||
|
||||
def test_process_deprecated_setting_edge(mocker, edge_conf, caplog):
|
||||
@pytest.mark.parametrize("setting", [
|
||||
("experimental", "use_sell_signal", False),
|
||||
("experimental", "sell_profit_only", True),
|
||||
("experimental", "ignore_roi_if_buy_signal", True),
|
||||
])
|
||||
def test_process_removed_settings(mocker, default_conf, setting):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
# Create sections for new and deprecated settings
|
||||
# (they may not exist in the config)
|
||||
default_conf[setting[0]] = {}
|
||||
# Assign removed setting
|
||||
default_conf[setting[0]][setting[1]] = setting[2]
|
||||
|
||||
# New and deprecated settings are conflicting ones
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Setting .* has been moved'):
|
||||
process_temporary_deprecated_settings(default_conf)
|
||||
|
||||
|
||||
def test_process_deprecated_setting_edge(mocker, edge_conf):
|
||||
patched_configuration_load_config_file(mocker, edge_conf)
|
||||
edge_conf.update({'edge': {
|
||||
'enabled': True,
|
||||
@@ -1153,6 +1215,30 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
|
||||
assert default_conf['sectionA']['new_setting'] == 'valA'
|
||||
|
||||
|
||||
def test_process_removed_setting(mocker, default_conf, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
# Create sections for new and deprecated settings
|
||||
# (they may not exist in the config)
|
||||
default_conf['sectionA'] = {}
|
||||
default_conf['sectionB'] = {}
|
||||
# Assign new setting
|
||||
default_conf['sectionB']['somesetting'] = 'valA'
|
||||
|
||||
# Only new setting exists (nothing should happen)
|
||||
process_removed_setting(default_conf,
|
||||
'sectionA', 'somesetting',
|
||||
'sectionB', 'somesetting')
|
||||
# Assign removed setting
|
||||
default_conf['sectionA']['somesetting'] = 'valB'
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"Setting .* has been moved"):
|
||||
process_removed_setting(default_conf,
|
||||
'sectionA', 'somesetting',
|
||||
'sectionB', 'somesetting')
|
||||
|
||||
|
||||
def test_process_deprecated_ticker_interval(mocker, default_conf, caplog):
|
||||
message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval."
|
||||
config = deepcopy(default_conf)
|
||||
|
@@ -1074,6 +1074,12 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order))
|
||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
||||
|
||||
# Fail to get price...
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_buy_rate', MagicMock(return_value=0.0))
|
||||
|
||||
with pytest.raises(PricingError, match="Could not determine buy price."):
|
||||
freqtrade.execute_buy(pair, stake_amount)
|
||||
|
||||
|
||||
def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
@@ -3556,7 +3562,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b
|
||||
# Test if buy-signal is absent
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
|
||||
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fee, caplog, mocker):
|
||||
|
Reference in New Issue
Block a user