Merge branch 'freqtrade:develop' into RangeStabilityFilterMax
This commit is contained in:
@@ -13,7 +13,7 @@ from freqtrade.commands import (start_convert_data, start_create_userdir, start_
|
||||
start_list_data, start_list_exchanges, start_list_hyperopts,
|
||||
start_list_markets, start_list_strategies, start_list_timeframes,
|
||||
start_new_hyperopt, start_new_strategy, start_show_trades,
|
||||
start_test_pairlist, start_trading)
|
||||
start_test_pairlist, start_trading, start_webserver)
|
||||
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
|
||||
get_ui_download_url, read_ui_version)
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
@@ -26,7 +26,7 @@ from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
|
||||
def test_setup_utils_configuration():
|
||||
args = [
|
||||
'list-exchanges', '--config', 'config_bittrex.json.example',
|
||||
'list-exchanges', '--config', 'config_examples/config_bittrex.example.json',
|
||||
]
|
||||
|
||||
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
|
||||
@@ -45,7 +45,7 @@ def test_start_trading_fail(mocker, caplog):
|
||||
exitmock = mocker.patch("freqtrade.worker.Worker.exit", MagicMock())
|
||||
args = [
|
||||
'trade',
|
||||
'-c', 'config_bittrex.json.example'
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
]
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 1
|
||||
@@ -58,6 +58,18 @@ def test_start_trading_fail(mocker, caplog):
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
|
||||
|
||||
def test_start_webserver(mocker, caplog):
|
||||
|
||||
api_server_mock = mocker.patch("freqtrade.rpc.api_server.ApiServer", )
|
||||
|
||||
args = [
|
||||
'webserver',
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
]
|
||||
start_webserver(get_args(args))
|
||||
assert api_server_mock.call_count == 1
|
||||
|
||||
|
||||
def test_list_exchanges(capsys):
|
||||
|
||||
args = [
|
||||
@@ -127,10 +139,10 @@ def test_list_timeframes(mocker, capsys):
|
||||
match=r"This command requires a configured exchange.*"):
|
||||
start_list_timeframes(pargs)
|
||||
|
||||
# Test with --config config_bittrex.json.example
|
||||
# Test with --config config_examples/config_bittrex.example.json
|
||||
args = [
|
||||
"list-timeframes",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
@@ -174,7 +186,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
# Test with --one-column
|
||||
args = [
|
||||
"list-timeframes",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--one-column",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
@@ -214,10 +226,10 @@ def test_list_markets(mocker, markets, capsys):
|
||||
match=r"This command requires a configured exchange.*"):
|
||||
start_list_markets(pargs, False)
|
||||
|
||||
# Test with --config config_bittrex.json.example
|
||||
# Test with --config config_examples/config_bittrex.example.json
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -244,7 +256,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test with --all: all markets
|
||||
args = [
|
||||
"list-markets", "--all",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -257,7 +269,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test list-pairs subcommand: active pairs
|
||||
args = [
|
||||
"list-pairs",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), True)
|
||||
@@ -269,7 +281,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test list-pairs subcommand with --all: all pairs
|
||||
args = [
|
||||
"list-pairs", "--all",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), True)
|
||||
@@ -282,7 +294,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, base=ETH, LTC
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "ETH", "LTC",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -295,7 +307,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, base=LTC
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -308,7 +320,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, quote=USDT, USD
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--quote", "USDT", "USD",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -321,7 +333,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, quote=USDT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--quote", "USDT",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -334,7 +346,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, base=LTC, quote=USDT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC", "--quote", "USDT",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -347,7 +359,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active pairs, base=LTC, quote=USDT
|
||||
args = [
|
||||
"list-pairs",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC", "--quote", "USD",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -360,7 +372,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, base=LTC, quote=USDT, NONEXISTENT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC", "--quote", "USDT", "NONEXISTENT",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -373,7 +385,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# active markets, base=LTC, quote=NONEXISTENT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||
"--print-list",
|
||||
]
|
||||
@@ -386,7 +398,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test tabular output
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
@@ -396,7 +408,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test tabular output, no markets found
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -408,7 +420,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test --print-json
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-json"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -420,7 +432,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test --print-csv
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--print-csv"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -432,7 +444,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test --one-column
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--one-column"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -444,7 +456,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
# Test --one-column
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_bittrex.json.example',
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
"--one-column"
|
||||
]
|
||||
with pytest.raises(OperationalException, match=r"Cannot get markets.*"):
|
||||
@@ -887,7 +899,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_bittrex.json.example'
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
]
|
||||
|
||||
start_test_pairlist(get_args(args))
|
||||
@@ -901,7 +913,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_bittrex.json.example',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'--one-column',
|
||||
]
|
||||
start_test_pairlist(get_args(args))
|
||||
@@ -910,7 +922,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_bittrex.json.example',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'--print-json',
|
||||
]
|
||||
start_test_pairlist(get_args(args))
|
||||
|
@@ -1783,14 +1783,14 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
|
||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
||||
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
assert exchange.get_buy_rate('ETH/BTC', False) == expected
|
||||
assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected
|
||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
# Running a 2nd time with Refresh on!
|
||||
caplog.clear()
|
||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
||||
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
|
||||
@@ -1825,12 +1825,12 @@ def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||
|
||||
# Test regular mode
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
rate = exchange.get_sell_rate(pair, True)
|
||||
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
# Use caching
|
||||
rate = exchange.get_sell_rate(pair, False)
|
||||
rate = exchange.get_rate(pair, refresh=False, side="sell")
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
@@ -1848,11 +1848,11 @@ def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, o
|
||||
pair = "ETH/BTC"
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
rate = exchange.get_sell_rate(pair, True)
|
||||
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
rate = exchange.get_sell_rate(pair, False)
|
||||
rate = exchange.get_rate(pair, refresh=False, side="sell")
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
@@ -1868,7 +1868,7 @@ def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
|
||||
return_value={'bids': [[]], 'asks': [[]]})
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
with pytest.raises(PricingError):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
exchange.get_rate(pair, refresh=True, side="sell")
|
||||
assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*",
|
||||
caplog)
|
||||
|
||||
@@ -1881,18 +1881,18 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog):
|
||||
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
exchange.get_rate(pair, refresh=True, side="sell")
|
||||
|
||||
exchange._config['ask_strategy']['price_side'] = 'bid'
|
||||
assert exchange.get_sell_rate(pair, True) == 0.12
|
||||
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12
|
||||
# Reverse sides
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
exchange.get_rate(pair, refresh=True, side="sell")
|
||||
|
||||
exchange._config['ask_strategy']['price_side'] = 'ask'
|
||||
assert exchange.get_sell_rate(pair, True) == 0.13
|
||||
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
|
||||
|
||||
|
||||
def make_fetch_ohlcv_mock(data):
|
||||
@@ -2203,7 +2203,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||
({'status': 'canceled', 'filled': 10.0}, False),
|
||||
({'status': 'unknown', 'filled': 10.0}, False),
|
||||
({'result': 'testest123'}, False),
|
||||
])
|
||||
])
|
||||
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
assert exchange.check_order_canceled_empty(order) == result
|
||||
|
@@ -346,6 +346,20 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||
assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC'])
|
||||
|
||||
|
||||
def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.check_abort()
|
||||
|
||||
backtesting.abort = True
|
||||
|
||||
with pytest.raises(DependencyException, match="Stop requested"):
|
||||
backtesting.check_abort()
|
||||
# abort flag resets
|
||||
assert backtesting.abort is False
|
||||
assert backtesting.progress.progress == 0
|
||||
|
||||
|
||||
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
def get_timerange(input1):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
@@ -496,6 +510,17 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is not None
|
||||
|
||||
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade
|
||||
assert trade.stake_amount == 123.5
|
||||
|
||||
# In case of error - use proposed stake
|
||||
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade
|
||||
assert trade.stake_amount == 495
|
||||
|
||||
# Stake-amount too high!
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
|
||||
|
||||
|
@@ -79,7 +79,8 @@ def whitelist_conf_agefilter(default_conf):
|
||||
},
|
||||
{
|
||||
"method": "AgeFilter",
|
||||
"min_days_listed": 2
|
||||
"min_days_listed": 2,
|
||||
"max_days_listed": 100
|
||||
}
|
||||
]
|
||||
return default_conf
|
||||
@@ -302,7 +303,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
# No pair for ETH, all handlers
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 2},
|
||||
{"method": "AgeFilter", "min_days_listed": 2, "max_days_listed": None},
|
||||
{"method": "PrecisionFilter"},
|
||||
{"method": "PriceFilter", "low_price_ratio": 0.03},
|
||||
{"method": "SpreadFilter", "max_spread_ratio": 0.005},
|
||||
@@ -310,12 +311,24 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
"ETH", []),
|
||||
# AgeFilter and VolumePairList (require 2 days only, all should pass age test)
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 2}],
|
||||
{"method": "AgeFilter", "min_days_listed": 2, "max_days_listed": 100}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']),
|
||||
# AgeFilter and VolumePairList (require 10 days, all should fail age test)
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 10}],
|
||||
{"method": "AgeFilter", "min_days_listed": 10, "max_days_listed": None}],
|
||||
"BTC", []),
|
||||
# AgeFilter and VolumePairList (all pair listed > 2, all should fail age test)
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 1, "max_days_listed": 2}],
|
||||
"BTC", []),
|
||||
# AgeFilter and VolumePairList LTC/BTC has 6 candles - removes all
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 4, "max_days_listed": 5}],
|
||||
"BTC", []),
|
||||
# AgeFilter and VolumePairList LTC/BTC has 6 candles - passes
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "AgeFilter", "min_days_listed": 4, "max_days_listed": 10}],
|
||||
"BTC", ["LTC/BTC"]),
|
||||
# Precisionfilter and quote volume
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||
{"method": "PrecisionFilter"}],
|
||||
@@ -417,7 +430,19 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "VolatilityFilter", "lookback_days": 3,
|
||||
"min_volatility": 0.002, "max_volatility": 0.004, "refresh_period": 1440}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC'])
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC']),
|
||||
# VolumePairList with no offset = unchanged pairlist
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "OffsetFilter", "offset": 0}],
|
||||
"USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']),
|
||||
# VolumePairList with offset = 2
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "OffsetFilter", "offset": 2}],
|
||||
"USDT", ['ADAHALF/USDT', 'ADADOUBLE/USDT']),
|
||||
# VolumePairList with higher offset, than total pairlist
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "OffsetFilter", "offset": 100}],
|
||||
"USDT", [])
|
||||
])
|
||||
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
||||
ohlcv_history, pairlists, base_currency,
|
||||
@@ -431,7 +456,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history.append(ohlcv_history),
|
||||
('XRP/BTC', '1d'): ohlcv_history,
|
||||
('HOT/BTC', '1d'): ohlcv_history_high_vola,
|
||||
}
|
||||
@@ -480,9 +505,13 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
|
||||
for pairlist in pairlists:
|
||||
if pairlist['method'] == 'AgeFilter' and pairlist['min_days_listed'] and \
|
||||
len(ohlcv_history) <= pairlist['min_days_listed']:
|
||||
len(ohlcv_history) < pairlist['min_days_listed']:
|
||||
assert log_has_re(r'^Removed .* from whitelist, because age .* is less than '
|
||||
r'.* day.*', caplog)
|
||||
if pairlist['method'] == 'AgeFilter' and pairlist['max_days_listed'] and \
|
||||
len(ohlcv_history) > pairlist['max_days_listed']:
|
||||
assert log_has_re(r'^Removed .* from whitelist, because age .* is less than '
|
||||
r'.* day.* or more than .* day', caplog)
|
||||
if pairlist['method'] == 'PrecisionFilter' and whitelist_result:
|
||||
assert log_has_re(r'^Removed .* from whitelist, because stop price .* '
|
||||
r'would be <= stop limit.*', caplog)
|
||||
@@ -507,6 +536,105 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
assert log_has_re(r'^Removed .* from whitelist, because volatility.*$', caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pairlists,base_currency,volumefilter_result", [
|
||||
# default refresh of 1800 to small for daily candle lookback
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_days": 1}],
|
||||
"BTC", "default_refresh_too_short"), # OperationalException expected
|
||||
# ambigous configuration with lookback days and period
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_days": 1, "lookback_period": 1}],
|
||||
"BTC", "lookback_days_and_period"), # OperationalException expected
|
||||
# negative lookback period
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_timeframe": "1d", "lookback_period": -1}],
|
||||
"BTC", "lookback_period_negative"), # OperationalException expected
|
||||
# lookback range exceedes exchange limit
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_timeframe": "1m", "lookback_period": 2000, "refresh_period": 3600}],
|
||||
"BTC", 'lookback_exceeds_exchange_request_size'), # OperationalException expected
|
||||
# expecing pairs as given
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
||||
"BTC", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||
# expecting pairs from default tickers, because 1h candles are not available
|
||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
|
||||
])
|
||||
def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history,
|
||||
pairlists, base_currency, volumefilter_result, caplog) -> None:
|
||||
whitelist_conf['pairlists'] = pairlists
|
||||
whitelist_conf['stake_currency'] = base_currency
|
||||
|
||||
ohlcv_history_high_vola = ohlcv_history.copy()
|
||||
ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090
|
||||
|
||||
# create candles for medium overall volume with last candle high volume
|
||||
ohlcv_history_medium_volume = ohlcv_history.copy()
|
||||
ohlcv_history_medium_volume.loc[ohlcv_history_medium_volume.index == 2, 'volume'] = 5
|
||||
|
||||
# create candles for high volume with all candles high volume
|
||||
ohlcv_history_high_volume = ohlcv_history.copy()
|
||||
ohlcv_history_high_volume.loc[:, 'volume'] = 10
|
||||
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history_medium_volume,
|
||||
('XRP/BTC', '1d'): ohlcv_history_high_vola,
|
||||
('HOT/BTC', '1d'): ohlcv_history_high_volume,
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
|
||||
if volumefilter_result == 'default_refresh_too_short':
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Refresh period of [0-9]+ seconds is smaller than one timeframe '
|
||||
r'of [0-9]+.*\. Please adjust refresh_period to at least [0-9]+ '
|
||||
r'and restart the bot\.'):
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
return
|
||||
elif volumefilter_result == 'lookback_days_and_period':
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Ambigous configuration: lookback_days and lookback_period both '
|
||||
r'set in pairlist config\..*'):
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
elif volumefilter_result == 'lookback_period_negative':
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'VolumeFilter requires lookback_period to be >= 0'):
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
elif volumefilter_result == 'lookback_exceeds_exchange_request_size':
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'VolumeFilter requires lookback_period to not exceed '
|
||||
r'exchange max request size \([0-9]+\)'):
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
else:
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_tickers=tickers,
|
||||
markets=PropertyMock(return_value=shitcoinmarkets)
|
||||
)
|
||||
|
||||
# remove ohlcv when looback_timeframe != 1d
|
||||
# to enforce fallback to ticker data
|
||||
if 'lookback_timeframe' in pairlists[0]:
|
||||
if pairlists[0]['lookback_timeframe'] != '1d':
|
||||
ohlcv_data = []
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||
)
|
||||
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
whitelist = freqtrade.pairlists.whitelist
|
||||
|
||||
assert isinstance(whitelist, list)
|
||||
assert whitelist == volumefilter_result
|
||||
|
||||
|
||||
def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
|
||||
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}]
|
||||
del whitelist_conf['stoploss']
|
||||
@@ -650,6 +778,22 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
def test_agefilter_max_days_lower_than_min_days(mocker, default_conf, markets, tickers):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'AgeFilter', 'min_days_listed': 3,
|
||||
"max_days_listed": 2}]
|
||||
|
||||
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'AgeFilter max_days_listed <= min_days_listed not permitted'):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
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}]
|
||||
@@ -695,6 +839,18 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == previous_call_count + 1
|
||||
|
||||
|
||||
def test_OffsetFilter_error(mocker, whitelist_conf) -> None:
|
||||
whitelist_conf['pairlists'] = (
|
||||
[{"method": "StaticPairList"}, {"method": "OffsetFilter", "offset": -1}]
|
||||
)
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'OffsetFilter requires offset to be >= 0'):
|
||||
PairListManager(MagicMock, whitelist_conf)
|
||||
|
||||
|
||||
def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'RangeStabilityFilter', 'lookback_days': 99999}]
|
||||
|
@@ -109,7 +109,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
results = rpc._rpc_trade_status()
|
||||
assert isnan(results[0]['current_profit'])
|
||||
@@ -217,7 +217,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
assert '-0.41% (-0.06)' == result[0][3]
|
||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert 'instantly' == result[0][2]
|
||||
@@ -427,7 +427,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
# Test non-available pair
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert stats['trade_count'] == 2
|
||||
@@ -886,7 +886,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
||||
|
||||
# Test not buying
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
freqtradebot.config['stake_amount'] = 0.0000001
|
||||
freqtradebot.config['stake_amount'] = 0
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'TKN/BTC'
|
||||
|
@@ -2,6 +2,7 @@
|
||||
Unit test file for rpc/api_server.py
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
@@ -16,7 +17,7 @@ from requests.auth import _basic_auth_str
|
||||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.enums import RunMode, State
|
||||
from freqtrade.exceptions import ExchangeError
|
||||
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
|
||||
from freqtrade.loggers import setup_logging, setup_logging_pre
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc import RPC
|
||||
@@ -48,9 +49,13 @@ def botclient(default_conf, mocker):
|
||||
ftbot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(ftbot)
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock())
|
||||
apiserver = ApiServer(rpc, default_conf)
|
||||
yield ftbot, TestClient(apiserver.app)
|
||||
# Cleanup ... ?
|
||||
try:
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(rpc)
|
||||
yield ftbot, TestClient(apiserver.app)
|
||||
# Cleanup ... ?
|
||||
finally:
|
||||
ApiServer.shutdown()
|
||||
|
||||
|
||||
def client_post(client, url, data={}):
|
||||
@@ -235,8 +240,13 @@ def test_api__init__(default_conf, mocker):
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
|
||||
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
assert apiserver._config == default_conf
|
||||
with pytest.raises(OperationalException, match="RPC Handler already attached."):
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
|
||||
ApiServer.shutdown()
|
||||
|
||||
|
||||
def test_api_UvicornServer(mocker):
|
||||
@@ -298,15 +308,21 @@ def test_api_run(default_conf, mocker, caplog):
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
|
||||
server_mock = MagicMock()
|
||||
server_inst_mock = MagicMock()
|
||||
server_inst_mock.run_in_thread = MagicMock()
|
||||
server_inst_mock.run = MagicMock()
|
||||
server_mock = MagicMock(return_value=server_inst_mock)
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
|
||||
|
||||
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
|
||||
assert server_mock.call_count == 1
|
||||
assert apiserver._config == default_conf
|
||||
apiserver.start_api()
|
||||
assert server_mock.call_count == 2
|
||||
assert server_inst_mock.run_in_thread.call_count == 2
|
||||
assert server_inst_mock.run.call_count == 0
|
||||
assert server_mock.call_args_list[0][0][0].host == "127.0.0.1"
|
||||
assert server_mock.call_args_list[0][0][0].port == 8080
|
||||
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
|
||||
@@ -325,6 +341,8 @@ def test_api_run(default_conf, mocker, caplog):
|
||||
apiserver.start_api()
|
||||
|
||||
assert server_mock.call_count == 1
|
||||
assert server_inst_mock.run_in_thread.call_count == 1
|
||||
assert server_inst_mock.run.call_count == 0
|
||||
assert server_mock.call_args_list[0][0][0].host == "0.0.0.0"
|
||||
assert server_mock.call_args_list[0][0][0].port == 8089
|
||||
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
|
||||
@@ -338,12 +356,24 @@ def test_api_run(default_conf, mocker, caplog):
|
||||
"Please make sure that this is intentional!", caplog)
|
||||
assert log_has_re("SECURITY WARNING - `jwt_secret_key` seems to be default.*", caplog)
|
||||
|
||||
server_mock.reset_mock()
|
||||
apiserver._standalone = True
|
||||
apiserver.start_api()
|
||||
assert server_inst_mock.run_in_thread.call_count == 0
|
||||
assert server_inst_mock.run.call_count == 1
|
||||
|
||||
apiserver1 = ApiServer(default_conf)
|
||||
assert id(apiserver1) == id(apiserver)
|
||||
|
||||
apiserver._standalone = False
|
||||
|
||||
# Test crashing API server
|
||||
caplog.clear()
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer',
|
||||
MagicMock(side_effect=Exception))
|
||||
apiserver.start_api()
|
||||
assert log_has("Api server failed to start.", caplog)
|
||||
ApiServer.shutdown()
|
||||
|
||||
|
||||
def test_api_cleanup(default_conf, mocker, caplog):
|
||||
@@ -359,11 +389,13 @@ def test_api_cleanup(default_conf, mocker, caplog):
|
||||
server_mock.cleanup = MagicMock()
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
|
||||
|
||||
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
|
||||
apiserver.cleanup()
|
||||
assert apiserver._server.cleanup.call_count == 1
|
||||
assert log_has("Stopping API Server", caplog)
|
||||
ApiServer.shutdown()
|
||||
|
||||
|
||||
def test_api_reloadconf(botclient):
|
||||
@@ -677,12 +709,16 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||
'profit_all_ratio_mean': -0.6641100666666667,
|
||||
'profit_all_percent_sum': -398.47,
|
||||
'profit_all_ratio_sum': -3.9846604,
|
||||
'profit_all_percent': -4.41,
|
||||
'profit_all_ratio': -0.044063014216106644,
|
||||
'profit_closed_coin': 0.00073913,
|
||||
'profit_closed_fiat': 9.124559849999999,
|
||||
'profit_closed_ratio_mean': 0.0075,
|
||||
'profit_closed_percent_mean': 0.75,
|
||||
'profit_closed_ratio_sum': 0.015,
|
||||
'profit_closed_percent_sum': 1.5,
|
||||
'profit_closed_ratio': 7.391275897987988e-07,
|
||||
'profit_closed_percent': 0.0,
|
||||
'trade_count': 6,
|
||||
'closed_trade_count': 2,
|
||||
'winning_trades': 2,
|
||||
@@ -843,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
@@ -1217,3 +1253,108 @@ def test_list_available_pairs(botclient):
|
||||
assert rc.json()['length'] == 1
|
||||
assert rc.json()['pairs'] == ['XRP/ETH']
|
||||
assert len(rc.json()['pair_interval']) == 1
|
||||
|
||||
|
||||
def test_api_backtesting(botclient, mocker, fee, caplog):
|
||||
ftbot, client = botclient
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
|
||||
# Backtesting not started yet
|
||||
rc = client_get(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
|
||||
result = rc.json()
|
||||
assert result['status'] == 'not_started'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest not yet executed'
|
||||
assert result['progress'] == 0
|
||||
|
||||
# Reset backtesting
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert result['status'] == 'reset'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest reset'
|
||||
|
||||
# start backtesting
|
||||
data = {
|
||||
"strategy": "DefaultStrategy",
|
||||
"timeframe": "5m",
|
||||
"timerange": "20180110-20180111",
|
||||
"max_open_trades": 3,
|
||||
"stake_amount": 100,
|
||||
"dry_run_wallet": 1000,
|
||||
"enable_protections": False
|
||||
}
|
||||
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
|
||||
assert result['status'] == 'running'
|
||||
assert result['progress'] == 0
|
||||
assert result['running']
|
||||
assert result['status_msg'] == 'Backtest started'
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
|
||||
result = rc.json()
|
||||
assert result['status'] == 'ended'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest ended'
|
||||
assert result['progress'] == 1
|
||||
assert result['backtest_result']
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/backtest/abort")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert result['status'] == 'not_running'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest ended'
|
||||
|
||||
# Simulate running backtest
|
||||
ApiServer._bgtask_running = True
|
||||
rc = client_get(client, f"{BASE_URI}/backtest/abort")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert result['status'] == 'stopping'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest ended'
|
||||
|
||||
# Get running backtest...
|
||||
rc = client_get(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert result['status'] == 'running'
|
||||
assert result['running']
|
||||
assert result['step'] == "backtest"
|
||||
assert result['status_msg'] == "Backtest running"
|
||||
|
||||
# Try delete with task still running
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert result['status'] == 'running'
|
||||
|
||||
# Post to backtest that's still running
|
||||
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
||||
assert_response(rc, 502)
|
||||
result = rc.json()
|
||||
assert 'Bot Background task already running' in result['error']
|
||||
|
||||
ApiServer._bgtask_running = False
|
||||
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
|
||||
side_effect=DependencyException())
|
||||
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
||||
assert log_has("Backtesting caused an error: ", caplog)
|
||||
|
||||
# Delete backtesting to avoid leakage since the backtest-object may stick around.
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest")
|
||||
assert_response(rc)
|
||||
|
||||
result = rc.json()
|
||||
assert result['status'] == 'reset'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest reset'
|
||||
|
@@ -5,6 +5,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.enums import RPCMessageType
|
||||
from freqtrade.rpc import RPCManager
|
||||
from freqtrade.rpc.api_server.webserver import ApiServer
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
@@ -190,3 +191,4 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None:
|
||||
assert len(rpc_manager.registered_modules) == 1
|
||||
assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
assert run_mock.call_count == 1
|
||||
ApiServer.shutdown()
|
||||
|
@@ -452,7 +452,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||
assert ('∙ `-0.00000500 BTC (-0.50%) (-0.5 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01)
|
||||
assert ('∙ `-0.00000500 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
in msg_mock.call_args_list[-1][0][0])
|
||||
msg_mock.reset_mock()
|
||||
|
||||
@@ -466,11 +467,11 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
telegram._profit(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0]
|
||||
assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
assert ('∙ `0.00006217 BTC (6.20%) (0.62 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
in msg_mock.call_args_list[-1][0][0])
|
||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||
assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
assert ('∙ `0.00006217 BTC (6.20%) (0.62 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||
in msg_mock.call_args_list[-1][0][0])
|
||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
@@ -172,7 +172,7 @@ def test_download_data_options() -> None:
|
||||
def test_plot_dataframe_options() -> None:
|
||||
args = [
|
||||
'plot-dataframe',
|
||||
'-c', 'config_bittrex.json.example',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'--indicators1', 'sma10', 'sma100',
|
||||
'--indicators2', 'macd', 'fastd', 'fastk',
|
||||
'--plot-limit', '30',
|
||||
|
@@ -28,7 +28,7 @@ from tests.conftest import log_has, log_has_re, patched_configuration_load_confi
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def all_conf():
|
||||
config_file = Path(__file__).parents[1] / "config_full.json.example"
|
||||
config_file = Path(__file__).parents[1] / "config_examples/config_full.example.json"
|
||||
conf = load_config_file(str(config_file))
|
||||
return conf
|
||||
|
||||
|
@@ -161,7 +161,7 @@ def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
(True, 0.0022, 3, 0.5, [0.001, 0.001, 0.0]),
|
||||
(True, 0.0027, 3, 0.5, [0.001, 0.001, 0.000673]),
|
||||
(True, 0.0022, 3, 1, [0.001, 0.001, 0.0]),
|
||||
])
|
||||
])
|
||||
def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_buy_order_open,
|
||||
amend_last, wallet, max_open, lsamr, expected) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
@@ -397,7 +397,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open,
|
||||
|
||||
|
||||
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order_open,
|
||||
fee, mocker) -> None:
|
||||
fee, mocker, caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
buy_mock = MagicMock(return_value=limit_buy_order_open)
|
||||
@@ -413,6 +413,27 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert freqtrade.create_trade('ETH/BTC')
|
||||
assert log_has_re(r"Stake amount for pair .* is too small.*", caplog)
|
||||
|
||||
|
||||
def test_create_trade_zero_stake_amount(default_conf, ticker, limit_buy_order_open,
|
||||
fee, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
buy_mock = MagicMock(return_value=limit_buy_order_open)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.config['stake_amount'] = 0
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert not freqtrade.create_trade('ETH/BTC')
|
||||
|
||||
|
||||
@@ -763,7 +784,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
buy_mm = MagicMock(return_value=limit_buy_order_open)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_buy_rate=buy_rate_mock,
|
||||
get_rate=buy_rate_mock,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 0.00001172,
|
||||
'ask': 0.00001173,
|
||||
@@ -803,7 +824,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order_open['id'] = '33'
|
||||
fix_price = 0.06
|
||||
assert freqtrade.execute_buy(pair, stake_amount, fix_price)
|
||||
# Make sure get_buy_rate wasn't called again
|
||||
# Make sure get_rate wasn't called again
|
||||
assert buy_rate_mock.call_count == 0
|
||||
|
||||
assert buy_mm.call_count == 2
|
||||
@@ -842,6 +863,24 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
assert trade.open_rate == 0.5
|
||||
assert trade.stake_amount == 40.495905365
|
||||
|
||||
# Test with custom stake
|
||||
limit_buy_order['status'] = 'open'
|
||||
limit_buy_order['id'] = '556'
|
||||
|
||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
trade = Trade.query.all()[4]
|
||||
assert trade
|
||||
assert trade.stake_amount == 150
|
||||
|
||||
# Exception case
|
||||
limit_buy_order['id'] = '557'
|
||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
trade = Trade.query.all()[5]
|
||||
assert trade
|
||||
assert trade.stake_amount == 2.0
|
||||
|
||||
# In case of the order is rejected and not filled at all
|
||||
limit_buy_order['status'] = 'rejected'
|
||||
limit_buy_order['amount'] = 90.99181073
|
||||
@@ -854,7 +893,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
||||
|
||||
# Fail to get price...
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_buy_rate', MagicMock(return_value=0.0))
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||
|
||||
with pytest.raises(PricingError, match="Could not determine buy price."):
|
||||
freqtrade.execute_buy(pair, stake_amount)
|
||||
@@ -870,7 +909,7 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order),
|
||||
get_buy_rate=MagicMock(return_value=0.11),
|
||||
get_rate=MagicMock(return_value=0.11),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
@@ -2474,7 +2513,7 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
'freqtrade.exchange.Exchange',
|
||||
cancel_order=cancel_order_mock,
|
||||
)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate', return_value=0.245441)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.245441)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
@@ -3917,7 +3956,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
|
||||
|
||||
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||
"""
|
||||
test if function get_buy_rate will return the order book price
|
||||
test if function get_rate will return the order book price
|
||||
instead of the ask rate
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
@@ -3935,7 +3974,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||
default_conf['telegram']['enabled'] = False
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
assert freqtrade.exchange.get_buy_rate('ETH/BTC', True) == 0.043935
|
||||
assert freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy") == 0.043935
|
||||
assert ticker_mock.call_count == 0
|
||||
|
||||
|
||||
@@ -3957,8 +3996,8 @@ def test_order_book_bid_strategy_exception(mocker, default_conf, caplog) -> None
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
# orderbook shall be used even if tickers would be lower.
|
||||
with pytest.raises(PricingError):
|
||||
freqtrade.exchange.get_buy_rate('ETH/BTC', refresh=True)
|
||||
assert log_has_re(r'Buy Price from orderbook could not be determined.', caplog)
|
||||
freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy")
|
||||
assert log_has_re(r'Buy Price at location 1 from orderbook could not be determined.', caplog)
|
||||
|
||||
|
||||
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None:
|
||||
|
@@ -67,12 +67,12 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_bittrex.json.example']
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_bittrex.json.example ...', caplog)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
|
||||
|
||||
@@ -85,12 +85,12 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_bittrex.json.example']
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_bittrex.json.example ...', caplog)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('SIGINT received, aborting ...', caplog)
|
||||
|
||||
|
||||
@@ -106,12 +106,12 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_bittrex.json.example']
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_bittrex.json.example ...', caplog)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Oh snap!', caplog)
|
||||
|
||||
|
||||
@@ -157,12 +157,16 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = Arguments(['trade', '-c', 'config_bittrex.json.example']).get_parsed_arg()
|
||||
args = Arguments([
|
||||
'trade',
|
||||
'-c',
|
||||
'config_examples/config_bittrex.example.json'
|
||||
]).get_parsed_arg()
|
||||
worker = Worker(args=args, config=default_conf)
|
||||
with pytest.raises(SystemExit):
|
||||
main(['trade', '-c', 'config_bittrex.json.example'])
|
||||
main(['trade', '-c', 'config_examples/config_bittrex.example.json'])
|
||||
|
||||
assert log_has('Using config: config_bittrex.json.example ...', caplog)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert worker_mock.call_count == 4
|
||||
assert reconfigure_mock.call_count == 1
|
||||
assert isinstance(worker.freqtrade, FreqtradeBot)
|
||||
@@ -180,7 +184,11 @@ def test_reconfigure(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = Arguments(['trade', '-c', 'config_bittrex.json.example']).get_parsed_arg()
|
||||
args = Arguments([
|
||||
'trade',
|
||||
'-c',
|
||||
'config_examples/config_bittrex.example.json'
|
||||
]).get_parsed_arg()
|
||||
worker = Worker(args=args, config=default_conf)
|
||||
freqtrade = worker.freqtrade
|
||||
|
||||
|
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.misc import (decimals_per_coin, file_dump_json, file_load_json, format_ms_time,
|
||||
pair_to_filename, plural, render_template,
|
||||
pair_to_filename, parse_db_uri_for_logging, plural, render_template,
|
||||
render_template_with_fallback, round_coin_value, safe_value_fallback,
|
||||
safe_value_fallback2, shorten_date)
|
||||
|
||||
@@ -179,3 +179,18 @@ def test_render_template_fallback(mocker):
|
||||
)
|
||||
assert isinstance(val, str)
|
||||
assert 'if self.dp' in val
|
||||
|
||||
|
||||
def test_parse_db_uri_for_logging() -> None:
|
||||
postgresql_conn_uri = "postgresql+psycopg2://scott123:scott123@host/dbname"
|
||||
mariadb_conn_uri = "mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company"
|
||||
mysql_conn_uri = "mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4"
|
||||
sqlite_conn_uri = "sqlite:////freqtrade/user_data/tradesv3.sqlite"
|
||||
censored_pwd = "*****"
|
||||
|
||||
def get_pwd(x): return x.split(':')[2].split('@')[0]
|
||||
|
||||
assert get_pwd(parse_db_uri_for_logging(postgresql_conn_uri)) == censored_pwd
|
||||
assert get_pwd(parse_db_uri_for_logging(mariadb_conn_uri)) == censored_pwd
|
||||
assert get_pwd(parse_db_uri_for_logging(mysql_conn_uri)) == censored_pwd
|
||||
assert sqlite_conn_uri == parse_db_uri_for_logging(sqlite_conn_uri)
|
||||
|
@@ -1124,6 +1124,21 @@ def test_total_open_trades_stakes(fee, use_db):
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
def test_get_total_closed_profit(fee, use_db):
|
||||
|
||||
Trade.use_db = use_db
|
||||
Trade.reset_trades()
|
||||
res = Trade.get_total_closed_profit()
|
||||
assert res == 0
|
||||
create_mock_trades(fee, use_db)
|
||||
res = Trade.get_total_closed_profit()
|
||||
assert res == 0.000739127
|
||||
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
def test_get_trades_proxy(fee, use_db):
|
||||
@@ -1298,6 +1313,7 @@ def test_Trade_object_idem():
|
||||
'open_date',
|
||||
'get_best_pair',
|
||||
'get_overall_performance',
|
||||
'get_total_closed_profit',
|
||||
'total_open_trades_stakes',
|
||||
'get_sold_trades_without_assigned_fees',
|
||||
'get_open_trades_without_assigned_fees',
|
||||
|
@@ -364,7 +364,7 @@ def test_start_plot_dataframe(mocker):
|
||||
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
|
||||
args = [
|
||||
"plot-dataframe",
|
||||
"--config", "config_bittrex.json.example",
|
||||
"--config", "config_examples/config_bittrex.example.json",
|
||||
"--pairs", "ETH/BTC"
|
||||
]
|
||||
start_plot_dataframe(get_args(args))
|
||||
@@ -408,7 +408,7 @@ def test_start_plot_profit(mocker):
|
||||
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
|
||||
args = [
|
||||
"plot-profit",
|
||||
"--config", "config_bittrex.json.example",
|
||||
"--config", "config_examples/config_bittrex.example.json",
|
||||
"--pairs", "ETH/BTC"
|
||||
]
|
||||
start_plot_profit(get_args(args))
|
||||
|
@@ -121,13 +121,19 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("balance_ratio,result1,result2", [
|
||||
(1, 50, 66.66666),
|
||||
(0.99, 49.5, 66.0),
|
||||
(0.50, 25, 33.3333),
|
||||
@pytest.mark.parametrize("balance_ratio,capital,result1,result2", [
|
||||
(1, None, 50, 66.66666),
|
||||
(0.99, None, 49.5, 66.0),
|
||||
(0.50, None, 25, 33.3333),
|
||||
# Tests with capital ignore balance_ratio
|
||||
(1, 100, 50, 0.0),
|
||||
(0.99, 200, 50, 66.66666),
|
||||
(0.99, 150, 50, 50),
|
||||
(0.50, 50, 25, 0.0),
|
||||
(0.50, 10, 5, 0.0),
|
||||
])
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
|
||||
result2, limit_buy_order_open,
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, capital,
|
||||
result1, result2, limit_buy_order_open,
|
||||
fee, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -141,6 +147,8 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
||||
conf['dry_run_wallet'] = 100
|
||||
conf['max_open_trades'] = 2
|
||||
conf['tradable_balance_ratio'] = balance_ratio
|
||||
if capital is not None:
|
||||
conf['available_capital'] = capital
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
@@ -170,3 +178,49 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
||||
freqtrade.config['max_open_trades'] = 0
|
||||
result = freqtrade.wallets.get_trade_stake_amount('NEO/USDT')
|
||||
assert result == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stake_amount,min_stake_amount,max_stake_amount,expected', [
|
||||
(22, 11, 50, 22),
|
||||
(100, 11, 500, 100),
|
||||
(1000, 11, 500, 500), # Above max-stake
|
||||
(20, 15, 10, 0), # Minimum stake > max-stake
|
||||
(1, 11, 100, 11), # Below min stake
|
||||
(1, 15, 10, 0), # Below min stake and min_stake > max_stake
|
||||
|
||||
])
|
||||
def test__validate_stake_amount(mocker, default_conf,
|
||||
stake_amount, min_stake_amount, max_stake_amount, expected):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount",
|
||||
return_value=max_stake_amount)
|
||||
res = freqtrade.wallets._validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount)
|
||||
assert res == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('available_capital,closed_profit,open_stakes,free,expected', [
|
||||
(None, 10, 100, 910, 1000),
|
||||
(None, 0, 0, 2500, 2500),
|
||||
(None, 500, 0, 2500, 2000),
|
||||
(None, 500, 0, 2500, 2000),
|
||||
(None, -70, 0, 1930, 2000),
|
||||
# Only available balance matters when it's set.
|
||||
(100, 0, 0, 0, 100),
|
||||
(1000, 0, 2, 5, 1000),
|
||||
(1235, 2250, 2, 5, 1235),
|
||||
(1235, -2250, 2, 5, 1235),
|
||||
])
|
||||
def test_get_starting_balance(mocker, default_conf, available_capital, closed_profit,
|
||||
open_stakes, free, expected):
|
||||
if available_capital:
|
||||
default_conf['available_capital'] = available_capital
|
||||
mocker.patch("freqtrade.persistence.models.Trade.get_total_closed_profit",
|
||||
return_value=closed_profit)
|
||||
mocker.patch("freqtrade.persistence.models.Trade.total_open_trades_stakes",
|
||||
return_value=open_stakes)
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=free)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
assert freqtrade.wallets.get_starting_balance() == expected
|
||||
|
Reference in New Issue
Block a user